跳到內容

標準 ML 程式設計/表示式

來自華夏公益教科書,開放的書籍,開放的世界

詞法單元

[編輯 | 編輯原始碼]

標準 ML 程式由一系列“詞法單元”組成;它們可以被認為是語言的“詞”。一些最常見的詞法單元型別是

詞法單元型別 示例
特殊常量 2 , ~5.6 , "string" , #"c"
字母數字識別符號 x, mod, 'a
符號識別符號 + , - , * , /
關鍵字 val , = , ( , )

算術表示式

[編輯 | 編輯原始碼]

算術表示式類似於許多其他語言中的算術表示式

3 + 4
3.0 / 4.0
(2 - 3) * 6

但是,需要注意一些要點

  • 一元否定使用波浪號表示~而不是連字元-;後者僅用於二元減法。例如,三減負二寫為3 - ~2或者3 - ~ 2.
  • 雖然運算子“過載”以支援多種數值型別——例如,兩者都是3 + 43.0 + 4.0都是有效的(前者型別為 int,後者型別為 real)——但型別之間沒有隱式提升。因此,像這樣的表示式3 + 4.0是無效的;必須編寫3.0 + 4.0,或者real 3 + 4.0(使用基本函式real3轉換為3.0).
  • 整數除法使用特殊運算子表示div而不是斜線/;後者僅用於實數除法。(由於型別之間沒有隱式提升,因此像這樣的表示式3 / 4是無效的;必須編寫3.0 / 4.0或者real 3 / real 4。)還有一個取模運算子mod;例如,十七除以五是三餘二,所以17 div 5317 mod 52。(更一般地說,如果 q 是一個正整數,那麼d = p div qm = p mod q是整數,使得p = d * q + m, m >= 0,並且m < q。如果 q 是一個負整數,那麼p = d * q + m, m <= 0,並且m > q.)

函式呼叫

[編輯 | 編輯原始碼]

一旦函式被宣告

fun triple n = 3 * n

只需在函式名後新增一個引數即可呼叫它

val twelve = triple 4

在一般情況下,圓括號不是必需的,但它們通常是必要的用於分組。此外,正如我們在型別章節中所看到的,元組使用圓括號構建,並且將元組作為函式引數構建並不罕見

fun diff (a, b) = a - b
val six = diff (9, 3)

函式呼叫具有非常高的優先順序,高於任何中綴運算子;因此,triple 4 div 5 表示 (3 * 4) div 5,結果為 2,而不是 3 * (4 div 5),結果為 0。此外,它們是左結合的;f x y 表示 (f x) y(其中 fx 作為引數並返回一個接受 y 作為引數的函式),而不是 f (x y)

中綴函式呼叫

[編輯 | 編輯原始碼]

二元函式——即引數型別為 2 元組型別的函式——可以轉換為中綴運算子

fun minus (a, b) = a - b
val three = minus (5, 2)
infix minus
val seven = 4 minus ~3
val two = op minus (20, 18)

中綴運算子可以具有從 0 到 9 的任何優先順序級別,0(預設)為最低優先順序,9 為最高優先順序。標準基礎提供了這些內建的中綴規範

infix  7  * / div mod
infix  6  + - ^
infixr 5  :: @
infix  4  = <> > >= < <=
infix  3  := o
infix  0  before

注意,在第三行中,::@使用infixr而不是infix。這使得它們是結合的而不是結合的;而3 - 4 - 5意味著(3 - 4) - 5, 3 :: 4 :: nil意味著3 :: (4 :: nil).

識別符號實際上可以在它引用特定函式之前被宣告為中綴

infix pow
fun x pow y = Math.pow (x, y)
val eight = 2.0 pow 3.0

注意,在這種情況下,中綴表示法已經在宣告函式中使用。另一種方法是使用op在函式宣告中,無論函式是否已被宣告為中綴都適用

fun op pow (x, y) = Math.pow (x, y)

首選風格通常是不這樣做,但如果聲明發生在一個不確定函式是否已被宣告為中綴的環境中,或者當use可能在包含此類函式宣告的檔案中多次呼叫,其中函式在定義之後被宣告為中綴,這將很有用。它也可以是一種表明函式可能稍後被宣告為中綴的方式,可能在另一個檔案中,在這種情況下,確保解析檔案的順序無關緊要將很有用。

布林值和條件表示式

[編輯 | 編輯原始碼]

正如我們在型別章節中所看到的,bool(布林值)型別有兩個值,truefalse。我們還看到了內建的多型相等運算子 =,其型別為 ''a * ''a -> bool。密切相關的是不相等運算子 <>,其型別也為 ''a * ''a -> bool,當 = 返回 false 時,它返回 true,反之亦然。

