title: OCaml从入门到放弃
date: 2020-01-04 13:28:47
学习函数式语言
OCaml注释如下:
(* hello world *)
输入输出如下:
输入
read_int()
: 读入一个整数read_float()
: 读入一个浮点数read_line()
: 读入一个字符串输出
print_char 'a'
: 打印字符print_int 2
: 打印数字print_float 3.4
: 打印浮点数print_newline()
: 打印换行print_string "hello world"
: 打印字符串print_endline "hello world"
: 打印字符串,并换行Printf.printf "int %i, float %f, char %c, string %s\n" 3 3.2 'a' "ok";;
: 打印格式化字符串(注:OCaml中的函数调用:不需要括号,除非无参数时才需要;各参数之间用空格隔开。)
OCaml代码文件以.ml
结尾。
ocaml xxx.ml
ocamlc -c xxx.ml -o xxx.cmo
ocamlc -c xxx.ml -o xxx.o
ocamlc -o xxx xxx.cmo
ocamlc -o xxx xxx.ml
ocamlopt -o xxx xxx.ml
变量名开头字母不能大写,若大写则会被当作联合类型中的构造子。
int
: 整数类型float
: 浮点类型,浮点常数必须带小数点.
,否则会被视为int
类型char
: 字符类型string
: 字符串类型unit
: 啥都不是类型在OCaml中,没有强制类型转换,整型只能与整型变量做运算。
若要类型转换,需使用相应函数,如下:
float_of_int 1
: int => floatint_of_float 2.6
: float => intint_of_string "-23"
: string => intfloat_of_string "1.2e3"
: string => floatstring_of_int 12
: int => stringstring_of_float (-2.3)
: float => string字符与ASCII码:
int_of_char 'x'
: 获取字符x
的ASCII码值char_of_int 120
: 把ASCII码转换到字符let <变量1> = <表达式1> and <变量2> = <表达式2>
: 全局定义
let <变量1> = <表达式1> and <变量2> = <表达式2> in <表达式3>
: 局部定义
注意:let定义的变量是无法修改!!!修改需要使用命令式的方式,后文细说。
(a/b) * b + (a mod b)
: 整型运算+
, -
, *
, /
, mod
4e2 *. 2. /. 3. +. 1.
: 浮点型运算,运算符必须带小数点.
not true
: 非true && false
: 与true || false
: 或>
, <
, >=
, <=
如常
相等与不等:
=
and <>
: 结构化比较,对比结构内部的子元素==
and !=
: 物理比较,比较变量在内存中的存储地址(即比较地址)对于非结构化数据,两种相等于不等相同。基本类型中,整型和字符是非结构化数据,浮点型和字符串都是结构化数据。
op1 land op2
: 按位与op1 lor op2
: 按位或op1 lxor op2
: 按位异或op1 lsl op2
: op1左移op2位op1 lsr op2
: op1右移op2位op1 asr op2
: op1算术右移op2位格式:
if <条件表达式> then <表达式1> [ else <表达式2> ]
示例:
# if 1<2 then
true && false
else
true || false;;
- : bool = false
纯函数式语言没有循环。惊不惊喜,意不意外。
需要循环完成的工作,可以通过递归函数来完成。
强大至极
格式:
match <表达式> with
| <模式1> [when <条件1>] -> <表达式1> (* 第一个 | 可以省略 *)
| <模式2> [when <条件2>] -> <表达式2>
...
| <模式n> [when <条件n>] -> <表达式n>
基础使用示例:
(* 取反 *)
let neg x =
match x with
| true -> false
| false -> true
;;
(* 类型:val neg : bool -> bool = <fun> *)
(* 是否为零 *)
let is_zero x =
match x with
| 0 -> true
| _ -> false (* 可以使用通配符 *)
;;
(* 类型:val is_zero : int -> bool = <fun> *)
(* 异或 *)
let xor z =
match z with
(false, false) -> false (* 第一个 | 可以省略 *)
| (false, true) -> true
| (true, false) -> true
| (true, true) -> false
;;
(* 类型:val xor : bool * bool -> bool = <fun> *)
let xor x y =
match x,y with
(false, u) -> u (* 在模式中可以使用变量 *)
| (true, u) -> not u
;;
(* 类型:val xor : bool -> bool -> bool = <fun> *)
let f (c:char) : string =
match c with
'0'..'9' -> "digit" (* 可以使用 <字符>..<字符> 字符区间模式 *)
| 'a'..'z' -> "lower char"
| 'A'..'Z' -> "upper char"
| _ -> "other char"
;;
(* val f : char -> string = <fun> *)
let int_of_bool = function (* function 可以直接做模式匹配,函数不带参数 *)
true -> 1
| false -> 0
;;
(* val int_of_bool : bool -> int = <fun> *)
First of all, 函数是变量。
let <fun_name> <参数1> <参数2>...<参数n> = <表达式>
let <fun_name> (<参数1>, <参数2>, ...<参数n>) = <表达式>
: 将多个参数合并到一个元组中
示例:
let add x y =
x + y ;;
(* 类型:val add : int -> int -> int = <fun> *)
print_int (add 1 2) ;; (* 3- : unit = () *)
(* 部分作用/部分求值 *)
let inc x =
add x 1 ;;
(* val inc : int -> int = <fun> *)
(* 将多个参数合并到一个元组中,但这种方式不能部分求值 *)
let plus3 (a, b, c) =
a + b + c ;;
(* val plus3 : int * int * int -> int = <fun> *)
print_int (plus3 (1,2,3)) ;; (* 6- : unit = () *)
let <fun_name> = function <参数> -> <表达式>
: 只能有一个参数,可用于模式匹配
let <fun_name> = fun <参数1> <参数2>...<参数n> -> <表达式>
: 多参数
函数作参和作返回值
在函数名前添加rec
关键字,指定是递归函数。
let rec <fun_name> <参数1> <参数2>...<参数n> = <表达式>
示例:
(* 普通递归 *)
let rec factorial n =
if n = 0 then
1
else
n * factorial (n-1) ;;
(* val factorial : int -> int = <fun> *)
尾递归实现循环,在参数中加入辅助函数。
(* 尾递归实现循环 *)
let fib n =
if n < 3 then
0
else
let rec fib_tail n n_1 n_2 =
if n = 3 then
n_1+n_2
else
fib_tail (n-1) (n_1+n_2) n_1
in fib_tail n 1 1
;;
(* val fib : int -> int = <fun> *)
格式:
type [<类型参数>] <类型标识符> = <类型定义表达式>
格式:
<元素1>, <元素2>...<元素n>
每个元素的类型可以不相同。
元组的类型描述为:
<元素1类型> * <元素2类型> *...<元素n类型>
示例:
# let a = "Number", 1;;
val a : string * int = ("Number", 1)
# let b = "pi", 3.14, 5 ;;
val b : string * float * int = ("pi", 3.14, 5)
# let c = 1, (2,3), ((4,5), 6) ;;
val c : int * (int * int) * ((int * int) * int) = (1, (2, 3), ((4, 5), 6))
只有二个元素的元组也称作对偶。
函数fst
和snd
分别取对偶的第一个和第二个分量。
定义一个记录类型:
type <类型标识符> = { <字段名1>:<类型1>; ... ; <字段名n>:<类型n> }
创建一个记录类型变量:
{ <字段名1> = <表达式1>; ... ; <字段名n> = <表达式n> }
访问记录类型中的字段:
<记录类型变量>.<字段名>
注意:上述记录类型不可修改!!!
格式:
type [<多态类型变量>] <类型标识符> =
<构造子名1> [of <参数类型1>]
...
<构造子名n> [of <参数类型n>]
注:构造子名首字母必须大写。
无参构造子。类似于枚举类型,构造子能和常数或者布尔值一样使用
示例:
type seasons = Spring | Summer | Autumn | Winter ;;
let int_of_seasons = function
Spring -> 1
| Summer -> 2
| Autumn -> 3
| Winter -> 4
;;
(* val int_of_seasons : seasons -> int = <fun> *)
int_of_seasons Autumn ;; (* - : int = 3 *)
带参构造子。构造子相当于类型,可以用于定义变量
示例:
type num = Int of int | Float of float ;;
Int 3 ;; (* - : num = Int 3 *)
let add_num = function
(Int m, Int n) -> Int (m+n)
| (Int m, Float n) -> Float ((float_of_int m) +. n)
| (Float m, Int n) -> Float (m +. (float_of_int n))
| (Float m, Float n) -> Float (m +. n)
;;
(* val add_num : num * num -> num = <fun> *)
示例,定义一个二叉树:
type inttree =
Leaf of int
| Node of inttree * inttree (* *号表明是元组类型,不是乘号 *)
;;
类型描述:
<类型> list
[ e1; e2; ... en;]
ei
可以是变量或者常量,但类型必须相同。
头插:
# 1::[2;3];;
- : int list = [1; 2; 3]
追加:
# List.append [1;2] [3;4] ;;
- : int list = [1; 2; 3; 4]
# [1;2] @ [3;4] ;;
- : int list = [1; 2; 3; 4]
表头是第一个元素,表尾是除第一个元素外的子表。
# List.hd [1;2;3] ;;
- : int = 1
# List.tl [1;2;3] ;;
- : int list = [2; 3]
注意:表中元素也不可以修改!!!
OCaml 名字 类型定义的例子 用法
list int list [1; 2; 3]
tuple int * string (3, "hello")
record type pair = { a = 3; b = "hello" }
{ a: int; b: string }
变体 type foo =
| Int of int Int 3
| Pair of int * string
变体 type sign =
| Positive Positive
| Zero Zero
| Negative
参数化变体 type 'a my_list =
| Empty Cons (1, Cons (2, Empty))
| Cons of 'a * 'a my_list
如果有一个util.ml
文件:
(* util.ml *)
let msg = "helloworld" ;;
let printMsg =
print_endline msg
;;
那么,可以在另一个main.ml
文件中:
print_endline Util.msg ;;
调用Util
模块(即util.ml
文件)中的变量或者函数。
如果还存在一个util.mli
文件,那它就是util.ml
模块的接口。未在util.mli
中出现的变量或函数,都不能被调用。同时,util.mli
文件需要在util.ml
文件编译前编译,如下:
ocamlc -c util.mli -o util.cmi
接口定义
module type <接口名> =
sig
<接口定义体>
end
<接口名>是一个大写字母开头的标识符,<接口定义体>中包括了 type 定义、函数的类型描述等内容。
模块定义:
module <模块名> [:<模块接口>] =
struct
<模块体>
end
<模块名>是一个大写字母开头的标识符,<模块体>中包括了 type 定义和 let 定义。
定义:
let <变量> = ref <表达式> ;;
引用:
!<变量>
赋值:
<变量> := <表达式>
定义:
type <记录类型> = {
...
mutable <分量名> : <分量类型>;
...
}
赋值:
<记录>.<分量> <- <表达式>
定义:
let arr = [|1;2;3|] ;;
访问:
<数组>.(<下标>) (* 如:mart.(1).(2) *)
赋值:
<数组>.(<下标>) <- <表达式>
常用函数:
Array.make 4 1; (* 创建长度为4的一维数组,都初始化为1 *)
Array.make_matrix 2 3 0; (* 创建行为2列为3的二维数组,都初始化为0 *)
Array.length arr; (* 返回数组长度 *)
Array.iter print_int [|1;2;3|]; (* 把一个函数作用于数组每个元素上 *)
for <变量>=<初始表达式> to <终止表达式> do
<表达式1>;
...
<表达式n>;
done