Pony語言學習(七)——表達式(Expressions)語法(單篇向)

一、字面量(Literals):

(一)Bool值:沒啥要說的,就是truefalse👊

(二)數值(Numeric Literals):

支持類型:

  • U8, U16, U32, U64, U128, USize, ULong
  • I8, I16, I32, I64, I128, ISize, ILong
  • F32, F64

當然,作爲一門現代編程語言,Pony支持自動類型推導,但有時候它與我們用C++、Swift時的直覺相悖:

let my_explicit_unsigned: U32 = 42_000
let my_constructor_unsigned = U8(1)
let my_constructor_float = F64(1.234)

同Swift一樣,數值字面量可以用下劃線分割。此外你還注意到,U8(1),F64(1.234)這些字面量帶上了類型,有些用戶說,這從C的觀點來看,這類型推導完全沒有技術含量啊,我都已經告訴你類型了,哪裏還有推導的餘地?其實,大多數(用過之前版本的)Java用戶並不同意這樣的觀點,因爲他們之前會寫出這樣的代碼:

AReallyUselessClass anotherUselessObj = new AReallyUselessClass("useless things!")

其中痛楚無需贅言。

除此之外,Pony還支持不同進制(指2 8 10 16進制)的數值字面量(不同於Erlang支持2—36進制)以及科學計數法:

let my_decimal_int: I32 = 1024
let my_hexadecimal_int: I32 = 0x400
let my_binary_int: I32 = 0b10000000000

let my_double_precision_float: F64 = 0.009999999776482582092285156250
let my_scientific_float: F32 = 42.12e-4

(三)字符(Character Literals):

