標準 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 + 4和3.0 + 4.0都是有效的(前者型別為
int,後者型別為real)——但型別之間沒有隱式提升。因此,像這樣的表示式3 + 4.0是無效的;必須編寫3.0 + 4.0,或者real 3 + 4.0(使用基本函式real將3轉換為3.0). - 整數除法使用特殊運算子表示div而不是斜線/;後者僅用於實數除法。(由於型別之間沒有隱式提升,因此像這樣的表示式3 / 4是無效的;必須編寫3.0 / 4.0或者real 3 / real 4。)還有一個取模運算子mod;例如,十七除以五是三餘二,所以17 div 5是3和17 mod 5是2。(更一般地說,如果 q 是一個正整數,那麼d = p div q和m = 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(其中 f 以 x 作為引數並返回一個接受 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(布林值)型別有兩個值,true 和 false。我們還看到了內建的多型相等運算子 =,其型別為 ''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 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 表示式是可以評估為函式的表示式,而無需將函式繫結到識別符號,也就是匿名函式、函式常量或函式字面量。這樣的函式可以使用 Standard ML 中的fn關鍵字定義。這對於高階函式特別有用,即接受其他函式作為引數的函式,例如內建的map和foldl函式。無需編寫
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關鍵字更好。