OCaml从入门到放弃
[TOC]
0. 前言
学习函数式语言
1. 注释与输入输出
OCaml注释如下:
1 | (* 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中的函数调用:不需要括号,除非无参数时才需要;各参数之间用空格隔开。)
2. 编译与运行
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
- 字节码:
- 编译生成可重定位文件:
3. 变量
变量名开头字母不能大写,若大写则会被当作联合类型中的构造子。
3.1. 基本类型
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码转换到字符
3.2. let 定义
let <变量1> = <表达式1> and <变量2> = <表达式2>
: 全局定义- 必须赋初值
- 可重复定义一个变量
- 不能在表达式内部使用
let <变量1> = <表达式1> and <变量2> = <表达式2> in <表达式3>
: 局部定义- 必须赋初值
- 可重复定义一个变量
- 可在表达式内部使用
- 变量1,2作用域局限于表达式3
注意:let定义的变量是无法修改!!!修改需要使用命令式的方式,后文细说。
3.3. 多态类型
4. 表达式
4.1. 算术运算
(a/b) * b + (a mod b)
: 整型运算+
,-
,*
,/
,mod
4e2 *. 2. /. 3. +. 1.
: 浮点型运算,运算符必须带小数点.
4.2. 逻辑运算
not true
: 非true && false
: 与true || false
: 或
4.3. 比较运算
>
, <
, >=
, <=
如常
相等与不等:
=
and<>
: 结构化比较,对比结构内部的子元素==
and!=
: 物理比较,比较变量在内存中的存储地址(即比较地址)
对于非结构化数据,两种相等于不等相同。基本类型中,整型和字符是非结构化数据,浮点型和字符串都是结构化数据。
4.4. 位运算
op1 land op2
: 按位与op1 lor op2
: 按位或op1 lxor op2
: 按位异或op1 lsl op2
: op1左移op2位op1 lsr op2
: op1右移op2位op1 asr op2
: op1算术右移op2位
4.5. if 表达式
格式:
1 | if <条件表达式> then <表达式1> [ else <表达式2> ] |
示例:
1 | # if 1<2 then |
4.6. while 表达式
纯函数式语言没有循环。惊不惊喜,意不意外。
需要循环完成的工作,可以通过递归函数来完成。
4.7. 模式匹配表达式
强大至极
格式:
1 | match <表达式> with |
基础使用示例:
1 | (* 取反 *) |
5. 函数
First of all, 函数是变量。
5.1. 简单函数
let <fun_name> <参数1> <参数2>...<参数n> = <表达式>
let <fun_name> (<参数1>, <参数2>, ...<参数n>) = <表达式>
: 将多个参数合并到一个元组中
示例:
1 | let add x y = |
5.2. function 和 fun
let <fun_name> = function <参数> -> <表达式>
: 只能有一个参数,可用于模式匹配let <fun_name> = fun <参数1> <参数2>...<参数n> -> <表达式>
: 多参数
5.3. 高阶函数
函数作参和作返回值
5.4. 递归函数
在函数名前添加rec
关键字,指定是递归函数。
1 | let rec <fun_name> <参数1> <参数2>...<参数n> = <表达式> |
示例:
1 | (* 普通递归 *) |
尾递归实现循环,在参数中加入辅助函数。
1 | (* 尾递归实现循环 *) |
6. 数据结构
6.1. 类型的显式定义(type)
格式:
1 | type [<类型参数>] <类型标识符> = <类型定义表达式> |
6.2. 元组类型(tuple)
格式:
<元素1>, <元素2>...<元素n>
每个元素的类型可以不相同。
元组的类型描述为:
<元素1类型> * <元素2类型> *...<元素n类型>
示例:
1 | # let a = "Number", 1;; |
只有二个元素的元组也称作对偶。
函数fst
和snd
分别取对偶的第一个和第二个分量。
6.3. 记录类型(结构体 record)
定义一个记录类型:
1 | type <类型标识符> = { <字段名1>:<类型1>; ... ; <字段名n>:<类型n> } |
创建一个记录类型变量:
1 | { <字段名1> = <表达式1>; ... ; <字段名n> = <表达式n> } |
访问记录类型中的字段:
1 | <记录类型变量>.<字段名> |
注意:上述记录类型不可修改!!!
6.4. 联合类型/变体(加强版 union)
格式:
1 | type [<多态类型变量>] <类型标识符> = |
注:构造子名首字母必须大写。
6.4.1. 无参构造子(枚举类型)
无参构造子。类似于枚举类型,构造子能和常数或者布尔值一样使用
示例:
1 | type seasons = Spring | Summer | Autumn | Winter ;; |
6.4.2. 带参构造子
带参构造子。构造子相当于类型,可以用于定义变量
示例:
1 | type num = Int of int | Float of float ;; |
6.4.3. 递归类型
示例,定义一个二叉树:
1 | type inttree = |
6.4.4. 带多态变量的联合类型
6.4.5. 多态变体
6.5. 表(list 不可以修改)
类型描述:
1 | <类型> list |
6.5.1. 创建
1 | [ e1; e2; ... en;] |
ei
可以是变量或者常量,但类型必须相同。
6.5.2. 添加
头插:
1 | # 1::[2;3];; |
追加:
1 | # List.append [1;2] [3;4] ;; |
6.5.3. 取表头表尾
表头是第一个元素,表尾是除第一个元素外的子表。
1 | # List.hd [1;2;3] ;; |
注意:表中元素也不可以修改!!!
6.6. 汇总
1 | OCaml 名字 类型定义的例子 用法 |
7. 模块
7.1. 文件即模块
如果有一个util.ml
文件:
1 | (* util.ml *) |
那么,可以在另一个main.ml
文件中:
1 | print_endline Util.msg ;; |
调用Util
模块(即util.ml
文件)中的变量或者函数。
如果还存在一个util.mli
文件,那它就是util.ml
模块的接口。未在util.mli
中出现的变量或函数,都不能被调用。同时,util.mli
文件需要在util.ml
文件编译前编译,如下:
1 | ocamlc -c util.mli -o util.cmi |
7.2. 接口和模块定义
接口定义
1 | module type <接口名> = |
<接口名>是一个大写字母开头的标识符,<接口定义体>中包括了 type 定义、函数的类型描述等内容。
模块定义:
1 | module <模块名> [:<模块接口>] = |
<模块名>是一个大写字母开头的标识符,<模块体>中包括了 type 定义和 let 定义。
8. 命令式程序设计
8.1. 可更改变量
定义:
1 | let <变量> = ref <表达式> ;; |
引用:
1 | !<变量> |
赋值:
1 | <变量> := <表达式> |
8.2. 可更改记录
定义:
1 | type <记录类型> = { |
赋值:
1 | <记录>.<分量> <- <表达式> |
8.3. 可修改的数组(Array)
定义:
1 | let arr = [|1;2;3|] ;; |
访问:
1 | <数组>.(<下标>) (* 如:mart.(1).(2) *) |
赋值:
1 | <数组>.(<下标>) <- <表达式> |
常用函数:
1 | Array.make 4 1; (* 创建长度为4的一维数组,都初始化为1 *) |
8.4. for循环
1 | for <变量>=<初始表达式> to <终止表达式> do |