Post

Learning Ocaml note1 - A Tour of Ocaml

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”,所以返回了()

This post is licensed under CC BY 4.0 by the author.