<(小於)、>(大於)、<=(小於或等於)和 >=(大於或等於)運算子被過載以可用於各種數值、字元和字串型別(但與算術運算子一樣,兩個運算元必須具有相同的型別)。

布林值操作

[編輯 | 編輯原始碼]

三個主要函式對布林值進行操作

  • 函式 not,型別為 bool -> bool,將 true 對映為 false,反之亦然。
  • 中綴運算子 andalso,型別為 bool * bool -> bool,將 (true, true) 對映為 true,並將所有其他可能性對映為 false。這被稱為短路運算子;如果它的第一個運算元是 false,那麼它將返回 false 而不評估它的第二個運算元。
  • 中綴運算子 orelse,型別為 bool * bool -> bool,將 (false, false) 對映為 false,並將所有其他可能性對映為 true。像 andalso 一樣,它是一個短路運算子,但方向相反:如果它的第一個運算元是 true,那麼它將返回 true 而不評估它的第二個運算元。

條件表示式

[編輯 | 編輯原始碼]

布林值的一個主要用途是在條件表示式中。此形式的表示式

if boolean_expression then expression_if_true else expression_if_false

如果 boolean_expression 評估為 true,則評估為 expression_if_true 的結果;如果 boolean_expression 評估為 false,則評估為 expression_if_false 的結果。與短路運算子一樣,不會評估不需要的表示式。這使得條件表示式可用於建立遞迴函式

fun factorial x = if x = 0 then 1 else x * (factorial (x - 1))

它還允許條件副作用

if x = 0 then print "x = 0" else print "x <> 0"

注意,由於條件表示式返回一個值,因此“then”和“else”分支都需要存在,並且具有相同的型別,儘管它們不必具有特別的意義

if x = 0 then print "x = 0" else ()

case 表示式和模式匹配

[編輯 | 編輯原始碼]

函式可以由一個或多個規則組成。規則由其函式名、引數模式及其表示式組成。當函式被呼叫時,引數值將按自上而下的順序與模式匹配。使用模式匹配的函式與 case 表示式非常相似。事實上,可以將任何 case 結構轉換為函式。例如,這段程式碼

case compare(a,b) of
 GREATER => 1
 LESS => 2
 EQUAL => 3

在語義上等同於

fun case_example_function GREATER = 1
  | case_example_function LESS = 2
  | case_example_function EQUAL = 3;
case_example_function compare(a,b);

第一個匹配的規則將得到評估並返回其表示式。這意味著如果模式是重疊的(多個模式匹配給定的引數值),則必須牢記只有第一個匹配的規則會被評估

fun list_size (nil) = 0
 |  list_size (_::xs) = 1 + str_len xs;

如果對於所有合法引數值都存在一個匹配模式,則函式模式被稱為完全。以下示例是不完全的。

fun list_size ([a]) = 1
 |  list_size ([a,b]) = 2;

任何空列表或大小>2的列表都會導致Match異常。為了使其完全,可以新增一些模式。

fun list_size ([a]) = 1
 |  list_size ([a,b]) = 2
 |  list_size (nil) = 0
 |  list_size (_) = 0;

大小>2的列表將返回 0,這沒有太多意義,但函式模式現在是完全的。

異常用於中止評估。有幾個內建的異常可以使用raise關鍵字丟擲,並且可以使用exception關鍵字定義自己的異常。異常可以透過在宣告中寫入of附加訊息,並且使用方式與資料型別建構函式非常相似。異常可以使用handle關鍵字捕獲。示例

exception Exc of string;
fun divide (_, 0) = raise Exc("Cannot divide by zero")
  | divide (num, den) = num div den
val zero_or_infinity = divide (0, 0)
  handle Exc msg => (print (msg ^ "\n"); 0)

這將列印 "Cannot divide by zero" 並由於handle子句,將 zero_or_infinity 評估為 0。

內建異常包括 Empty、Domain 和 Fail(msg)。

Lambda 表示式

[編輯 | 編輯原始碼]

Lambda 表示式是可以評估為函式的表示式,而無需將函式繫結到識別符號,也就是匿名函式、函式常量或函式字面量。這樣的函式可以使用 Standard ML 中的fn關鍵字定義。這對於高階函式特別有用,即接受其他函式作為引數的函式,例如內建的mapfoldl函式。無需編寫

fun add_one x = x + 1
val incremented = map add_one [1, 2, 3]

你可以簡單地編寫

val incremented = map (fn x => x + 1) [1, 2, 3]

注意=>運算子用於代替=。Thefn關鍵字可以代替fun使用,包括模式匹配甚至遞迴(如果rec關鍵字使用)。

val rec fact = fn 1 => 1 | n => if n > 1 then n * fact(n - 1) else raise Domain

請注意,通常認為使用fun關鍵字更好。

Let 表示式

[編輯 | 編輯原始碼]
華夏公益教科書