Lập trình · Ngôn ngữ lập trình
Hướng dẫn học lập trình OCaml cơ bản cho người mới
Trả lời nhanh
OCaml là ngôn ngữ hàm kiểu tĩnh thuộc họ ML. Trình biên dịch của nó suy luận kiểu giúp bạn, bắt được nhiều lỗi trước khi chương trình chạy và sinh ra mã native nhanh. Chương trình được xây từ các giá trị bất biến, variant (union có thẻ), khớp mẫu và module. Cài bằng opam, chạy một tệp bằng ocaml hello.ml, hoặc biên dịch bằng ocamlopt.
OCaml là gì và ai dùng nó
OCaml được tạo ra tại Inria ở Pháp và nay do một cộng đồng mã nguồn mở phát triển, trong đó có các công ty như Jane Street và Tarides. Nó kết hợp lập trình hàm với các tính năng mệnh lệnh tùy chọn (tham chiếu khả biến, mảng, vòng lặp) và một hệ thống đối tượng mà phần lớn mã không cần đến. OCaml 5 bổ sung song song hóa bộ nhớ chia sẻ qua domain và effect handler.
OCaml được dùng cho trình biên dịch và công cụ phân tích chương trình, trợ lý chứng minh như Rocq (trước đây là Coq), hệ thống giao dịch (Jane Street là người dùng công nghiệp nổi tiếng nhất), và công cụ cho lập trình viên như Flow và Infer của Meta. Nó cũng là ngôn ngữ giảng dạy phổ biến cho lập trình hàm và hệ thống kiểu.
Cài OCaml và chạy Hello, world
Cách chuẩn để cài OCaml là opam, trình quản lý gói của OCaml. Sau khi cài opam, chạy opam init một lần; lệnh này thiết lập một trình biên dịch. Sau đó cài công cụ build dune và REPL thân thiện hơn là utop bằng opam install dune utop.
(* hello.ml *)
let () = print_endline "Hello, world!"ocaml hello.ml # run with the interpreter
ocamlopt -o hello hello.ml
./hello # native executable
# Hello, world!let () = ... là cách thông thường để viết điểm vào chương trình: nó đánh giá biểu thức và kiểm tra rằng kết quả là unit (không có giá trị). Trong REPL (utop hoặc ocaml), kết thúc mỗi cụm bằng ;;; trong tệp mã nguồn bạn không cần ;;. Với dự án thực tế, dune init project myapp tạo dự án và dune build biên dịch nó.
Giá trị và các kiểu dữ liệu cơ bản
| Kiểu | Ví dụ | Ghi chú |
|---|---|---|
| int | 42 | 63 bit trên hệ 64 bit; toán tử + - * / mod |
| float | 3.14 | Có toán tử riêng: +. -. *. /. |
| string | "hello" | Byte bất biến; nối bằng ^ |
| char | 'a' | Một byte đơn |
| bool | true, false | &&, ||, not |
| unit | () | Giá trị của các biểu thức không có kết quả |
let name = "Ana"
let age = 30
let height = 1.68 *. 100.0
let () = Printf.printf "%s is %d, %.0f cm\n" name age height
(* Ana is 30, 168 cm *)
let count = ref 0
let () = incr count; Printf.printf "count = %d\n" !countCác binding là bất biến. ref là một ô khả biến: !r đọc nó và r := v ghi vào nó. OCaml không bao giờ tự chuyển đổi giữa int và float; hãy dùng float_of_int và int_of_float.
Điều khiển luồng và hàm
if là biểu thức. Hàm được định nghĩa bằng let; hàm đệ quy cần let rec. Các đối số cách nhau bằng dấu cách, và mọi hàm đều được curry, nên áp dụng một phần là miễn phí.
let sign n =
if n > 0 then "positive"
else if n < 0 then "negative"
else "zero"
let rec factorial n = if n <= 1 then 1 else n * factorial (n - 1)
let add x y = x + y (* int -> int -> int *)
let add5 = add 5
let () =
for i = 1 to 3 do
Printf.printf "%d squared is %d\n" i (i * i)
done;
Printf.printf "%s %d %d\n" (sign (-2)) (factorial 5) (add5 10)
(* negative 120 15 *)Chú ý dấu ngoặc trong sign (-2): nếu thiếu, OCaml đọc sign - 2 là phép trừ. Dùng ; để nối tiếp các biểu thức trả về unit.
Danh sách, mảng, tuple và record
| Cấu trúc | Cú pháp literal | Ghi chú |
|---|---|---|
| List | [1; 2; 3] | Danh sách liên kết đơn bất biến; x :: xs thêm vào đầu |
| Array | [| 1; 2; 3 |] | Kích thước cố định, khả biến, truy cập chỉ số bằng a.(0) |
| Tuple | ("Ana", 30) | Nhóm cố định các giá trị khác kiểu |
| Record | { name = "Ana"; age = 30 } | Các trường có tên, kiểu phải khai báo trước |
| Hashtbl / Map | Hashtbl.create 16 | Bảng băm khả biến / map dạng cây cân bằng bất biến |
type person = { name : string; age : int }
let people = [ { name = "Ana"; age = 30 }; { name = "Ben"; age = 17 } ]
let () =
people
|> List.filter (fun p -> p.age >= 18)
|> List.map (fun p -> p.name)
|> List.iter print_endline
(* Ana *)
let older = { (List.hd people) with age = 31 }Toán tử pipeline x |> f nghĩa là f x. Module List cung cấp map, filter, fold_left, find_opt và nhiều hàm khác.
Tính năng đặc trưng: variant, khớp mẫu và module
Kiểu variant liệt kê mọi trường hợp có thể có của một giá trị. Khớp mẫu tách một giá trị theo từng trường hợp, và trình biên dịch cảnh báo khi thiếu một trường hợp, giúp tái cấu trúc an toàn.
type shape =
| Circle of float
| Rectangle of float * float
let area = function
| Circle r -> Float.pi *. r *. r
| Rectangle (w, h) -> w *. h
let () =
List.iter (fun s -> Printf.printf "%.2f\n" (area s))
[ Circle 1.0; Rectangle (2.0, 3.0) ]
(* 3.14 then 6.00 *)
module StringMap = Map.Make (String)
let ages = StringMap.(empty |> add "Ana" 30 |> add "Ben" 17)
let () = Printf.printf "%d\n" (StringMap.find "Ana" ages) (* 30 *)Module nhóm các kiểu và hàm, và mỗi tệp là một module (list.ml sẽ là List). Functor là hàm từ module sang module: Map.Make (String) tạo một module map có khóa là chuỗi. Signature (tệp .mli) ẩn các chi tiết triển khai.
Xử lý lỗi
OCaml có ba công cụ: option cho giá trị có thể thiếu, result cho thao tác có thể thất bại kèm thông tin, và ngoại lệ cho các tình huống ngoại lệ.
let parse_age s =
match int_of_string_opt s with
| Some n when n >= 0 -> Ok n
| Some _ -> Error "Age must not be negative"
| None -> Error "Not a number"
let () =
match parse_age "abc" with
| Ok n -> Printf.printf "Age %d\n" n
| Error msg -> print_endline ("Error: " ^ msg)
(* Error: Not a number *)
exception Empty_input
let first = function [] -> raise Empty_input | x :: _ -> x
let () =
try print_int (first []) with Empty_input -> print_endline "empty list"Một chương trình hoàn chỉnh nhỏ: tần suất từ
let () =
let text = "the cat and the hat and the bat" in
let counts = Hashtbl.create 16 in
String.split_on_char ' ' text
|> List.iter (fun w ->
let n = Option.value (Hashtbl.find_opt counts w) ~default:0 in
Hashtbl.replace counts w (n + 1));
Hashtbl.to_seq counts
|> List.of_seq
|> List.sort (fun (_, a) (_, b) -> compare b a)
|> List.filteri (fun i _ -> i < 2)
|> List.iter (fun (w, n) -> Printf.printf "%s %d\n" w n)
(* the 3
and 2 *)Chương trình đếm từ trong một bảng băm khả biến, chuyển bảng thành danh sách các cặp (word, count), sắp xếp theo số lần giảm dần và in hai mục đầu. ~default:0 là một đối số có nhãn.
Học tiếp ở đâu
- ocaml.org: hướng dẫn cài đặt, "A Tour of OCaml" và các hướng dẫn khác trong mục Docs.
- Sách hướng dẫn OCaml cho tài liệu tham chiếu đầy đủ về ngôn ngữ và thư viện chuẩn.
- Học dune để build dự án và opam để cài thư viện.
- Thử cuốn sách trực tuyến miễn phí "Real World OCaml" để có các ví dụ thực tế lớn hơn.
Câu hỏi thường gặp
OCaml có khó học không?
Cú pháp nhỏ gọn, nhưng tư duy bằng kiểu, đệ quy và khớp mẫu cần luyện tập nếu bạn đến từ các ngôn ngữ mệnh lệnh. Thông báo lỗi từ bộ kiểm tra kiểu sẽ nhanh chóng trở nên dễ đọc hơn.
Vì sao OCaml có toán tử riêng cho số thực?
OCaml không nạp chồng toán tử số học, nên + chỉ hoạt động với int và +. chỉ với float. Điều này giữ cho suy luận kiểu đơn giản và dễ đoán.
OCaml và F# khác nhau thế nào?
F# bắt nguồn từ OCaml và chia sẻ phần lớn cú pháp lõi, nhưng F# chạy trên .NET và dùng thụt lề có ý nghĩa, trong khi OCaml biên dịch ra mã native hoặc bytecode và có hệ thống module mạnh hơn với functor.
Khi nào cần ;; trong OCaml?
Chỉ trong toplevel tương tác (ocaml hoặc utop), nơi nó báo cho REPL đánh giá những gì bạn đã gõ. Trong tệp nguồn .ml bạn thường bỏ nó.