Learning Ocaml note1 - A Tour of Ocaml
先前对函数式编程语言产生了兴趣,学过一点haskell(虽然说连门都还没有入),里面确实有一些让我感到巧妙的设计,但是在实际应用的过程中,为了保持“pure”而付出的一些代价,总让我感到有些麻烦. 后来听说了ocaml这门语言,似乎在实际生产中,会更加“自由”; 而且据说还适合用于编译器、解析器或者符号求解等领域, 感觉很有趣,于是打算了解一下.
初步接触的体验:有了haskell的一点基础,至少对于基本的ocaml语法,都可以做到秒懂.
下面是基于官方tutorial的学习小记, 在utop (交互式console)中进行.
https://ocaml.org/docs/tour-of-ocaml
Expressions and Definitions
ocaml的语句以;;结尾. 如:
1
let u = [1; 2; 3; 4];;
元素和列表的“append”操作,用:: (而haskell中是:):
1
0 :: [1; 2; 3];;
作为一门函数式编程语言,if-else语句不是statement,而是一种”expression”:
1
2 * if "hello" = "world" then 3 else 5;;
ocaml中的“等于”和“不等于”有两种:=和<>只判读数值是否相等,但是==和!=则更强,会判断这两个变量在内存上的位置是否一样,也就是“这两个值是否是同一个”.
Functions
函数规则和haskell基本一致, 都使用柯里化做参数扩展. 但是ocaml还支持arg-label. 如一个函数的定义如下:- : suffix:string -> string -> bool = <fun>,那么就可以在赋值的时候使用:
1
String.ends_with ~suffix:"less" "stateless";;
lambda表达式:(fun x -> x * x) (haskell中对应的写法: (\x -> x*x))
至于IO操作,就比haskell要简单的多,直接用一些IO函数,而没有haskell中”IO action”等比较复杂的概念. 比如read_line的定义:- : unit -> string = <fun>,其中unit意味着不需要接受任何参数,但是在使用的时候需要用()来占位:
1
read_line ();;
定义递归函数的时候,需要增加rec关键词:
1
2
3
4
5
let rec range lo hi =
if lo > hi then
[]
else
lo :: range (lo + 1) hi;;
经过测试,这里的缩进并不重要.
Data and Typing
ocaml也为强类型语言,比如1 + 2.3会报错,需要手动转换:float_of_int 1 + 2.3.
pattern-matching和haskell基本一致,不过注意要加上with:
1
2
3
4
5
6
let g' x = match x with
| "foo" -> 1
| "bar" -> 2
| "baz" -> 3
| "qux" -> 4
| _ -> 0;;
而且同样支持pair/tuple类型的match:
1
2
3
let snd p =
match p with
| (_, y) -> y;;
类型的表示:列表的类型一般写作'a list的形式; pair/tuple用 * 连接, 比如'a * 'b.
自定义类型和haskell几乎没有差别:
1
2
3
4
5
6
7
8
9
10
11
type primary_colour = Red | Green | Blue;;
(*type primary_colour = Red | Green | Blue*)
type http_response =
| Data of string
| Error_code of int;;
(*type http_response = Data of string | Error_code of int*)
type page_range =
| All
| Current
| Range of int * int;;
(*type page_range = All | Current | Range of int * int*)
类型定义中,可以进行“自我递归 ”:
1
2
# show list;;
(*type 'a list = [] | (::) of 'a * 'a list*)
后半部分定义了一个运算符 (::) (加上括号表示当成一个整体的运算符), (xxx) of 'a * 'b表示运算符xxx可以用在类型'a和'b的中间,作为一个infix operator.
定义结构体, 或者说“Record”:
1
2
3
4
5
6
type person = {
first_name : string;
surname : string;
age : int
};;
(*type person = { first_name : string; surname : string; age : int; }*)
定义了一个Record后,后续如果定义了一个变量,包含相应的字段、且类型也符合,那么会被自动推断为这个Record的类型.
Dealing with Errors
我在haskell中还没有学到错误处理相关的内容,所以和其它语言类比.
第一种错误处理方法是try-catch体系, 通过raise来发起一个Exception:
1
2
let id_42 n = if n <> 42 then raise (Failure "Sorry") else n;;
try id_42 0 with Failure _ -> 0;;
第二种就是使用Result类型(和rust一样),其定义如下:
1
2
3
4
5
6
7
#show result;;
type ('a, 'b) result = Ok of 'a | Error of 'b
```ocaml
let id_42_res n = if n <> 42 then Error "Sorry" else Ok n;;
match id_42_res 0 with
| Ok n -> n
| Error _ -> 0;;
Working with Mutable State
从这里开始,和haskell就不一样了. haskell是不允许有mutable state出现的,但是ocaml却允许这种imperative programming形式的写法:
1
2
3
4
5
6
let x = ref 0;;
(*val r : int ref = {contents = 0}*)
!x ;;
(*- : int = 0*)
r := 42;;
(*- : unit = ()*)
在纯粹的函数式语言中,一个变量要求在定义的时候就确定了(constant), 不允许存在这种在后续程序中能够改变的变量(在ocaml中称为reference,其类型也多轮一层ref修饰)
可以用!对ref进行“解引用”,用:=进行赋值。但是改变状态是一个”side effect”,所以返回了()