强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

OCaml 教程 / 基本类型与表达式

基本类型与表达式

概述

OCaml 拥有一套精心设计的基本类型系统。所有类型在编译期确定,编译器通过 Hindley-Milner 类型推导算法自动推导大部分类型,开发者无需处处标注。

基本类型一览

类型关键字示例字节大小说明
整数int42, -7, 0xFF63 位(64 位平台)1 位用于 GC 标记
浮点数float3.14, 1e-364 位(IEEE 754)双精度
布尔booltrue, false-用 0/1 表示
字符char'a', '\n'8 位仅支持 Latin-1
字符串string"hello"可变长度不可变(默认)
单元unit()0 位类似 void,表示副作用

整数类型

OCaml 的 int 类型在 64 位平台上是 63 位有符号整数(最低位被 GC 使用)。

(* 基本整数运算 *)
let a = 10 + 20        (* => 30 *)
let b = 100 - 42       (* => 58 *)
let c = 6 * 7          (* => 42 *)
let d = 100 / 3        (* => 33, 整数除法截断 *)
let e = 100 mod 3      (* => 1, 取模运算 *)

(* 十六进制、八进制、二进制字面量 *)
let hex = 0xFF         (* => 255 *)
let oct = 0o77         (* => 63 *)
let bin = 0b1010       (* => 10 *)

(* 边界值 *)
let max_int = max_int  (* => 4611686018427387903 *)
let min_int = min_int  (* => -4611686018427387904 *)

⚠️ 注意:OCaml 的整数溢出是环绕行为(wrap around),不会抛出异常。这是与 Java、Python 等语言的重要区别。

(* 溢出示例 *)
let overflow = max_int + 1  (* 结果为 min_int,不会报错! *)

32 位整数与其他整数类型

(* 32 位整数(用于 FFI 和特定场景) *)
let x = Int32.of_int 42       (* int -> int32 *)
let y = Int32.add x 1l        (* 注意 1l 后缀表示 int32 字面量 *)

(* 64 位整数 *)
let z = Int64.of_int 42       (* int -> int64 *)
let w = Int64.add z 1L        (* 1L 后缀表示 int64 字面量 *)

(* 原生整数(与平台字长一致) *)
let n = Nativeint.of_int 42   (* int -> nativeint *)
类型字面量后缀模块
int32lInt32
int64LInt64
nativeintnNativeint

浮点数类型

OCaml 的 float 类型是 64 位 IEEE 754 双精度浮点数。

(* 浮点运算 — 注意使用专门的运算符 *)
let pi = 3.141592653589793
let radius = 5.0
let area = pi *. radius *. radius   (* 用 *. 而不是 * *)
let circumference = 2.0 *. pi *. radius

(* 浮点除法 *)
let half = 1.0 /. 2.0               (* 用 /. 而不是 / *)

(* 浮点数函数 *)
let sqrt_2 = sqrt 2.0                (* 平方根 *)
let log_e = log 2.718281828          (* 自然对数 *)
let power = 2.0 ** 10.0              (* 幂运算,=> 1024.0 *)

(* 特殊浮点值 *)
let nan_value = nan                   (* NaN *)
let inf_value = infinity              (* 正无穷 *)
let neg_inf = neg_infinity            (* 负无穷 *)

⚠️ 注意:OCaml 中整数和浮点数使用不同的运算符,这是初学者最容易犯的错误:

操作整数浮点数
加法++.
减法--.
乘法**.
除法//.
取模modmod_float
取负~-~-.

浮点运算陷阱

(* 经典的浮点精度问题 *)
let result = 0.1 +. 0.2
(* => 0.30000000000000004,不是 0.3 *)

(* 浮点比较要使用 epsilon *)
let epsilon = 1e-10
let float_equal a b =
  abs_float (a -. b) < epsilon

let _ = float_equal (0.1 +. 0.2) 0.3
(* => true *)

(* 不要用 = 直接比较浮点数 *)
let _ = (0.1 +. 0.2 = 0.3)
(* => false ⚠️ *)

💡 提示:金融计算中应使用定点整数或专用的十进制库,避免浮点精度问题。

布尔类型

(* 布尔字面量 *)
let is_valid = true
let has_error = false

(* 逻辑运算 *)
let a = true && false        (* 逻辑与,短路求值 *)
let b = true || false        (* 逻辑或,短路求值 *)
let c = not true             (* 逻辑非,=> false *)

(* 比较运算(对所有类型有效) *)
let _ = 1 < 2                (* => true *)
let _ = "abc" > "abd"        (* => true,字典序 *)
let _ = 1 = 1                (* 结构相等性 *)
let _ = 1 == 1               (* 物理相等性(引用相等) *)

