
OCaml 5 คือการปฏิวัติครั้งใหญ่ของภาษา OCaml ที่เปิดประตูสู่การเขียนโปรแกรมแบบ multicore parallelism อย่างแท้จริง พร้อมด้วย Effect Handlers ที่เป็น algebraic effects ระดับภาษา ทำให้ OCaml กลายเป็นหนึ่งในภาษาที่เหมาะสมที่สุดสำหรับงาน systems programming ที่ต้องการทั้ง type safety, performance และ concurrency
ส่วนนี้อธิบายเหตุผลที่นักพัฒนาระบบควรพิจารณา OCaml 5 เป็นเครื่องมือหลัก พร้อมเปรียบเทียบกับภาษายอดนิยมอื่น ๆ และวิธีการตั้งค่าสภาพแวดล้อมการพัฒนาให้พร้อมใช้งานในระดับ production
OCaml (Objective Categorical Abstract Machine Language) เป็นภาษาตระกูล ML (Meta Language) ที่พัฒนาโดย INRIA ประเทศฝรั่งเศสตั้งแต่ปี 1996 ผสมผสานแนวคิด functional programming, object-oriented programming และ imperative programming เข้าด้วยกัน การเปิดตัว OCaml 5.0 ในเดือนธันวาคม 2022 ถือเป็นจุดเปลี่ยนสำคัญที่นำ Multicore Parallelism และ Effect Handlers เข้ามาในภาษา ซึ่งทำให้ OCaml ก้าวเข้าสู่ยุคใหม่ของ systems programming อย่างเต็มตัว
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#3c3836',
'primaryTextColor':'#ebdbb2',
'primaryBorderColor':'#928374',
'lineColor':'#a89984',
'secondaryColor':'#504945',
'tertiaryColor':'#282828',
'background':'#282828',
'mainBkg':'#3c3836',
'secondBkg':'#504945',
'clusterBkg':'#32302f',
'clusterBorder':'#d79921'
}}}%%
flowchart TD
subgraph Era1["ยุคบุกเบิก (Pioneer Era) 1996-2005"]
A1["1996: OCaml 1.00
เปิดตัวครั้งแรก"] --> A2["2003: OCaml 3.07
Polymorphic Variants"]
A2 --> A3["2005: Industrial Adoption
Jane Street, Citrix"]
end
subgraph Era2["ยุคพัฒนา (Growth Era) 2006-2015"]
B1["2012: OCaml 4.00
GADTs, First-class modules"] --> B2["2013: opam
Package manager"]
B2 --> B3["2014: Dune/Jbuilder
Build system"]
B3 --> B4["2015: MirageOS
Unikernel OS"]
end
subgraph Era3["ยุค Multicore (Multicore Era) 2016-2022"]
C1["2016: Multicore OCaml
เริ่มวิจัยที่ OCaml Labs"] --> C2["2019: Effect Handlers
Algebraic Effects prototype"]
C2 --> C3["2022: OCaml 5.0
Multicore + Effects!"]
end
subgraph Era4["ยุคปัจจุบัน (Modern Era) 2023-ปัจจุบัน"]
D1["2023: OCaml 5.1
ปรับปรุง GC"] --> D2["2024: OCaml 5.2
Eio maturation"]
D2 --> D3["2025: OCaml 5.3+
Production-ready"]
end
Era1 --> Era2
Era2 --> Era3
Era3 --> Era4
style Era1 fill:#32302f,stroke:#458588,color:#83a598
style Era2 fill:#32302f,stroke:#98971a,color:#b8bb26
style Era3 fill:#32302f,stroke:#d79921,color:#fabd2f
style Era4 fill:#32302f,stroke:#cc241d,color:#fb4934
การเลือกภาษาสำหรับ systems programming เป็นการตัดสินใจที่ส่งผลต่อ architecture, performance และ maintainability ในระยะยาว ตารางต่อไปนี้เปรียบเทียบคุณสมบัติหลักของภาษาสี่ภาษา
| คุณสมบัติ (Feature) | OCaml 5 | Rust | Go | C++ |
|---|---|---|---|---|
| Memory Management | Garbage Collector (GC) | Ownership + Borrowing | Garbage Collector (GC) | Manual + RAII |
| Memory Safety | ปลอดภัย (ยกเว้น FFI) | ปลอดภัยตาม type system | ปลอดภัย (ยกเว้น unsafe) | ไม่ปลอดภัยโดย default |
| Type System | Hindley-Milner + Effects | Affine types + Lifetimes | Structural (limited generics) | Template metaprogramming |
| Type Inference | ครอบคลุมเกือบทั้งหมด (global) | Local inference | Local inference | Limited (auto) |
| Concurrency Model | Domains + Effects + Eio | async/await + Send/Sync | Goroutines + channels | std::thread + coroutines |
| True Parallelism | ได้ (OCaml 5) | ได้ | ได้ | ได้ |
| Compile Speed | เร็วมาก | ช้ามาก | เร็วมาก | ช้า |
| Runtime Size | เล็ก (~500 KB) | ไม่มี runtime | กลาง (~2 MB) | ไม่มี runtime |
| FFI Overhead | ต่ำ (คล้าย C) | ต่ำมาก (zero-cost) | สูง (cgo) | ไม่มี |
| GC Pause | มี (generational, ~1-10ms) | ไม่มี | มี (sub-ms) | ไม่มี |
| Pattern Matching | รองรับเต็มรูปแบบ | รองรับเต็มรูปแบบ | ไม่รองรับ (มี switch) | ไม่รองรับ (มี if constexpr) |
| Algebraic Data Types | รองรับ (variants + records) | รองรับ (enum + struct) | ไม่รองรับ (interface{}) | ไม่รองรับ (variant C++17) |
| Null Safety | ไม่มี null (option type) | ไม่มี null (Option |
มี nil | มี nullptr |
| Learning Curve | ชันปานกลาง | ชันมาก | ต่ำ | ชันสุด |
| Ecosystem Size | กลาง (~4,500 packages) | ใหญ่ (~150,000 crates) | ใหญ่มาก | ใหญ่มาก |
OCaml มี type system ที่ทรงพลังระดับเดียวกับ Haskell โดยสามารถอนุมาน (infer) ชนิดข้อมูลของตัวแปรและฟังก์ชันได้โดยไม่ต้องเขียน type annotation ให้ครบ ตัวอย่าง
(* Compiler อนุมานว่า f : int -> int -> int โดยอัตโนมัติ *)
let add x y = x + y
(* Polymorphic function: 'a -> 'a list -> 'a list *)
let prepend x xs = x :: xs
(* ใช้งานได้กับ type ใดก็ได้ *)
let () =
let result1 = add 3 5 in (* int: 8 *)
let result2 = prepend 1 [2; 3; 4] in (* int list: [1;2;3;4] *)
let result3 = prepend "hi" ["there"] in (* string list *)
Printf.printf "%d\n" result1;
List.iter (Printf.printf "%d ") result2;
print_newline ();
List.iter print_endline result3
OCaml ใช้ generational GC ที่ออกแบบมาให้ pause time สั้นและสม่ำเสมอ ต่างจาก JVM GC ที่อาจมี stop-the-world pause ยาวหลายร้อยมิลลิวินาที
ความสัมพันธ์ระหว่าง pause time และขนาด heap แสดงได้ด้วยสมการ
โดยที่
เนื่องจาก minor heap ของ OCaml มีขนาดเล็ก (default 262,144 words หรือประมาณ 2 MB บน 64-bit) การเก็บขยะในรุ่นนี้จึงเร็วมาก (ต่ำกว่า 1 ms ในสถานการณ์ทั่วไป)
OCaml compiler มีความสามารถในการ inline และ specialize ฟังก์ชัน polymorphic ให้เป็น monomorphic code ที่ไม่มี overhead เช่น
(* Functor (module-level generic) ไม่มี runtime cost หลังจาก compile *)
module IntSet = Set.Make(Int)
module StringSet = Set.Make(String)
(* ทั้งสอง module ใช้ code เดียวกันหลัง specialization *)
let _ = IntSet.(add 1 (add 2 (add 3 empty)))
let _ = StringSet.(add "a" (add "b" empty))
OCaml 5 เป็น mainstream language แรก ที่รองรับ Algebraic Effects and Handlers ระดับภาษา ทำให้สามารถสร้าง abstraction ของ control flow ได้โดยไม่ต้องใช้ monad transformer stack ที่ซับซ้อน
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#3c3836',
'primaryTextColor':'#ebdbb2',
'primaryBorderColor':'#928374',
'lineColor':'#a89984',
'clusterBkg':'#32302f',
'clusterBorder':'#d79921'
}}}%%
flowchart LR
subgraph Finance["การเงิน (Finance)"]
F1["Jane Street
Trading Systems
$1T+/day"]
F2["LexiFi
Derivatives DSL"]
F3["Bloomberg
Risk modeling"]
end
subgraph Blockchain["บล็อกเชน (Blockchain)"]
B1["Tezos
Smart contracts"]
B2["Marigold
Layer 2"]
B3["Nomadic Labs
Protocol dev"]
end
subgraph OS["ระบบปฏิบัติการ (OS & Infra)"]
O1["MirageOS
Unikernel"]
O2["Docker
Early versions"]
O3["Unison
File sync"]
end
subgraph Tools["เครื่องมือ (Developer Tools)"]
T1["Semgrep
Static analysis"]
T2["Coq/Rocq
Proof assistant"]
T3["Flow
JS type checker"]
T4["Hack
Facebook PHP"]
end
OCaml[OCaml 5]
OCaml --> Finance
OCaml --> Blockchain
OCaml --> OS
OCaml --> Tools
style OCaml fill:#d79921,stroke:#fabd2f,color:#282828
style Finance fill:#32302f,stroke:#98971a,color:#b8bb26
style Blockchain fill:#32302f,stroke:#458588,color:#83a598
style OS fill:#32302f,stroke:#cc241d,color:#fb4934
style Tools fill:#32302f,stroke:#b16286,color:#d3869b
MirageOS เป็น library operating system ที่เขียนด้วย OCaml ล้วน สร้าง unikernel ที่รันโดยตรงบน hypervisor (Xen, KVM, Firecracker) โดยไม่ต้องมี general-purpose OS คุณสมบัติเด่น
Tezos เป็น blockchain ที่เขียนด้วย OCaml ทั้งหมด (core protocol, node, client) เหตุผลที่เลือก OCaml
Jane Street เป็นบริษัทการเงินที่มีนักพัฒนา OCaml มากที่สุดในโลก (หลายร้อยคน) ใช้ OCaml สำหรับ
Jane Street พัฒนา library ที่เปิดเป็น open-source เช่น Core (standard library ทดแทน), Async (concurrent programming), Base (minimal stdlib), และ Bonsai (web UI framework)
Semgrep เป็นเครื่องมือ static analysis ที่สแกน code ได้หลายสิบภาษา engine เขียนด้วย OCaml เนื่องจาก
แม้ OCaml GC จะ predictable แต่ก็ยังมี pause time อยู่ สำหรับระบบที่ต้องการ hard real-time (เช่น medical devices, avionics) หรือ ultra-low latency (เช่น HFT sub-microsecond) อาจต้องใช้เทคนิคเพิ่มเติม
Gc.setแม้ FFI (Foreign Function Interface) ของ OCaml จะ overhead ต่ำ แต่การเรียก C function ยังมี cost จาก
เมื่อเทียบกับ Rust (crates.io มี 150,000+ packages) หรือ Go ecosystem ของ OCaml (opam มี ~4,500 packages) ยังเล็กกว่ามาก ส่งผลให้
ข้อควรระวังสำคัญ: OCaml 5 ไม่ป้องกัน data race ด้วย type system (ต่างจาก Rust ที่ใช้ Send/Sync traits) นักพัฒนาต้องออกแบบโค้ดให้
ตัวอย่างต่อไปนี้แสดงการเขียนโปรแกรมคำนวณผลรวมของเลข Fibonacci แบบขนาน
OCaml 5 (ใช้ Domainslib)
(* file: fib_parallel.ml *)
(* ต้องเพิ่ม domainslib ใน dune file: libraries domainslib *)
(* Sequential fibonacci - baseline *)
let rec fib n =
if n < 2 then n
else fib (n - 1) + fib (n - 2)
(* Parallel fibonacci using Task pool *)
let rec fib_parallel pool n =
if n < 35 then fib n (* threshold: สำหรับ n เล็กใช้ sequential ดีกว่า *)
else
let open Domainslib.Task in
(* async spawn งานที่ 1 *)
let a = async pool (fun () -> fib_parallel pool (n - 1)) in
(* async spawn งานที่ 2 *)
let b = async pool (fun () -> fib_parallel pool (n - 2)) in
(* await ผลลัพธ์ทั้งสอง *)
await pool a + await pool b
(* Example usage *)
let () =
let num_domains = 4 in
let pool = Domainslib.Task.setup_pool ~num_domains () in
let n = 40 in
let result =
Domainslib.Task.run pool (fun () -> fib_parallel pool n)
in
Domainslib.Task.teardown_pool pool;
Printf.printf "fib(%d) = %d\n" n result
(* รันด้วย: dune exec ./fib_parallel.exe *)
(* ผลลัพธ์: fib(40) = 102334155 *)
Rust (ใช้ Rayon)
// file: fib_parallel.rs
use rayon::join;
fn fib(n: u64) -> u64 {
if n < 2 { n } else { fib(n - 1) + fib(n - 2) }
}
fn fib_parallel(n: u64) -> u64 {
if n < 35 {
fib(n)
} else {
// rayon::join รันสองงานแบบขนานด้วย work-stealing
let (a, b) = join(
|| fib_parallel(n - 1),
|| fib_parallel(n - 2)
);
a + b
}
}
fn main() {
let n = 40;
let result = fib_parallel(n);
println!("fib({}) = {}", n, result);
}
จะเห็นว่าทั้งสองภาษามีรูปแบบคล้ายกันมาก แต่ OCaml 5 ต้องจัดการ pool เอง ขณะที่ Rust/Rayon มี global thread pool ให้ใช้โดยตรง
การเตรียมสภาพแวดล้อมที่ดีคือรากฐานของการทำงานกับ OCaml อย่างมีประสิทธิภาพ ส่วนนี้จะพาทำทั้งหมดตั้งแต่ 0 จนพร้อมเขียน systems code ได้จริง
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#3c3836',
'primaryTextColor':'#ebdbb2',
'primaryBorderColor':'#928374',
'lineColor':'#a89984',
'clusterBkg':'#32302f',
'clusterBorder':'#d79921'
}}}%%
flowchart TB
subgraph Layer1["ชั้นที่ 1: Package Manager"]
opam["opam
(OCaml Package Manager)"]
end
subgraph Layer2["ชั้นที่ 2: Compiler Switch"]
switch["ocaml 5.2.x switch
(compiler version)"]
end
subgraph Layer3["ชั้นที่ 3: Build & Dev Tools"]
dune["dune
(build system)"]
lsp["ocaml-lsp-server
(IDE support)"]
fmt["ocamlformat
(formatter)"]
merlin["merlin
(editor tooling)"]
end
subgraph Layer4["ชั้นที่ 4: Profiling & Debug"]
memtrace["memtrace
(heap profiler)"]
landmarks["landmarks
(CPU profiler)"]
perf["perf
(Linux profiler)"]
debug["ocamldebug
(debugger)"]
end
subgraph Layer5["ชั้นที่ 5: Editor"]
vscode["VS Code
+ OCaml Platform"]
zed["Zed Editor
+ OCaml extension"]
emacs["Emacs / Neovim
+ tuareg / nvim-ocaml"]
end
opam --> switch
switch --> dune
switch --> lsp
switch --> fmt
switch --> merlin
dune --> memtrace
dune --> landmarks
dune --> debug
lsp --> vscode
lsp --> zed
lsp --> emacs
style opam fill:#d79921,stroke:#fabd2f,color:#282828
style switch fill:#cc241d,stroke:#fb4934,color:#ebdbb2
style Layer1 fill:#32302f,stroke:#d79921
style Layer2 fill:#32302f,stroke:#cc241d
style Layer3 fill:#32302f,stroke:#98971a
style Layer4 fill:#32302f,stroke:#458588
style Layer5 fill:#32302f,stroke:#b16286
opam (OCaml Package Manager) เป็น package manager หลักของ OCaml ecosystem สิ่งที่ต้องเข้าใจคือ opam ใช้แนวคิด switch ซึ่งคล้ายกับ nvm ของ Node.js หรือ pyenv ของ Python โดยแต่ละ switch เป็น sandbox ที่มี compiler version และ packages ของตัวเอง
บน Linux (Arch / CachyOS / EndeavourOS)
# ติดตั้ง opam จาก official repo
sudo pacman -S opam
# หรือจาก AUR ถ้าต้องการเวอร์ชันล่าสุด
yay -S opam
บน Linux (Ubuntu / Debian)
# ติดตั้งจาก apt (เวอร์ชันอาจเก่า)
sudo apt update
sudo apt install opam
# หรือใช้ bubblewrap installer สำหรับเวอร์ชันล่าสุด
bash -c "sh <(curl -fsSL https://opam.ocaml.org/install.sh)"
บน macOS
# ผ่าน Homebrew
brew install opam
บน Windows (ผ่าน WSL2 แนะนำ) หรือ native
# Native Windows (OCaml 5.2+)
winget install Git.Git OCaml.opam
# Initialize opam (ครั้งแรกเท่านั้น)
# --bare = ไม่สร้าง default switch
opam init --bare --disable-sandboxing
# เพิ่ม opam environment เข้า shell
# สำหรับ bash/zsh
eval $(opam env --switch=default)
# เพิ่มใน ~/.bashrc หรือ ~/.zshrc ให้โหลดอัตโนมัติ
echo 'eval $(opam env)' >> ~/.bashrc
# สำหรับ fish shell
echo 'opam env | source' >> ~/.config/fish/config.fish
หมายเหตุ:
--disable-sandboxingจะปิด bubblewrap sandbox ซึ่งจำเป็นถ้าใช้งานใน container หรือ WSL2 ถ้าระบบปกติควรเปิดไว้เพื่อความปลอดภัย
# ดูรายการ compiler ที่มีให้เลือก
opam switch list-available | grep -E "^ocaml-base-compiler.5"
# สร้าง switch ใหม่สำหรับ OCaml 5.2.0 (เวอร์ชันแนะนำ ณ ตอนนี้)
opam switch create systems-5.2 ocaml-base-compiler.5.2.0
# ใช้งาน switch
eval $(opam env --switch=systems-5.2)
# ตรวจสอบเวอร์ชัน
ocaml --version
# ผลลัพธ์: The OCaml toplevel, version 5.2.0
# ดู switch ทั้งหมดที่มี
opam switch list
# ติดตั้ง core development tools
opam install -y \
dune \
ocaml-lsp-server \
ocamlformat \
utop \
odoc \
merlin
# สำหรับ systems work โดยเฉพาะ
opam install -y \
domainslib \
eio \
eio_main \
memtrace \
memtrace_viewer \
landmarks \
landmarks-ppx \
bechamel \
core_bench \
cmdliner \
logs \
fmt \
alcotest \
qcheck
settings.json (Ctrl+Shift+P → "Preferences: Open User Settings (JSON)"){
"ocaml.sandbox": {
"kind": "opam",
"switch": "systems-5.2"
},
"[ocaml]": {
"editor.defaultFormatter": "ocamllabs.ocaml-platform",
"editor.formatOnSave": true,
"editor.tabSize": 2
},
"editor.rulers": [90],
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true
}
Zed รองรับ OCaml ผ่าน extension ocaml เปิดไฟล์ ~/.config/zed/settings.json
{
"theme": "Gruvbox Dark Hard",
"languages": {
"OCaml": {
"tab_size": 2,
"formatter": {
"external": {
"command": "ocamlformat",
"arguments": ["--name", "{buffer_path}", "-"]
}
},
"format_on_save": "on",
"language_servers": ["ocaml-lsp-server"]
}
},
"lsp": {
"ocaml-lsp-server": {
"binary": {
"path_lookup": true
}
}
}
}
-- ~/.config/nvim/lua/plugins/ocaml.lua
return {
{
"neovim/nvim-lspconfig",
opts = {
servers = {
ocamllsp = {
cmd = { "ocamllsp" },
filetypes = { "ocaml", "ocaml.menhir", "ocaml.interface", "ocaml.ocamllex", "reason", "dune" },
root_dir = function(fname)
return require("lspconfig.util").root_pattern(
"*.opam", "esy.json", "package.json", ".merlin", "dune-project", "dune-workspace"
)(fname)
end,
},
},
},
},
}
ไฟล์ .ocamlformat วางที่ root ของโปรเจคเพื่อตั้งค่า formatter
# .ocamlformat
version = 0.26.2
profile = default
margin = 90
parse-docstrings = true
break-infix = fit-or-vertical
wrap-comments = true
# สร้างโฟลเดอร์และ initialize project
cd ~/projects
dune init proj moo_systems --kind=executable
# เข้าไปในโปรเจค
cd moo_systems
# ดูโครงสร้าง
tree .
ผลลัพธ์จะได้โครงสร้างประมาณนี้
moo_systems/
├── bin/
│ ├── dune # Build config สำหรับ executable
│ └── main.ml # Entry point
├── lib/
│ └── dune # Build config สำหรับ library
├── test/
│ ├── dune # Build config สำหรับ tests
│ └── test_moo_systems.ml
├── dune-project # Project-level config
└── moo_systems.opam # opam package metadata (generated)
(lang dune 3.15)
(name moo_systems)
(generate_opam_files true)
(source (github moo/moo_systems))
(authors "Moo <moo@rmutsv.ac.th>")
(maintainers "Moo <moo@rmutsv.ac.th>")
(license MIT)
(package
(name moo_systems)
(synopsis "OCaml 5 systems programming playground")
(description "Learning OCaml 5 for systems development")
(depends
(ocaml (>= 5.2.0))
dune
(domainslib (>= 0.5.1))
(eio (>= 1.0))
(eio_main (>= 1.0))
(cmdliner (>= 1.2.0))
logs
fmt
(alcotest :with-test)))
(executable
(name main)
(public_name moo_systems)
(libraries
moo_systems
domainslib
eio
eio_main
cmdliner
logs
fmt))
(* file: bin/main.ml *)
(* โปรแกรมตัวอย่างแสดง:
1. Basic OCaml 5 features
2. Domains parallelism
3. Effect handlers
*)
(* ---------- Part A: Basic features ---------- *)
(* Algebraic data type สำหรับเก็บข้อมูล system info *)
type os_kind =
| Linux of string (* distro name *)
| Macos
| Windows
| Unknown
type system_info = {
os: os_kind;
cpu_count: int;
hostname: string;
}
(* Pattern matching แบบ exhaustive *)
let os_to_string = function
| Linux distro -> Printf.sprintf "Linux (%s)" distro
| Macos -> "macOS"
| Windows -> "Windows"
| Unknown -> "Unknown OS"
(* อ่าน system info *)
let get_system_info () : system_info =
let hostname = Unix.gethostname () in
let cpu_count = Domain.recommended_domain_count () in
let os =
(* ตรวจสอบ OS อย่างง่าย ๆ ผ่าน environment *)
match Sys.os_type with
| "Unix" ->
if Sys.file_exists "/etc/os-release" then Linux "detected"
else Macos (* สมมติ macOS ถ้าไม่มี /etc/os-release *)
| "Win32" | "Cygwin" -> Windows
| _ -> Unknown
in
{ os; cpu_count; hostname }
(* ---------- Part B: Parallelism with Domains ---------- *)
(* ฟังก์ชันที่ใช้ CPU หนัก *)
let heavy_computation n =
let rec loop i acc =
if i = 0 then acc
else loop (i - 1) (acc +. sin (float_of_int i) *. cos (float_of_int i))
in
loop n 0.0
(* รัน 4 งานขนานกันด้วย Domain *)
let parallel_demo () =
Printf.printf "\n--- Parallel computation demo ---\n";
let iterations = 10_000_000 in
(* Spawn 4 domains พร้อมกัน *)
let d1 = Domain.spawn (fun () -> heavy_computation iterations) in
let d2 = Domain.spawn (fun () -> heavy_computation iterations) in
let d3 = Domain.spawn (fun () -> heavy_computation iterations) in
let d4 = Domain.spawn (fun () -> heavy_computation iterations) in
(* รอให้ทุก domain ทำงานเสร็จ และรวมผลลัพธ์ *)
let results =
[ Domain.join d1; Domain.join d2
; Domain.join d3; Domain.join d4 ]
in
let sum = List.fold_left (+.) 0.0 results in
Printf.printf "Sum from 4 parallel domains: %f\n" sum
(* ---------- Part C: Effect Handler (OCaml 5 flagship feature) ---------- *)
(* ประกาศ effect ใหม่: ถามค่าจาก environment *)
type _ Effect.t += Ask : string -> string Effect.t
(* ฟังก์ชันที่ perform effect *)
let greet_user () =
let name = Effect.perform (Ask "name") in
let role = Effect.perform (Ask "role") in
Printf.sprintf "Hello %s, the %s!" name role
(* Handler: interpret effect *)
let run_with_static_answers computation =
let open Effect.Deep in
try_with computation ()
{ effc = fun (type a) (eff : a Effect.t) ->
match eff with
| Ask "name" ->
Some (fun (k : (a, _) continuation) -> continue k "Moo")
| Ask "role" ->
Some (fun (k : (a, _) continuation) -> continue k "Lecturer")
| _ -> None
}
let effect_demo () =
Printf.printf "\n--- Effect handler demo ---\n";
let greeting = run_with_static_answers greet_user in
Printf.printf "%s\n" greeting
(* ---------- Main ---------- *)
let () =
let info = get_system_info () in
Printf.printf "=== Moo's OCaml 5 Systems Demo ===\n";
Printf.printf "Hostname: %s\n" info.hostname;
Printf.printf "OS: %s\n" (os_to_string info.os);
Printf.printf "Recommended domains: %d\n" info.cpu_count;
parallel_demo ();
effect_demo ();
Printf.printf "\nDone!\n"
# Build โปรเจค
dune build
# Run
dune exec moo_systems
# Build + run ในคำสั่งเดียว (useful สำหรับ dev loop)
dune exec ./bin/main.exe
# Watch mode: rebuild อัตโนมัติเมื่อไฟล์เปลี่ยน
dune build --watch
# Clean build artifacts
dune clean
ตัวอย่างผลลัพธ์
=== Moo's OCaml 5 Systems Demo ===
Hostname: moo-cachyos
OS: Linux (detected)
Recommended domains: 16
--- Parallel computation demo ---
Sum from 4 parallel domains: -2.847502
--- Effect handler demo ---
Hello Moo, the Lecturer!
Done!
# บน CachyOS / Arch
sudo pacman -S perf
# บน Ubuntu / Debian
sudo apt install linux-tools-common linux-tools-generic
# ตรวจสอบ
perf --version
การใช้งานพื้นฐาน
# Compile โปรแกรมพร้อม debug symbols
OCAMLFIND_FLAGS="-g" dune build --profile=release
# Profile โปรแกรม
perf record -g ./_build/default/bin/main.exe
perf report # เปิด interactive TUI
# Flamegraph
perf record -g -F 99 ./_build/default/bin/main.exe
perf script > out.perf
# ใช้ FlameGraph tool จาก https://github.com/brendangregg/FlameGraph
(* เพิ่ม ppx_landmarks ใน dune file:
(preprocess (pps landmarks-ppx --auto))
*)
(* แค่ build ด้วย flag ก็ profile ทุก function อัตโนมัติ *)
let compute x =
let y = x * 2 in
Thread.delay 0.1;
y + 1
let () =
let _ = compute 42 in
()
# รันโดยเปิด landmarks
OCAML_LANDMARKS=on dune exec ./bin/main.exe
# Export เป็น JSON เพื่อวิเคราะห์
OCAML_LANDMARKS="on,output=profile.json,format=json" dune exec ./bin/main.exe
(* เพิ่มบรรทัดนี้ตอนเริ่มโปรแกรม *)
let () =
Memtrace.trace_if_requested ();
(* ... main code ... *)
()
# รันพร้อม tracing
MEMTRACE=/tmp/trace.ctf dune exec ./bin/main.exe
# View ด้วย memtrace_viewer
memtrace-viewer /tmp/trace.ctf
# เปิด browser ที่ http://localhost:8080
เพื่อให้ workflow เร็วขึ้น สร้างไฟล์ Makefile ที่ root ของโปรเจค
# Makefile for moo_systems
.PHONY: all build release test fmt lint clean watch repl profile bench
# Default target
all: build
# Development build
build:
dune build
# Optimized release build
release:
dune build --profile=release
# Run tests
test:
dune runtest --force
# Format all source files
fmt:
dune build @fmt --auto-promote
# Lint (type-check only, no codegen)
lint:
dune build @check
# Clean all artifacts
clean:
dune clean
# Watch mode for dev
watch:
dune build --watch
# Interactive REPL with project loaded
repl:
dune utop lib
# Run with landmarks profiling
profile:
OCAML_LANDMARKS="on,output=profile.txt" dune exec ./bin/main.exe
@echo "Profile saved to profile.txt"
# Run with memtrace
memtrace:
MEMTRACE=/tmp/moo-trace.ctf dune exec ./bin/main.exe
@echo "Memory trace saved to /tmp/moo-trace.ctf"
@echo "View with: memtrace-viewer /tmp/moo-trace.ctf"
# Run benchmarks
bench:
dune exec ./bench/bench.exe
# Install as opam package (local)
install:
dune install
# Setup dev environment (first time)
setup:
opam install . --deps-only --with-test -y
@echo "Environment ready. Run: eval \$$(opam env)"
| # | รายการตรวจสอบ | คำสั่งตรวจสอบ | ผลลัพธ์ที่คาดหวัง |
|---|---|---|---|
| 1 | opam ติดตั้งแล้ว | opam --version |
2.2.0 หรือใหม่กว่า |
| 2 | OCaml 5.x active | ocaml --version |
5.2.0 หรือใหม่กว่า |
| 3 | dune พร้อมใช้ | dune --version |
3.15.x หรือใหม่กว่า |
| 4 | LSP ทำงาน | which ocamllsp |
~/.opam/systems-5.2/bin/ocamllsp |
| 5 | Formatter พร้อม | ocamlformat --version |
0.26.x หรือใหม่กว่า |
| 6 | Domainslib ติดตั้งแล้ว | opam list domainslib |
มีในรายการ |
| 7 | Eio ติดตั้งแล้ว | opam list eio |
มีในรายการ |
| 8 | Multicore ใช้งานได้ | ocaml -e "print_int (Domain.recommended_domain_count ())" |
เลขจำนวน CPU cores |
| 9 | memtrace พร้อม | which memtrace-viewer |
มี path |
| 10 | utop ทำงาน | utop |
เข้าสู่ REPL |
utop คือ REPL ที่ใช้งานง่ายกว่า ocaml native และมี syntax highlighting, auto-complete
# เริ่ม utop
utop
# โหลด library
#require "domainslib";;
#require "eio_main";;
# ลอง code
let n = Domain.recommended_domain_count ();;
(* val n : int = 16 *)
# Load project library
#require "moo_systems";;
# Open module
open Domainslib;;
# ออก
#quit;;
เทคนิคที่เป็นประโยชน์
(* ดู type ของ expression *)
let x = 42;;
(* val x : int = 42 *)
(* โหลดไฟล์ทั้งไฟล์ *)
#use "my_module.ml";;
(* ดู directives ทั้งหมด *)
#help;;
(* เปิด timing ของแต่ละ command *)
#time;;
let _ = List.init 1_000_000 (fun i -> i * i);;
(* แสดง time ที่ใช้ *)
ในส่วนที่ 1 นี้ เราได้เห็นภาพรวมของ OCaml 5 ในบริบทของ systems programming ทั้งในแง่
ในส่วนต่อไป เราจะเจาะลึก type system และ memory layout ของ OCaml ซึ่งเป็นพื้นฐานที่จำเป็นก่อนเริ่มเขียน systems code ที่มีประสิทธิภาพ