OCaml for Haskellers

来源:互联网 发布:php 记录错误日志 编辑:程序博客网 时间:2024/05/21 09:47
最大的差别是OCaml默认impurestrict.

OCaml特有的特性:

  • OCaml有命名参数(~x:i 绑定命名参数x的值到i,~x~x:x的缩写).
  • OCaml有可选参数(?(x:i = default)以default为缺省值绑定i到可选命名参数x).
  • OCaml有open的联合类型([>'Integer of int|'Real of float])此处类型保留实现; 你可以将其赋值给具有类型'a number = [> 'Integer of int | 'Real of float]as a的类型. 匿名closed联合也是允许的([<'Integer of int|'Real of float]).
  • OCaml有可变记录(在定义中preface记录字段可变, 并使用<-操作符赋值).
  • OCaml有模块系统.
  • OCaml有native对象.

省略部分意味着二者相同(比如, let f x y = x + y)

组织方式:(上一行为Haskell, 下一行为Ocaml)
类型:
()   Int Float Char String Bool (首字母大写)
unit int float char string bool (小写)


操作符:
  == /= .&. .|. xor shiftL shiftR complement
= == != land lor lxor [la]sl [la]sr lnot

(Haskell中,算术还是逻辑移位依赖于Bits的类型.)


OCaml中的俘点操作: 以.为后缀(如+.)

俘点转换:

floor fromIntegral
int_of_float float_of_int

字符串操作:

++ !!i
^ .[i] (注意: 字符串不是字符的列表)

复合类型:

(Int, Int) [Bool]
int * int  bool list

列表:

x : [1, 2, 3]
x :: [1; 2; 3]

数据类型:

data Tree a = Node a (Tree a) (Tree a) | Leaf
type 'a tree = Node of 'a * 'a tree * 'a tree | Leaf;;

(注意: 尽管类型参数实际不是tuple, OCaml还是需要用 Node(v,l,r) 做模式匹配)

记录:

data Rec = Rec { x :: Int, y :: Int }
type rec = { x : int; y : int };;

访问字段:
x r
r.x

更新字段:
r { x = 2 }
{ r with x = 2 }

(OCaml records also have destructive update.)

Maybe:
data Maybe a = Just a | Nothing
type 'a option = None | Some of 'a;;

数组:
readArray a i   writeArray a i v
[|1; 3|] a.(i)  a.(i)<- v

引用:
newIORef writeIORef readIORef
ref := !

全局定义:
x = 1
let x = 1;;

匿名函数:
\x y -> f y x
fun x y -> f y x

递归:
let f x = if x == 0 then 1 else x * f (x-1)
let rec f x = if x == 0 then 1 else x * f (x-1)

Mutual recursion (note that Haskell let is always recursive):

let f x = g x
    g x = f x
let rec f x = g x
and g x = f x

模式匹配:
let f 0 = 1
    f 1 = 2
let f = function
    | 0 -> 1
    | 1 -> 2

(note: you can put pattern matches in the arguments for OCaml, but lack of an equational function
definition style makes this not useful)

分支:
case f x of
    0 -> 1
    y | y > 5 -> 2
    y | y == 1 || y == 2 -> y
    _ -> -1
match f x with
    | 0 -> 1
    | y when y > 5 -> 2
    | (1 | 2) as y -> y
    | _ -> -1

例外:

Definition
data MyException = MyException String
exception MyException of string;;

Throw exception
throw (MyException "error")
raise (MyException "error")

Catch exception
catch expr $ \e -> case e of
   x -> result
try expr with
   | x -> result

Assertion
assert (f == 1) expr
assert (f == 1); expr

编译:
ghc --make file.hs
ocamlopt -o file file.ml

运行:
runghc file.hs
ocaml file.ml


类型签名. Haskell支持用两个冒号定义表达式的类型. OCaml有两种方式定义类型, 类型声名可以嵌入代码中:

let intEq (x : int) (y : int) : bool = ...

或放入接口文件中(*.mli):

val intEq : int -> int -> bool

Eta expansion. '_a形式的多态类型可以认为像Haskell的monomorphism(单一同态)限制: 仅能被实例化为一具体类型. However, 在Haskell中对于用户不期望的值monomorphism限制用于避免额外的重复计算; OCaml中在面对副作用时,为了保持类型系统的完整,值的限制是需要的, 并且这也适用于函数(仅查找签名中tell-tale '_a). 更基本的,'a意味着一个通用类型, 而'_a意味着一个当时还未知的具体类型 — Haskell中, 所有类型变量都是隐式普遍量化的, 因此前者总是对的(除了monomorphism限制开始生效, 甚至在那时,你也未曾看见任何类型变量. 但OCaml需要monomorphic类型变量防止脱离编译单元, 两者有点相似. 这有意义吗? 别怕.)

Haskell中, 通过定义显示的类型签名,可以使monomorphic值再次polymorphic. OCaml中, 通过eta expanding通用化一个类型. 权威的例子是id函数, 当它以自己为参数时(id id)就产生一个'_a-> '_a类型的函数 (也就是说,被限制了) 。可以通过写一个函数fun x-> id id x来恢复成类型'a-> 'a.

有一个精明的地方处理OCaml的impurity与strictness:eta expansion使用起来就像thunk, 因此如果eta expand的表达式有副作用, 它就会被延迟处理. 当然你可以使用fun ()-> expr来模拟典型的thunk。.

尾递归. 在Haskell中, 当计算是惰性时,你不用担心尾递归; instead你将计算放入一个数据结构中,以便用户不会强制要求超过其需要那么多(被看守的递归), 并且当模式匹配深入到结构中时,栈框架可以被高兴地丢弃. However, 如果你要实现一个类似foldl'这样的严格函数时, 你要注意不要建一个太大的thunk.

OCaml默认是严格的, 因此你总是应该注意尾递归调用. 一个有趣的地方是map函数的实现,其本地版本不能尾递归优化. Haskell中, 由于map是惰性的并且递归隐藏在cons构造子中,这就不是什么问题了; OCaml中, 当获得大列表时,在拷贝整个列表获得TCO或不拷贝而潜在用尽堆栈需要权衡. (注意Haskell中严格的map函数也有同样问题; 这是惰性与严格的区别, 而不是Haskell与OCaml的.)

单个OCaml脚本文件含有按顺序执行的语句列表.(没有main函数).

Haskell的模块对应OCaml的编译单元, 像foo.ml(小写)对应HaskellFoo模块, 或者Foo.foo指Foo中的foo函数.

好的实践是写接口文件(*.mli), 这就像输出列表. 接口文件中也可以有数据定义(忽略构造子来实现隐藏).

缺省时就像引入受限的Foo一样,所有模块是自动"被引入的"(不必有引入列表). 传统的可以使用不受限名字的引入方式在Ocaml中用open Foo实现.

OCaml没有类型类但使用模块能取得一样的效果. (另一个典型的取得类型类效果的方法是使用对象).