(* 注意:结构相等 vs 物理相等 *)
let a = [1; 2; 3]
let b = [1; 2; 3]
let _ = (a = b)              (* => true,值相同 *)
let _ = (a == b)             (* => false,不是同一个对象 *)

⚠️ 注意:OCaml 中 = 是结构相等比较(比较值),== 是物理相等比较(比较内存地址)。通常应该使用 =

字符类型

(* 字符字面量 *)
let c = 'A'
let newline = '\n'
let escaped = '\\'           (* 反斜杠 *)
let quote = '\''             (* 单引号 *)

(* 字符操作 *)
let code = Char.code 'A'     (* => 65, ASCII 码 *)
let char_of = Char.chr 97    (* => 'a' *)
let upper = Char.uppercase_ascii 'a'  (* => 'A' *)
let lower = Char.lowercase_ascii 'B'  (* => 'b' *)
let is_digit = Char.code '0' <= Char.code '5' && Char.code '5' <= Char.code '9'

(* 字符转字符串 *)
let s = String.make 1 'x'   (* => "x" *)

⚠️ 注意:OCaml 的 char 类型仅支持 Latin-1 编码(0-255),不直接支持 Unicode。处理中文等多字节字符需要使用 string 或第三方库(如 Uutf)。

字符串类型

(* 字符串字面量 *)
let greeting = "你好,OCaml!"
let empty = ""
let multiline = "第一行\n第二行\n第三行"

(* 字符串拼接 *)
let full = greeting ^ " " ^ "欢迎!"

(* 字符串长度 *)
let len = String.length greeting   (* 返回字节数,不是字符数 *)

(* 字符串索引 *)
let first = String.get greeting 0  (* 返回 char,不是子串 *)

(* 子串提取 *)
let sub = String.sub greeting 0 2 (* 从索引 0 取 2 个字节 *)

(* 字符串搜索 *)
let contains = String.contains greeting 'O'  (* => true *)

(* 遍历字符串 *)
let () =
  String.iter (fun c -> print_char c; print_char ' ') "hello"
(* => h e l l o *)

(* 格式化(使用 Printf 模块) *)
let msg = Printf.sprintf "Name: %s, Age: %d" "Alice" 30

💡 提示:OCaml 的字符串默认是不可变的(从 4.06 开始)。如需可变字符串,使用 Bytes 模块(详见第 10 篇)。

unit 类型

unit 类型只有一个值 (),通常用于表示只有副作用的函数。

(* unit 类型函数 — 纯粹为了副作用 *)
let greet name =
  Printf.printf "Hello, %s!\n" name

(* unit 作为参数 *)
let read_line () =
  input_line stdin

(* unit 作为返回值 — 表示函数有副作用 *)
let process_data data =
  (* ... 处理数据 ... *)
  ()   (* 显式返回 unit *)

(* 在 if 表达式中使用 unit *)
let check_age age =
  if age >= 18 then
    print_endline "成年人"
  else
    print_endline "未成年"
(* else 分支自动返回 unit *)

⚠️ 注意unit 不等同于 voidunit 是一个真正的类型,有唯一的值 (),可以作为参数和返回值使用。

类型推导

OCaml 编译器使用 Hindley-Milner 类型推导算法,大多数情况下无需标注类型:

(* 编译器自动推导类型 *)
let x = 42                    (* int *)
let y = 3.14                  (* float *)
let b = true                  (* bool *)
let s = "hello"               (* string *)

(* 函数类型推导 *)
let add a b = a + b           (* int -> int -> int *)
let f_to_c f = (f -. 32.0) *. 5.0 /. 9.0  (* float -> float *)

(* 多态函数 *)
let identity x = x            (* 'a -> 'a,任意类型到自身 *)
let compose f g x = f (g x)   (* ('b -> 'c) -> ('a -> 'b) -> 'a -> 'c *)

💡 提示:在 utop 中输入表达式后,- : type = value 中的 type 就是编译器推导出的类型。

let 绑定

let 是 OCaml 中最核心的绑定机制:

(* 基本 let 绑定 *)
let x = 10

(* let ... in 表达式:局部绑定 *)
let result =
  let x = 5 in
  let y = 3 in
  x + y
(* result = 8,x 在此作用域外仍为 10 *)

(* 嵌套 let 绑定 *)
let computation =
  let a = 10 in
  let b = a * 2 in
  let c = b + 1 in
  a + b + c  (* => 10 + 20 + 21 = 51 *)

(* let 绑定是表达式,不是语句 *)
let x = (let y = 5 in y * y)  (* x = 25 *)

⚠️ 注意let ... in 中的绑定是不可变的。一旦绑定,不能修改。这是函数式编程的核心理念。