各位在C語言裏都見的不愛見了,但是在很多現代語言裏,人們似乎更喜歡用string去寫業務邏輯,不論是char還是Char似乎只有在foo_str[1]裏出現。Pony裏除了能用單引號(')括起來一個字符外,還支持多字節字面量(Multibyte Character literals):

let big_a: U8 = 'A'                 // 65
let hex_escaped_big_a: U8 = '\x41'  // 65
let newline: U32 = '\n'             // 10

let multiByte: U64 = 'ABCD' // 0x41424344

別把它當Javascript🙏!

(四)字符串(String Literals):

無論如何,string被現代語言處理的已經可以稱得上elegant了。大概是從PHP開始吧,人們注意到只用“”並不夠用,所以造了一個多行字符串,後來又衍生出原字符串(raw string)和插值語法。總之,從語言層面上,string這個概念已經很完美了。現在讓我們看看Pony裏字符串是什麼樣子:

use "format"

actor Main
  new create(env: Env) =>

    let pony = "🐎"
    let pony_hex_escaped = "p\xF6n\xFF"
    let pony_unicode_escape = "\U01F40E"

    env.out.print(pony + " " + pony_hex_escaped + " " + pony_unicode_escape)
    for b in pony.values() do
      env.out.print(Format.int[U8](b, FormatHex))
    end

自蘋果公司發佈的iOS 5輸入法中加入了emoji後,這種表情符號開始席捲全球。在2015年蘋果推出的編程語言Swift,當然是後來的教程中,也明確表示emoji可以被加入到字符串,似乎這就是在提醒我們把emoji應當看作是字符和表情的結合體。字符串之字符的取值範圍一步步的擴大,我們有理由相信string除了elegant還能變得even more powerful:

let stacked_ponies = "
🐎
🐎
🐎
"

let u_umlaut = "ü"

let triple_quoted_string_docs =
  """
  Triple quoted strings are the way to go for long multiline text.
  They are extensively used as docstrings which are turned into api documentation.

  They get some special treatment, in order to keep Pony code readable:

  * The string literal starts on the line after the opening triple quote.
  * Common indentation is removed from the string literal
    so it can be conveniently aligned with the enclosing indentation
    e.g. each line of this literal will get its first two whitespaces removed
  * Whitespace after the opening and before the closing triple quote will be
    removed as well. The first line will be completely removed if it only 
    contains whitespace. e.g. this strings first character is `T` not `\n`.
  """

上述三例均來自官網,意義分別爲:1.支持多行字符串(公平對待換行符)2.可以直接打入特殊字符3.三雙引號多行字符串。

最後一個被拿來用作大部分現代編程語言的多行字符串處理方案,它還可以更強大!就比如Ceylon就把這種字符串當成了註釋方式的一種。

關於比較字符串,Java程序員又要抱怨了:什麼是池?什麼是哈希值?什麼是實例?如果僅僅是比較字符串就要考慮這麼多,不禁有點小憂傷:

let pony = "🐎"
let another_pony = "🐎"
if pony is another_pony then
  // True, therefore this line will run.
end

(五)數組(Array Literals):

關於數組,STL的容器思想和Java的Collection逼得人們不得不從一個更高的層面去思考這樣一個數據結構。在Pony中,things got a little different:

let my_literal_array =
  [
    "first"; "second"
    "third one on a new line"
  ]

1.類型推導:

以下介紹會涉及部分Reference Capability的知識,建議囫圇吞棗🙈

let my_heterogenous_array = 
  [
    U64(42)
    "42"
    U64.min_value()
  ]

默認數組的元素可以改變,所以通常我們聲明出來的數組類型爲:Array[T] ref,其中T是泛型語法。上面這個數組的類型就是Array[(U64|String)] ref。當然了,我們也可以顯式指明數組類型:

let my_stringable_array: Array[Stringable] ref =
  [
    U64(0xA)
    "0xA"
  ]

如果我們想要一個不可改變的安全的數組:

let my_immutable_array: Array[Stringable] val =
  [
    U64(0xBEEF)
    "0xBEEF"
  ]

2.As表達式:

有時候,我們需要限制類型推導但同時不失靈活性👌:

let my_as_array =
  [ as Stringable:
    U64(0xFFEF)
    "0xFFEF"
    U64(1 + 1)
  ]

否則,它將被推導爲Array[(U64|String)] ref😱

二、變量(Variables):

(一)局部變量:

同Swift,var用來聲明可變量,let用來聲明常量:

var x: String = "Hello, world!"
let name = "PECman"

出於安全考慮(依舊同Swift),局部常量在聲明之初必須賦值(從併發的思想去思考爲什麼需要這樣的限制),而變量是沒有此限制的:

var z: String
z = "Hello"    //🆗
let y: U32 // Error😥, can't declare a let local without assigning to it
y = 6 // Error😭, can't reassign to a let local

當然,局部意思是存在其作用域(scope):

if a > b then
  var x = "a is bigger"
  env.out.print(x)  // OK
end

env.out.print(x)  // Illegal

(二)字段(Fields):

和其他OOP語言類似,Pony對字段的改動並不大,只不過要注意下劃線開頭的字段是私有字段。除此之外,每一個字段的生命週期都與實例對象綁定。上面我們提到過一個局部變量的賦值限制,字段有一點點不同:

如果字段沒有在聲明時構造方法裏賦值,就會報錯:

class Wombat
  let name: String
  var _hunger_level: U64

  new ref create(name': String, level: U64) =>
    name = name'
    set_hunger_level(level)
    // Error: field _hunger_level left undefined in constructor
  
  fun ref set_hunger_level(hunger_level: U64) =>
    _hunger_level = hunger_level

嗯哼?

(三)嵌入字段(Embedded Fields):

與局部變量不同,某些類型的字段可以使用embed聲明。具體來說,字段只能嵌入類或結構而不能嵌入接口、特徵、原生類和數字類型。使用embed聲明的字段類似於使用let聲明的字段,但在實現(implementation)級別,嵌入類的內存直接在外部類中佈局。與let或var相比,實現使用指針來引用field類。嵌入字段可以以與let或var字段完全相同的方式傳遞給其他函數。嵌入字段必須從構造函數表達式初始化。

爲什麼要使用嵌入?嵌入避免了訪問字段時的指針間接尋址和創建該字段時的單獨內存分配。默認情況下,如果可能,建議使用嵌入。但是,由於嵌入字段與其父對象一起分配,因此對該字段的外部引用禁止對父對象進行垃圾收集,如果字段比其父對象長,則可能導致更高的內存使用率。如果這恰好是你關心的問題,請使用let。

(四)全局變量和重名變量:

Some programming languages have global variables that can be accessed from anywhere in the code. What a bad idea! Pony doesn’t have global variables at all.

同時,很多編程語言裏都允許你利用遮蔽(shadowing)覆蓋變量,如:

fn main() {
    let mut mutable = 200;
    let mutable = true  //利用遮蔽更改類型和值
}

然而:

If you accidentally shadow a variable in Pony, the compiler will complain.

 If you need a variable with nearly the same name, you can use a prime '😊.

三、操作符(Operators):

(一)操作符別名:

下面兩個語句是等效的:

x + y
x.add(y)

依此,我們可以改變一些二元操作符的業務邏輯:

// Define a suitable type
class Pair
  var _x: U32 = 0
  var _y: U32 = 0

  new create(x: U32, y: U32) =>
    _x = x
    _y = y

  // Define a + function
  fun add(other: Pair): Pair =>
    Pair(_x + other._x, _y + other._y)

// Now let's use it
class Foo
  fun foo() =>
    var x = Pair(1, 2)
    var y = Pair(3, 4)
    var z = x + y

和C++一樣,咱們不能逮着一個操作符就把它給改了,這裏有一份列表,以示範圍:

 (二)一元操作符:

以下兩行代碼等價:

-x
x.neg()

同上,也有一張表:

官網還有短路運算、優先級等知識:https://tutorial.ponylang.io/expressions/ops.html,此處從略

算術(Arithmetic)相關知識和操作符關聯很大:https://tutorial.ponylang.io/expressions/arithmetic.html,請自行閱讀。

四、控制流(Control Structures):

(一)條件判斷:

一個例子搞清基礎語法:if then elseif then ... else

if a == b then
  env.out.print("they are the same")
else
  if a > b then
    env.out.print("a is bigger")
  else
    env.out.print("b bigger")
  end
end

此外,不同於C中可以用1或0或任意一個整數或指針代替條件,Pony的條件表達式返回值就是布爾值,不能是其他值.

(二)重要理念——萬物皆表達式(而表達式就有值):

這一點在很多現代編程語言裏很常見,你有充分理由把它理解爲三元運算符的延申:

var x: (String | Bool) =
  if friendly then
    "Hello"
  else
    false
  end

當然,對於空類型(None)各大現代編程語言也有所支持,例如Scala中有Nothing等,Swift和Ceylon也各有方案。主要是想萃取null或nullptr的功能:

var x: (String | None) =
  if friendly then
    "Hello"
  end

記住,表達式一定要返回值,哪怕是空值!

(三)循環語句:

1.While循環:

var count: U32 = 1

while count <= 10 do
  env.out.print(count.string())
  count = count + 1
end

按照萬物皆表達式的原則,這個while表達式會返回什麼呢?答案是10.(注:Pony中表達式永遠返回舊值,例如:

a=b=a  <=>  swap = a; a = b; b = a)

2.Break和Continue語句:

這兩個語句的重要性就不用多說了,最著名的是那個意大利麪程序(goto語句帶來的災難)。剛剛說過,萬物皆表達式,而表達式則需要返回值。讓我們認真分析一下break和continue的應用場景:

(1)Break語句意味着跳出循環,象徵着循環表達式的結束,這樣就暗示需要返回值。在Pony中,我們通常需要這樣使用break:

var name =
  while moreNames() do
    var name' = getName()
    if name' == "Jack" or name' == "Jill" then
      break name'
    end
    name'
  else
    "Herbert"
  end

有的讀者見過帶標籤的循環/break語句(Swift代碼):

outerloop:
for  i in 0 ..< 10
{
  innerloop:    
    for j in 0 ..< 10
    {
        if (j > 3)
        {
            break;
        }
        if (i == 2)
        {
            break innerloop;
        }
        if (i == 4)
        {
            break outerloop;
        }
        print("i = \(i)  and with j = \(j)"  )
    }
}

在Pony中這是禁止的。Pony認爲如果你使用了類似的語法,說明你的代碼存在算法級別問題,也就是說你需要重構你的代碼。

(2)continue語句通常代表繼續循環,可能循環沒結束,也有可能下一輪循環就會結束(條件不符時),這個時候你並不需要像break語句一樣附帶一個值。循環中的else代碼塊會起到大作用

2.For循環:

和Swift中一樣,Pony放棄了C風格的For循環,採用了迭代For循環:

for name in ["Bob"; "Fred"; "Sarah"].values() do
  env.out.print(name)
end

同時,Pony也支持一種STL用戶熟悉的語法(注意迭代器被聲明爲常量):

let iterator = ["Bob"; "Fred"; "Sarah"].values()
while iterator.has_next() do
  let name = iterator.next()?
  env.out.print(name)
end

其中?代表可選值的展開,swift中有類似概念:Swift中的問號?和感嘆號! Charly_Zheng

3.Repeat語句:

其實它非常像do while語句,只不過要有現代編程語言的一點樣子:

var counter = U64(1)
    repeat
      env.out.print("hello!")
      counter = counter + 1
    until counter > 7 end

記住,即使條件不符合這個循環也會執行一遍。儘管如此,你有時候仍然需要爲這個循環加上一個else代碼塊:

continue in the last iteration of a repeat loop needs to get a value from somewhere and an else expression is used for that.

 五、方法(Methods):

1.函數:

Pony functions are quite like functions (or methods) in other languages. 

 但是要注意,如果不指明返回值類型,一律按None處理:

class C
  fun add(x: U32, y: U32): U32 =>
    x + y

  fun nop() =>
    add(1, 2)  // Pointless, we ignore the result

不少人聽說過甚至可能是重載(overload)概念的狂熱愛好者,可惜,Pony並不允許這樣的事情發生。

2.構造器:

在類及其他類型中,我們使用new關鍵字聲明一個構造函數。如果你需要非常多的構造器,你可以用new指明哪些是構造器,但是,你必須要有一個create構造器(這是語法糖需要,詳情請看:語法糖)。依代碼邏輯所需,你可以提前退出構造器——使用return語句(你一定想到了!)

3.默認參數(Default arguments):

這是當年很多人爲之發愁的特性。很多同學在初學C語言時比較善思——printf是怎麼實現的呢?C語言裏對這方面處理的有點複雜,在一個合格的現代編程語言那裏應該這麼做:

class Coord
  var _x: U32
  var _y: U32

  new create(x: U32 = 0, y: U32 = 0) =>
    _x = x
    _y = y

class Bar
  fun f() =>
    var a: Coord = Coord.create()     // Contains (0, 0)
    var b: Coord = Coord.create(3)    // Contains (3, 0)
    var c: Coord = Coord.create(3, 4) // Contains (3, 4)

但是,另一個問題油然而生了。就拿C++中的Win32基礎開發,在註冊窗口類的時候,有很多參數實際上是不需要我們來打理的,這就意味着需要體現程序的自動優越性。在簡單的封裝之後,比如:

void createMyWindow(..., string title = "Title", int height = 80, int width = 100, ...)

假設我想用默認的標題但是我想改一改窗口的高度或寬度,我就必須先填好第一個參數,之後纔有權更改之後的參數。很煩,不是嗎?

4.命名參數:

Pony使用where語法解決了上述問題:

class Foo
  fun f(a: U32 = 1, b: U32 = 2, c: U32 = 3, d: U32 = 4, e: U32 = 5): U32  =>
    0

  fun g() =>
    f(6, 7 where d = 8)
    // Equivalent to:
    f(6, 7, 3, 8, 5)

記住,如果你的輸入數據起初是按順序的,那麼就先按順序來,之後再用where語句。

5.鏈語法:

primitive Printer
  fun print_two_strings(out: StdStream, s1: String, s2: String) =>
    out.>print(s1).>print(s2)
    // Equivalent to:
    out.print(s1)
    out.print(s2)
    out

object.>method(arg1: ...)     <===>    object.method(arg1:...) ; object

如果是這樣的鏈,則等於:

object.>method1(arg1:...).method2(arg1:...)  <===>  object.method1(arg1:...) ; object.method2(arg2:...)

細細思考,這是很自然的:

interface Factory
  fun add_option(o: Option)
  fun make_object(): Object

primitive Foo
  fun object_wrong(f: Factory, o1: Option, o2: Option): Object =>
    f.>add_option(o1).>add_option(o2).>make_object() // Error! The expression returns a Factory

  fun object_right(f: Factory, o1: Option, o2: Option): Object =>
    f.>add_option(o1).>add_option(o2).make_object() // Works. The expression returns an Object

6.匿名函數(閉包):

use "collections"

actor Main
  new create(env: Env) =>
    let list_of_numbers = List[U32].from([1; 2; 3; 4])
    let is_odd = {(n: U32): Bool => (n % 2) == 1}
    for odd_number in list_of_numbers.filter(is_odd).values() do
      env.out.print(odd_number.string())
    end

更詳細的闡述見:Object Literals section

7.其他注意事項:

 六、錯誤處理:

凡是完備的語言都需要錯誤處理,這方面比較出色的是Java,比較遜色則是Go和C++。這和代碼風格和語言傳統有很大關係,就和大括號到底寫在哪是一個道理。Pony這方面我還並沒有看到我想要的那種錯誤處理,畢竟剛剛0.9,出了1.0再說。

1.拋出和處理錯誤:

try
  callA()
  if not callB() then error end
  callC()
else
  callD()
end

記住,error不是泛指,而是特指一個名爲“error”的命令(command)。如果程序有哪裏讓你不滿意了,你就可以做一些處理,再使用error命令拋出錯誤。而關於try表達式的返回值,就是它的代碼塊之一的最後一句(你肯定又猜到了!)

我們在Java裏見到過finally塊,那麼它在Pony裏是怎麼被引入的呢?

try
  callA()
  if not callB() then error end
  callC()
else
  callD()
then
  callE()
end

即使你在try代碼塊裏返回了某個值或者None,then塊還是會執行的。

2.偏函數:

資料鏈接:Partial Functions(偏函數)

閱讀完後請讀者自己按其中要義自己寫出一個偏函數,之後可以和官網例子對照看看:

fun factorial(x: I32): I32 ? =>
  if x < 0 then error end
  if x == 0 then
    1
  else
    x * factorial(x - 1)?
  end

3.With語法:

with被用於簡化try塊(特別是當需要聲明變量時)和清除變量的操作,這一點和Python十分類似(在Python中被廣泛用於文件操作):

with obj = SomeObjectThatNeedsDisposing(), other = SomeOtherDisposableObject() do
  // use objs
else
  // only run if an error has occurred
end

如果你想要你的類也支持with語法:

class SomeObjectThatNeedsDisposing
  // constructor, other functions

  fun dispose() =>
    // release resources

*延伸閱讀:

七、結餘:

剩下的部分我認爲只有一個Object Literals section需要好好學習學習:https://tutorial.ponylang.io/expressions/object-literals.html。其他的話諸君儘可泛讀過去。爲了我們下面Reference Capability的學習,大家再加把勁,加油!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章