类型注解

虽然 OCaml 能自动推导类型,但在某些场景下显式类型注解是必要的:

(* 函数签名注解 *)
let add (a : int) (b : int) : int = a + b

(* 变量类型注解 *)
let x : int = 42
let s : string = "hello"

(* 让类型更精确 *)
let read_input () : string =
  input_line stdin

(* 歧义消解 *)
let float_of_int_and_back (x : int) : float =
  float_of_int x +. 0.5

(* 模块签名中的类型注解 *)
let max (a : int) (b : int) : int =
  if a > b then a else b

💡 提示:建议在函数的参数和返回值上添加类型注解,这既是一种文档,也能帮助编译器给出更精确的错误信息。

if-then-else 表达式

在 OCaml 中,if-then-else表达式,不是语句,它会返回一个值:

(* 基本用法 *)
let abs_value x =
  if x >= 0 then x
  else -x

(* if 表达式的结果可以被绑定 *)
let grade score =
  if score >= 90 then "A"
  else if score >= 80 then "B"
  else if score >= 70 then "C"
  else if score >= 60 then "D"
  else "F"

(* if 作为表达式使用 *)
let max a b = if a > b then a else b

(* 省略 else 分支时,隐含 else () *)
let print_if_positive x =
  if x > 0 then print_int x
(* 隐含 else () *)

⚠️ 注意:当 if 没有 else 分支时,then 分支的类型必须是 unit。否则编译器会报错,因为缺少 else 分支无法产生一致的类型。

begin-end 块

begin ... end 用于在需要单个表达式的地方执行多个表达式:

(* if 的 then 分支需要执行多条语句 *)
let check_and_print x =
  if x > 0 then begin
    print_string "正数: ";
    print_int x;
    print_newline ()
  end else begin
    print_string "非正数: ";
    print_int x;
    print_newline ()
  end

(* 等价于使用括号 *)
let check_and_print' x =
  if x > 0 then (
    print_string "正数: ";
    print_int x;
    print_newline ()
  ) else (
    print_string "非正数: ";
    print_int x;
    print_newline ()
  )

💡 提示begin ... end(...) 完全等价。社区风格更推荐使用括号,但 begin-end 在复杂的嵌套表达式中可读性更好。

类型转换

OCaml 是强类型语言,不同类型之间不会隐式转换,必须显式转换:

(* int -> float *)
let x = float_of_int 42         (* => 42.0 *)
let y = Int.to_float 42         (* 同上,模块风格 *)

(* float -> int(截断,不是四舍五入) *)
let a = int_of_float 3.9        (* => 3,不是 4! *)
let b = Int.of_float 3.9        (* 同上 *)

(* 字符串转换 *)
let s1 = string_of_int 42       (* => "42" *)
let s2 = string_of_float 3.14   (* => "3.14" *)
let s3 = string_of_bool true    (* => "true" *)

let n1 = int_of_string "42"     (* => 42 *)
let f1 = float_of_string "3.14" (* => 3.14 *)

(* char <-> int *)
let c = Char.chr 65             (* => 'A' *)
let n = Char.code 'A'           (* => 65 *)

(* bool <-> string *)
let b1 = bool_of_string "true"  (* => true *)

⚠️ 注意int_of_float截断(truncate),不是四舍五入。如需四舍五入,使用 Float.roundFloat.iround

实用示例:温度转换器

(* temperature.ml — 温度转换器 *)

let celsius_to_fahrenheit (c : float) : float =
  c *. 9.0 /. 5.0 +. 32.0

let fahrenheit_to_celsius (f : float) : float =
  (f -. 32.0) *. 5.0 /. 9.0

let format_temperature (value : float) (unit : string) : string =
  Printf.sprintf "%.2f°%s" value unit

let () =
  let temp_c = 37.0 in
  let temp_f = celsius_to_fahrenheit temp_c in
  Printf.printf "体温: %s = %s\n"
    (format_temperature temp_c "C")
    (format_temperature temp_f "F");

  let boiling_f = 212.0 in
  let boiling_c = fahrenheit_to_celsius boiling_f in
  Printf.printf "沸点: %s = %s\n"
    (format_temperature boiling_f "F")
    (format_temperature boiling_c "C")

(* 输出:
   体温: 37.00°C = 98.60°F
   沸点: 212.00°F = 100.00°C
*)

业务场景

  • 数据验证:利用类型系统确保输入合法性
  • 配置解析:使用类型转换函数将字符串配置转为具体类型
  • 数值计算:整数用于精确计数,浮点数用于科学计算
  • 日志格式化:使用 Printf.sprintf 构造结构化日志

扩展阅读