/static/codemoomoo.png

OCaml 5 สำหรับการพัฒนาระบบ IT: Memory Safety, Concurrency และ Multicore

Appendix — Reference Materials for Systems Development

ภาคผนวกนี้รวบรวม cheatsheets, ตารางเปรียบเทียบเชิงลึก (in-depth comparison tables), แผนที่ระบบนิเวศ (ecosystem map), และทรัพยากรอ้างอิง (reference resources) ที่จำเป็นสำหรับนักพัฒนา OCaml 5 สายงาน systems programming โดยเนื้อหาทุกส่วนมาพร้อมตัวอย่างโค้ดที่ทดลองใช้จริงได้ (runnable examples) และการเปรียบเทียบเชิงเทคนิคแบบตรงประเด็นกับ Rust และ Go


A. OCaml 5 Reference Appendix

A.1 Cheatsheet: Concurrency Primitives เปรียบเทียบ OCaml 5 vs Rust vs Go

เปรียบเทียบ concurrency primitives ทั้งสามภาษาในระดับ mapping ทีละ primitive พร้อมโค้ดตัวอย่างที่ทำงานได้จริง เพื่อให้นักพัฒนาที่ย้ายระหว่างภาษาสามารถหาคู่เทียบ (counterpart) ของ primitive ที่คุ้นเคยได้อย่างรวดเร็ว

A.1.1 ภาพรวม Concurrency Model ของทั้งสามภาษา

OCaml 5, Rust, และ Go เลือกใช้โมเดล concurrency ที่ต่างกันในระดับพื้นฐาน (fundamentally different models) ทำให้ primitive ที่ใช้ก็ต่างกันตามไปด้วย

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#3c3836','primaryTextColor':'#ebdbb2','primaryBorderColor':'#83a598','lineColor':'#a89984','secondaryColor':'#504945','tertiaryColor':'#282828','background':'#282828','mainBkg':'#3c3836','nodeBorder':'#83a598','clusterBkg':'#32302f','clusterBorder':'#fabd2f','titleColor':'#fabd2f','edgeLabelBackground':'#282828'}}}%%
flowchart LR
  subgraph OCaml["OCaml 5 Model"]
    D1[Domain 1
OS Thread + Minor Heap] D2[Domain 2
OS Thread + Minor Heap] F1[Fiber A
Effect-based] F2[Fiber B
Effect-based] D1 --> F1 D1 --> F2 end subgraph Rust["Rust Model"] T1[OS Thread 1] T2[OS Thread 2] A1[async Task A] A2[async Task B] T1 --> A1 T1 --> A2 end subgraph Go["Go Model"] M1[M:N Scheduler
GOMAXPROCS] G1[goroutine 1] G2[goroutine 2] G3[goroutine N] M1 --> G1 M1 --> G2 M1 --> G3 end

A.1.2 ตารางเปรียบเทียบ Primitive ต่อ Primitive

ตารางด้านล่างจับคู่ primitive ที่ให้ความหมายใกล้เคียงกัน (semantic equivalents) ระหว่างสามภาษา เพื่อให้มองเห็นได้ในครั้งเดียวว่าแต่ละโมเดลเลือกใช้ตัวไหนสำหรับงานเดียวกัน

วัตถุประสงค์ (Purpose) OCaml 5 Rust Go
Spawn parallel work (OS thread) Domain.spawn std::thread::spawn go keyword (auto-managed)
Lightweight task / green thread Eio.Fiber.fork tokio::spawn go keyword
Join / wait completion Domain.join JoinHandle::join sync.WaitGroup
Mutual exclusion Mutex.create std::sync::Mutex sync.Mutex
Read-write lock Rwlock (stdlib ใน 5.1+) std::sync::RwLock sync.RWMutex
Atomic variable Atomic.make std::sync::atomic::AtomicXxx sync/atomic package
Compare-and-swap (CAS) Atomic.compare_and_set AtomicXxx::compare_exchange atomic.CompareAndSwapXxx
Condition variable Condition.create std::sync::Condvar sync.Cond
Semaphore Semaphore.Counting.make tokio::sync::Semaphore chan ที่ buffered
Channel / message passing Eio.Stream.create หรือ Domainslib.Chan std::sync::mpsc / tokio::sync::mpsc chan T
Unbounded channel Eio.Stream.create max_int crossbeam::channel::unbounded make(chan T) (unbuffered)
Bounded channel Eio.Stream.create n tokio::sync::mpsc::channel(n) make(chan T, n)
Select on multiple channels Eio.Fiber.any tokio::select! select { case ... }
Cancellation Eio.Switch / Fiber.cancel CancellationToken / drop future context.Context
Structured concurrency Eio.Switch.run tokio::task::JoinSet errgroup.Group (third-party)
Parallel for loop Domainslib.Task.parallel_for rayon::par_iter manual + sync.WaitGroup
Task pool / work stealing Domainslib.Task.setup_pool rayon::ThreadPoolBuilder built-in scheduler
Cooperative yield Eio.Fiber.yield tokio::task::yield_now runtime.Gosched
Sleep / timer Eio.Time.sleep tokio::time::sleep time.Sleep
Data race prevention ไม่มี compile-time (runtime only) ✅ compile-time (Send + Sync) runtime detector (-race)

A.1.3 ตัวอย่างโค้ด: Spawn + Join

ต่อไปนี้คือตัวอย่าง "spawn ทำงานคู่ขนานแล้ว join" — งานพื้นฐานที่สุดของ concurrency — เขียนในทั้งสามภาษาเพื่อให้เห็นความแตกต่างของ syntax และ semantics

OCaml 5 — Domains (true parallelism)

(* file: spawn_join.ml *)
(* คอมไพล์: ocamlfind ocamlopt -package domainslib -linkpkg spawn_join.ml *)
(* หรือใช้ dune: dune exec ./spawn_join.exe *)

(* ฟังก์ชันหนักที่จะให้แต่ละ Domain ทำ — จำลอง CPU-bound work *)
let heavy_work label n =
  let sum = ref 0 in
  for i = 1 to n do
    sum := !sum + i
  done;
  Printf.printf "[%s] sum = %d\n%!" label !sum;
  !sum

let () =
  (* Domain.spawn สร้าง OS thread ใหม่ + minor heap แยก = parallelism จริง *)
  let d1 = Domain.spawn (fun () -> heavy_work "domain-1" 10_000_000) in
  let d2 = Domain.spawn (fun () -> heavy_work "domain-2" 20_000_000) in
  (* Domain.join รอให้ทำเสร็จและคืนค่าผลลัพธ์ *)
  let r1 = Domain.join d1 in
  let r2 = Domain.join d2 in
  Printf.printf "total = %d\n" (r1 + r2)

(* ผลลัพธ์ที่คาดหวัง (ลำดับอาจสลับได้):
   [domain-1] sum = 50000005000000
   [domain-2] sum = 200000010000000
   total = 250000015000000                            *)

Rust — std::thread

// file: spawn_join.rs
// คอมไพล์: rustc spawn_join.rs -O

use std::thread;

fn heavy_work(label: &str, n: u64) -> u64 {
    let sum: u64 = (1..=n).sum();
    println!("[{}] sum = {}", label, sum);
    sum
}

fn main() {
    // thread::spawn คืน JoinHandle — ต้อง move ค่าที่ใช้ข้าม thread เพราะ ownership
    let h1 = thread::spawn(|| heavy_work("thread-1", 10_000_000));
    let h2 = thread::spawn(|| heavy_work("thread-2", 20_000_000));
    // join คืน Result<T, Box<dyn Any>> — ต้อง unwrap panic case
    let r1 = h1.join().unwrap();
    let r2 = h2.join().unwrap();
    println!("total = {}", r1 + r2);
}

Go — goroutines + WaitGroup

// file: spawn_join.go
// run: go run spawn_join.go

package main

import (
    "fmt"
    "sync"
)

func heavyWork(label string, n uint64, out *uint64, wg *sync.WaitGroup) {
    defer wg.Done()             // แจ้ง WaitGroup เมื่อทำเสร็จ
    var sum uint64 = 0
    for i := uint64(1); i <= n; i++ {
        sum += i
    }
    fmt.Printf("[%s] sum = %d\n", label, sum)
    *out = sum
}

func main() {
    var wg sync.WaitGroup
    var r1, r2 uint64
    wg.Add(2)
    go heavyWork("goroutine-1", 10_000_000, &r1, &wg)
    go heavyWork("goroutine-2", 20_000_000, &r2, &wg)
    wg.Wait()
    fmt.Printf("total = %d\n", r1+r2)
}

ข้อสังเกตสำคัญ (Key Observations):

A.1.4 ตัวอย่างโค้ด: Channel / Message Passing

OCaml 5 — Eio.Stream

(* file: channel_demo.ml
   dune: (executables (names channel_demo) (libraries eio_main)) *)

open Eio.Std

let producer stream =
  for i = 1 to 5 do
    Eio.Stream.add stream i;
    traceln "produced: %d" i
  done;
  Eio.Stream.add stream (-1) (* sentinel — สัญญาณว่าจบแล้ว *)

let consumer stream =
  let rec loop () =
    match Eio.Stream.take stream with
    | -1 -> traceln "consumer: done"
    | n  -> traceln "consumed: %d" n; loop ()
  in
  loop ()

let () =
  Eio_main.run @@ fun _env ->
  let stream = Eio.Stream.create 3 in  (* bounded buffer = 3 *)
  Fiber.both
    (fun () -> producer stream)
    (fun () -> consumer stream)

Go — channel + range

// file: channel_demo.go
package main

import "fmt"

func main() {
    ch := make(chan int, 3)            // bounded channel size 3
    go func() {
        for i := 1; i <= 5; i++ {
            ch <- i
            fmt.Printf("produced: %d\n", i)
        }
        close(ch)                       // ปิด channel เพื่อ signal consumer
    }()
    for n := range ch {                 // range block จนกว่า channel ปิด
        fmt.Printf("consumed: %d\n", n)
    }
}

Rust — tokio::mpsc

// file: channel_demo.rs — ต้องมี [dependencies] tokio = { version = "1", features = ["full"] }

#[tokio::main]
async fn main() {
    let (tx, mut rx) = tokio::sync::mpsc::channel::<i32>(3);
    tokio::spawn(async move {
        for i in 1..=5 {
            tx.send(i).await.unwrap();
            println!("produced: {}", i);
        }
        // drop(tx) อัตโนมัติเมื่อ task จบ → rx.recv() จะคืน None
    });
    while let Some(n) = rx.recv().await {
        println!("consumed: {}", n);
    }
}

A.2 Memory Safety Comparison: OCaml GC vs Rust Ownership vs Go GC

สามภาษา สามปรัชญาในการรับประกัน memory safety: OCaml ใช้ generational GC ที่ปรับแต่งได้ (tunable), Rust ใช้ ownership + borrow checker ที่พิสูจน์ได้ตอนคอมไพล์ (compile-time), และ Go ใช้ concurrent GC ที่ออกแบบมาเพื่อ low-latency บน server workload

A.2.1 ปรัชญาการออกแบบ (Design Philosophy)

การรับประกันความปลอดภัยของหน่วยความจำ (memory safety) สามารถทำได้หลายวิธี แต่ละวิธีแลกเปลี่ยน (trade off) อย่างต่างกันระหว่าง developer ergonomics, runtime performance, และ predictability

  1. OCaml — เลือก automatic GC แต่ออกแบบให้ predictable มากที่สุดสำหรับ functional workloads โดยใช้ immutability เป็นค่าเริ่มต้น ทำให้ allocation pattern เดาง่าย GC ทำงานได้มีประสิทธิภาพ
  2. Rust — เลือก ownership model ที่ตรวจสอบตอนคอมไพล์ ไม่ต้องมี GC เลย ได้ predictability ระดับเดียวกับ C/C++ แต่ปลอดภัยกว่า trade-off คือ learning curve สูงและ developer cognitive load มากขึ้น
  3. Go — เลือก concurrent GC ที่ออกแบบมาเพื่อ low pause time (< 1ms) โดยยอมเสีย throughput trade-off ที่ดีสำหรับ server/microservice แต่ไม่ดีสำหรับงาน latency-critical ที่สุด
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#3c3836','primaryTextColor':'#ebdbb2','primaryBorderColor':'#83a598','lineColor':'#a89984','secondaryColor':'#504945','background':'#282828','mainBkg':'#3c3836','nodeBorder':'#b8bb26','clusterBkg':'#32302f','clusterBorder':'#fabd2f','edgeLabelBackground':'#282828'}}}%%
flowchart TB
  Start([Memory Safety Requirement]) --> Q1{ต้องการ
zero-overhead?} Q1 -->|Yes| Rust[Rust
Ownership + Borrow] Q1 -->|No| Q2{Pause time
สำคัญ?} Q2 -->|Very| Go[Go
Concurrent GC
<1ms pause] Q2 -->|Moderate| OCaml[OCaml
Generational GC
tunable] Rust --> R1[+ No runtime overhead
+ Predictable
- Steep learning curve] Go --> G1[+ Easy to write
+ Low pause
- Throughput cost] OCaml --> O1[+ Immutable by default
+ Short minor GC
- Tuning needed]

A.2.2 ตารางเปรียบเทียบเชิงเทคนิค

คุณสมบัติ (Feature) OCaml 5 Rust Go
กลไกหลัก (Primary mechanism) Generational GC Ownership + Borrow checker Concurrent mark-sweep GC
Check time Runtime Compile-time Runtime
Null safety ✅ (via option type) ✅ (via Option<T>) ❌ (nil pointer panic runtime)
Use-after-free prevention ✅ (GC) ✅ (lifetimes) ✅ (GC)
Double-free prevention ✅ (single owner)
Data race prevention ❌ (runtime concern) ✅ (Send + Sync traits) ❌ (need -race detector)
Memory leak prevention ❌ (cycle collector ช่วย) ❌ (Rc cycle leaks) ❌ (goroutine leaks common)
Typical pause time ~1–10 ms (minor), variable (major) 0 ms (no GC) <1 ms (sub-ms target)
Memory overhead vs C ~1.5–3× ~1.0× (สมจริง) ~2–3×
Throughput overhead 5–15% GC work 0% 10–20% GC work
Manual tuning needed ปานกลาง (Gc.set) ไม่ต้อง น้อยมาก (GOGC)
Off-heap memory support ✅ (Bigarray) ✅ (Box, Vec, raw pointers) ⚠️ (unsafe package)
FFI safety ⚠️ manual pinning ✅ with care ⚠️ cgo overhead

A.2.3 Generational Hypothesis และคณิตศาสตร์ของ OCaml GC

OCaml GC สร้างอยู่บน generational hypothesis ที่ว่า object ส่วนใหญ่ตายเร็ว (most objects die young) — การสังเกตเชิงประจักษ์ที่พบได้ในเกือบทุก workload ของ functional programming

ถ้าให้ $p(t)$ คือความน่าจะเป็นที่ object จะยังมีชีวิตอยู่หลังเวลา $t$ (survival probability) และพบว่า distribution นี้ decay แบบ exponential ในช่วงต้น คณิตศาสตร์ของ minor GC cost จะเป็นดังนี้:

Cminor = αscan · S + αcopy · L

โดยที่:

เนื่องจาก $L \ll N$ (จำนวน allocation ทั้งหมดใน minor heap) ตาม generational hypothesis ทำให้ minor GC cost เป็นสัดส่วนกับ live data ไม่ใช่ allocated data — นี่คือเหตุผลที่ minor GC ใน OCaml เร็วมากแม้ allocate หนัก

สำหรับ throughput utilization ของโปรแกรม:

U = Tmutator Tmutator + Tgc

โดยที่:

ค่า $U$ สำหรับ OCaml โดยทั่วไปอยู่ที่ 0.85–0.95 (GC overhead 5–15%) ขึ้นอยู่กับ space_overhead ที่ตั้ง — เทียบกับ Rust ที่ $U = 1.0$ (ไม่มี GC)

A.2.4 ตัวอย่าง: ปรับจูน GC ของ OCaml

(* file: gc_tuning.ml *)
(* ตัวอย่างการปรับจูน GC สำหรับ workload ต่างกัน *)

let print_gc_stats label =
  let s = Gc.stat () in
  Printf.printf "\n=== %s ===\n" label;
  Printf.printf "  minor collections : %d\n" s.minor_collections;
  Printf.printf "  major collections : %d\n" s.major_collections;
  Printf.printf "  heap words        : %d (%.2f MB)\n"
    s.heap_words (float_of_int s.heap_words *. 8.0 /. 1024.0 /. 1024.0);
  Printf.printf "  live words        : %d\n" s.live_words;
  Printf.printf "  promoted words    : %d\n" s.promoted_words

(* Workload 1: allocation-heavy — ทำซ้ำสร้าง list สั้นๆ *)
let allocation_heavy () =
  for _ = 1 to 100_000 do
    let _ = List.init 100 (fun i -> i * 2) in
    ()
  done

let () =
  (* ค่า default *)
  print_gc_stats "Before tuning (default)";
  allocation_heavy ();
  print_gc_stats "After default run";

  (* ปรับให้ minor heap ใหญ่ขึ้น = major GC น้อยลง แต่ใช้ RAM มากขึ้น *)
  Gc.set { (Gc.get ()) with
    minor_heap_size = 1 lsl 20;   (* 1M words = 8MB *)
    space_overhead  = 120;         (* default 80; ใหญ่ขึ้น = GC เบาลง *)
  };
  Gc.compact ();                    (* reset stats เพื่อเปรียบเทียบ *)

  allocation_heavy ();
  print_gc_stats "After tuned run"

(* การรัน:
   ocaml gc_tuning.ml
   
   ผลที่คาดหวัง: After tuned run จะมี major_collections น้อยกว่ามาก
   แต่ heap_words ใหญ่กว่า (trade-off: throughput vs memory)    *)

A.2.5 เปรียบเทียบโค้ด: การป้องกัน Use-After-Free

OCaml 5 — GC จัดการให้ ไม่ต้องเขียน lifetime

let get_ref () =
  let x = ref 42 in
  x  (* คืน ref กลับ — GC จะรักษาไว้ตราบใดที่มี reachable path *)

let () =
  let r = get_ref () in
  Printf.printf "%d\n" !r  (* ปลอดภัย — x ยังมีชีวิตอยู่ *)

Rust — compiler บังคับ lifetime

// โค้ดนี้ "ไม่ผ่าน" borrow checker
fn get_ref() -> &'static i32 {
    let x = 42;
    &x  // ❌ error: `x` does not live long enough
}

// วิธีที่ถูกคือ move ownership หรือใช้ Box
fn get_ref_ok() -> Box<i32> {
    Box::new(42)  // heap allocation, ownership transferred
}

Go — GC จัดการ เหมือน OCaml

func getRef() *int {
    x := 42
    return &x  // escape analysis จะ promote x ไป heap อัตโนมัติ
}

ข้อสรุป: OCaml และ Go ให้ developer ergonomics ที่คล้ายกัน (ไม่ต้องคิดเรื่อง lifetime) แลกกับ GC overhead ส่วน Rust บังคับให้ developer จัดการเอง แลกกับ zero overhead และความปลอดภัยที่พิสูจน์ได้


A.3 Ecosystem Map: opam Packages ที่สำคัญสำหรับ Systems Work

แผนที่ระบบนิเวศของ OCaml ในงาน systems programming จัดกลุ่มตามโดเมน (domain) พร้อมคำแนะนำการเลือกใช้ (selection guide) ระบุ package ที่เป็น production-grade และสถานะการบำรุงรักษา (maintenance status) ในปัจจุบัน

A.3.1 แผนที่ภาพรวม (Ecosystem Overview Map)

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#3c3836','primaryTextColor':'#ebdbb2','primaryBorderColor':'#83a598','lineColor':'#a89984','secondaryColor':'#504945','background':'#282828','mainBkg':'#3c3836','nodeBorder':'#8ec07c','clusterBkg':'#32302f','clusterBorder':'#fe8019','edgeLabelBackground':'#282828','titleColor':'#fabd2f'}}}%%
flowchart TB
  Core((OCaml 5
Systems Core)) subgraph Build["Build & Tools"] dune opam ocamlformat ocaml-lsp-server odoc end subgraph Concur["Concurrency"] eio domainslib lwt async end subgraph Net["Networking"] cohttp dream conduit tls mirage-crypto end subgraph Data["Data & Serialization"] angstrom faraday yojson cstruct zarith end subgraph DB["Database"] caqti irmin index end subgraph CLI["CLI & Logging"] cmdliner logs fmt bos end subgraph Test["Testing"] alcotest qcheck bechamel crowbar end Core --> Build Core --> Concur Core --> Net Core --> Data Core --> DB Core --> CLI Core --> Test

A.3.2 Build System, Formatting และ Development Tools

เครื่องมือพื้นฐานที่ทุกโปรเจกต์ควรมี (foundation tooling) ไม่ว่าจะเขียน systems code ประเภทใด

Package หน้าที่ (Role) สถานะ (Status) คำสั่งติดตั้ง (Install)
dune Build system หลัก (de facto standard) ✅ Active opam install dune
opam Package manager ✅ Active ติดตั้งระดับระบบ
ocamlformat Code formatter ✅ Active opam install ocamlformat
ocaml-lsp-server LSP server สำหรับ editor ✅ Active opam install ocaml-lsp-server
merlin Semantic analysis (ใช้ร่วมกับ LSP) ✅ Active opam install merlin
odoc Documentation generator ✅ Active opam install odoc
utop Enhanced REPL ✅ Active opam install utop
dune-release Release automation ✅ Active opam install dune-release

A.3.3 Concurrency และ Async Libraries

Eio เป็นตัวเลือกใหม่ที่แนะนำสำหรับโปรเจกต์ OCaml 5 ใหม่ๆ เนื่องจากใช้ประโยชน์จาก effect handlers และ direct-style โค้ด (no monadic >>=)

Package Paradigm เมื่อไหร่ใช้ (When to use)
eio + eio_main Effect-based, direct style โปรเจกต์ใหม่ใน OCaml 5 — แนะนำเป็น default
domainslib Task pool + parallel_for CPU-bound parallelism (numerical, data processing)
lwt + lwt_ppx Promise/monadic style Legacy codebases, หรือ ecosystem lock-in
async (Jane Street) Promise/monadic style ใช้ร่วมกับ Core/Base ecosystem
saturn Lock-free data structures เมื่อต้องการ concurrent data structure ที่เร็วกว่า Mutex
kcas Software transactional memory Multi-word atomic updates

A.3.4 Networking และ Protocols

Package หน้าที่ หมายเหตุ
cohttp-eio HTTP client/server (low-level) ใช้ร่วมกับ Eio; production-grade
dream Web framework (high-level) คล้าย Flask/Sinatra; built on Httpaf
httpaf HTTP/1 low-level parser Zero-copy, เร็วมาก
h2 HTTP/2 implementation Pure OCaml
tls / ocaml-tls TLS pure OCaml ใช้ใน MirageOS, audit-friendly
mirage-crypto Crypto primitives Modern algorithms, AEAD support
conduit Connection abstraction รวม TCP, Unix sockets, TLS ในอินเทอร์เฟซเดียว
uri URI parsing RFC 3986 Standard library-grade
ocaml-protoc Protocol Buffers codegen สำหรับ gRPC ecosystem
grpc-eio gRPC client/server Eio-based

A.3.5 Data Serialization และ Parsing

Package รูปแบบ (Format) จุดเด่น
angstrom Binary/text parser combinator Fast, resumable parsing (streaming)
faraday Serialization คู่กับ Angstrom สำหรับ write
yojson JSON Standard; ppx_yojson_conv สำหรับ auto-derive
jsonaf JSON (Jane Street) เร็วกว่า Yojson; Core ecosystem
cstruct Binary buffer (typed) คล้าย Rust zerocopy
bigarray C-compatible arrays Built-in; off-heap memory
zarith Arbitrary-precision integer ใช้ GMP; สำคัญสำหรับ crypto/finance
hex Hex encoding/decoding Trivial แต่ใช้บ่อย
base64 Base64 RFC-compliant
sexplib + ppx_sexp_conv S-expressions Jane Street standard serialization

A.3.6 Database และ Persistence

Package ประเภท หมายเหตุ
caqti Unified DB interface รองรับ PostgreSQL, SQLite, MariaDB; async via Lwt/Eio
caqti-eio Eio driver สำหรับ OCaml 5 projects
caqti-lwt Lwt driver สำหรับ legacy codebase
postgresql-ocaml PostgreSQL binding Direct binding; lower-level
irmin Git-like distributed store Pure OCaml, MirageOS origin
index On-disk key-value Backing store สำหรับ Irmin
sqlite3 SQLite direct binding Minimal wrapper

A.3.7 CLI, Logging และ Operations

(* ตัวอย่าง: CLI + logging + config — pattern มาตรฐานสำหรับ systems tool *)
(* dune: (executables (names mytool)
         (libraries cmdliner logs logs.cli logs.fmt fmt.tty)) *)

open Cmdliner

(* กำหนด logger สำหรับแต่ละ module *)
let src = Logs.Src.create "mytool" ~doc:"My systems tool"
module Log = (val Logs.src_log src : Logs.LOG)

(* subcommand: serve *)
let serve_cmd port host =
  Log.info (fun m -> m "starting server on %s:%d" host port);
  (* ... server code ... *)
  `Ok 0

let port =
  let doc = "Port to listen on" in
  Arg.(value & opt int 8080 & info ["p"; "port"] ~doc ~docv:"PORT")

let host =
  let doc = "Host to bind to" in
  Arg.(value & opt string "0.0.0.0" & info ["H"; "host"] ~doc ~docv:"HOST")

(* setup log level จาก --verbose flag *)
let setup_log =
  let docs = Manpage.s_common_options in
  Term.(const (fun style_renderer level ->
    Fmt_tty.setup_std_outputs ?style_renderer ();
    Logs.set_level level;
    Logs.set_reporter (Logs_fmt.reporter ()))
    $ Fmt_cli.style_renderer ~docs ()
    $ Logs_cli.level ~docs ())

let serve =
  let info = Cmd.info "serve" ~doc:"Start the server" in
  Cmd.v info Term.(ret (const serve_cmd $ port $ host $ setup_log))

let () =
  let info = Cmd.info "mytool" ~version:"0.1.0" ~doc:"Systems demo" in
  exit (Cmd.eval (Cmd.group info [serve]))

(* การใช้งาน:
   ./mytool serve --port 9090 --host 127.0.0.1 --verbose
   ./mytool serve --help              *)
Package หน้าที่
cmdliner Declarative CLI + man page generation
logs Structured logging (hierarchical sources)
fmt Printing combinators + color
bos Unix command-line tool utilities (paths, env, exec)
daemonize Daemonization (fork + session detach)

A.3.8 Testing และ Property-Based Testing

Package ประเภท ใช้สำหรับ
alcotest Unit testing Standard unit tests, colorful output
alcotest-eio Eio-aware testing Async tests สำหรับ Eio
qcheck Property-based testing Random input generation + shrinking
qcheck-alcotest QCheck + Alcotest integration รวมสอง framework เข้าด้วยกัน
bechamel Statistical benchmarking Modern replacement ของ core_bench
crowbar Fuzzing (AFL-based) Coverage-guided fuzzing
monolith Differential testing ทดสอบ implementation ใหม่เทียบ reference
ortac Runtime verification Gospel specs → runtime checks

A.3.9 FFI และ Low-Level

Package หน้าที่
ctypes Type-safe C FFI — ไม่ต้องเขียน C stubs
ctypes-foreign Dynamic loading (dlopen-like)
integers Portable C-compatible integer types
bigstringaf Zero-copy bigstring operations
lru LRU cache (pure OCaml)

A.4 ทรัพยากรและการเรียนรู้เพิ่มเติม (Resources & Further Learning)

รายการแหล่งเรียนรู้ที่ curated และจัดลำดับตามความเหมาะสมสำหรับนักพัฒนา systems ที่ต้องการเข้าใจ OCaml 5 อย่างลึกซึ้ง แบ่งตามประเภท (หนังสือ, เอกสารทางการ, blog, community) พร้อมคำแนะนำลำดับการอ่าน

A.4.1 Timeline: พัฒนาการของ OCaml สำหรับ Systems Development

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#3c3836','primaryTextColor':'#ebdbb2','primaryBorderColor':'#83a598','lineColor':'#a89984','secondaryColor':'#504945','background':'#282828','mainBkg':'#3c3836','nodeBorder':'#fe8019','clusterBkg':'#32302f','clusterBorder':'#fabd2f','edgeLabelBackground':'#282828'}}}%%
flowchart LR
  subgraph Era1["ยุคต้น (Early Era) 1996-2010"]
    A1[OCaml 1.0
1996] A2[OCaml 3.x
2000s
Polymorphic variants] A3[Jane Street
adopts OCaml
~2003] A1 --> A2 --> A3 end subgraph Era2["ยุค Modern Tooling 2011-2018"] B1[opam 1.0
2013] B2[Real World OCaml
2013] B3[jbuilder → dune
2017] B4[MirageOS
unikernel
~2013] B5[Tezos launch
2018] B1 --> B2 --> B3 B3 --> B4 B3 --> B5 end subgraph Era3["ยุค Multicore (OCaml 5) 2019-ปัจจุบัน"] C1[Multicore OCaml
research
KC Sivaramakrishnan] C2[OCaml 5.0
Dec 2022
Domains + Effects] C3[Eio 0.x
2022-2023] C4[OCaml 5.1-5.3
performance fixes] C5[OCaml 5.x
production ready] C1 --> C2 --> C3 --> C4 --> C5 end Era1 --> Era2 --> Era3

อันดับที่ 1 — Real World OCaml, 2nd Edition

อันดับที่ 2 — OCaml from the Very Beginning / More OCaml

อันดับที่ 3 — OCaml Programming: Correct + Efficient + Beautiful

A.4.3 เอกสารทางการและ Official References

รายการ URL ที่ควร bookmark ไว้สำหรับงาน systems ใน OCaml 5:

A.4.4 Blogs และ Technical Writing

A.4.5 Community และ Discussion Forums

A.4.6 Source Code ที่ควรอ่าน (Production Codebases)

การอ่าน production code เป็นวิธีที่ดีที่สุดวิธีหนึ่งในการเรียน idiomatic style

โปรเจกต์ Domain เหตุผลที่ควรอ่าน
Tezos / Octez Blockchain node ใช้ Lwt extensively, modular design, crypto
MirageOS Unikernel Pure OCaml stack ตั้งแต่ TCP/IP ถึง TLS
Irmin Distributed store Elegant abstraction, functor pattern ที่ดี
Dune Build system Large OCaml project, concurrent builds
Semgrep Static analysis AST manipulation, parser integration
Xen Storage VM management Low-level systems via OCaml
Coq / Rocq Proof assistant Complex type system usage (note: moving to different approach)

A.4.7 Papers ที่ควรอ่าน (Academic Foundations)

สำหรับผู้ที่ต้องการเข้าใจ design decisions ในระดับลึกที่สุด:

  1. "Retrofitting Parallelism onto OCaml" (ICFP 2020) — KC Sivaramakrishnan et al. — เอกสารก่อตั้งของ OCaml 5 Multicore
  2. "Retrofitting Effect Handlers onto OCaml" (PLDI 2021) — การออกแบบ effect system ใน OCaml
  3. "Eio: A Unified Effects-Based IO Library for OCaml" — whitepaper ของ Eio architecture
  4. "OCaml 4.00 Garbage Collector" — Damien Doligez's notes เกี่ยวกับ generational design (historical แต่ยังใช้ได้)

A.4.8 แผนการเรียนรู้ที่แนะนำ (Learning Roadmap)

สำหรับผู้ที่มีพื้นฐานภาษาโปรแกรมอื่นมาแล้ว (เช่น มาจาก Rust, Python, หรือ Go) ลำดับการเรียนที่แนะนำคือ:

  1. สัปดาห์ 1–2: ทำ "OCaml from the Very Beginning" บทที่ 1–10 เพื่อให้ชินกับ syntax, pattern matching, และ module system
  2. สัปดาห์ 3–4: อ่าน Real World OCaml Part I (บทที่ 1–8) + เขียน CLI tool เล็กๆ ด้วย cmdliner + logs
  3. สัปดาห์ 5–6: Part II ของ RWO — focus บท GC, FFI, Functors — เป็นพื้นฐานสำหรับงาน systems
  4. สัปดาห์ 7–8: เรียน Eio ผ่าน official tutorial + เขียน TCP echo server
  5. สัปดาห์ 9–10: Multicore: อ่าน Tarides blog series + ลองเขียน parallel algorithm ด้วย Domainslib
  6. สัปดาห์ 11+: อ่าน production code (แนะนำเริ่มที่ Dune, Irmin) + contribute PR เล็กๆ

A.5 Quick Reference: OCaml 5 Syntax สำหรับ Systems Dev

Quick reference card สำหรับ syntax ที่ใช้บ่อยในงาน systems — ออกแบบให้พิมพ์ออกมา A4 เดียวแล้วติดไว้ข้างจอ

A.5.1 Type Declarations ที่สำคัญ

(* Sum type (variant) — สำหรับ state machine *)
type connection_state =
  | Disconnected
  | Connecting of { started_at : float }
  | Connected of { fd : Unix.file_descr; peer : string }
  | Closed of { reason : string }

(* Product type (record) — สำหรับ data bundle *)
type server_config = {
  host             : string;
  port             : int;
  max_connections  : int;
  mutable running  : bool;  (* mutable field *)
}

(* Generalized Algebraic Data Type (GADT) — สำหรับ type-safe DSL *)
type _ expr =
  | Int   : int    -> int expr
  | Bool  : bool   -> bool expr
  | Add   : int expr * int expr -> int expr
  | If    : bool expr * 'a expr * 'a expr -> 'a expr

(* Polymorphic variant — flexible โดยไม่ต้องประกาศ type *)
let handle = function
  | `Ok v    -> Printf.printf "ok: %d\n" v
  | `Error e -> Printf.eprintf "err: %s\n" e
  | `Timeout -> prerr_endline "timeout"

A.5.2 Module System Essentials

(* Module signature — interface ที่ชัดเจน *)
module type STORE = sig
  type key
  type value
  type t
  val create : unit -> t
  val put   : t -> key -> value -> unit
  val get   : t -> key -> value option
end

(* Concrete implementation *)
module StringStore : STORE with type key = string and type value = string = struct
  type key   = string
  type value = string
  type t     = (string, string) Hashtbl.t
  let create () = Hashtbl.create 16
  let put tbl k v = Hashtbl.replace tbl k v
  let get tbl k   = Hashtbl.find_opt tbl k
end

(* Functor — parameterized module *)
module Make_Cache (S : STORE) = struct
  type t = { store : S.t; mutable hits : int; mutable misses : int }
  let create () = { store = S.create (); hits = 0; misses = 0 }
  let lookup c k = match S.get c.store k with
    | Some v -> c.hits <- c.hits + 1; Some v
    | None   -> c.misses <- c.misses + 1; None
end

A.5.3 Concurrency Quick Reference

(* === Domains — true parallelism === *)
let d = Domain.spawn (fun () -> heavy_compute ())
let result = Domain.join d

(* === Atomic — lock-free counter === *)
let counter = Atomic.make 0
let () = Atomic.incr counter
let v = Atomic.get counter
let success = Atomic.compare_and_set counter 0 100

(* === Mutex — traditional locking === *)
let m = Mutex.create ()
let () =
  Mutex.lock m;
  (* critical section *)
  Mutex.unlock m

(* === Eio — structured concurrency === *)
open Eio.Std
let () = Eio_main.run @@ fun env ->
  Switch.run @@ fun sw ->
  Fiber.fork ~sw (fun () -> worker_a ());
  Fiber.fork ~sw (fun () -> worker_b ())
  (* ทุก fiber ภายใต้ switch จะจบก่อน run คืน *)

(* === Effect handlers === *)
type _ Effect.t += Log : string -> unit Effect.t
let log msg = Effect.perform (Log msg)

let run_with_logging f =
  Effect.Deep.try_with f ()
    { effc = fun (type a) (e : a Effect.t) ->
        match e with
        | Log msg -> Some (fun (k : (a, _) Effect.Deep.continuation) ->
            Printf.printf "[LOG] %s\n" msg;
            Effect.Deep.continue k ())
        | _ -> None }

A.5.4 Error Handling Patterns

(* === Option — absence of value === *)
let safe_div a b = if b = 0 then None else Some (a / b)
let result = match safe_div 10 2 with
  | Some v -> Printf.sprintf "ok: %d" v
  | None   -> "division by zero"

(* === Result — with error info === *)
let parse_int s =
  try Ok (int_of_string s)
  with Failure _ -> Error (Printf.sprintf "cannot parse: %s" s)

(* === Binding chain (monadic style) === *)
let ( let* ) = Result.bind
let compute s1 s2 =
  let* a = parse_int s1 in
  let* b = parse_int s2 in
  if b = 0 then Error "div by zero"
  else Ok (a / b)

(* === Exception (เมื่อ error ไม่ควร recover) === *)
exception Config_not_found of string
let load_config () =
  if not (Sys.file_exists "config.toml") then
    raise (Config_not_found "config.toml");
  (* ... *)

A.6 สรุปและคำแนะนำท้ายเล่ม (Final Recommendations)

OCaml 5 เป็นภาษาที่เหมาะกับ systems development ในบริบทที่ต้องการ type safety ระดับสูง, GC ที่ predictable, และ concurrency model ที่ทันสมัย ไม่ใช่ทุกสถานการณ์จะเหมาะกับ OCaml และการรู้ว่าเมื่อไหร่ควรเลือกเครื่องมือใดคือความสามารถที่สำคัญของ systems engineer

A.6.1 เมื่อไหร่ควรเลือก OCaml 5

เลือก OCaml 5 เมื่อ:

พิจารณาภาษาอื่นเมื่อ:

A.6.2 Checklist ก่อน Deploy Production OCaml System

  1. ตั้งค่า OCAMLRUNPARAM="b" เพื่อให้ได้ backtrace ใน production logs
  2. เปิด -rectypes หรือ -strict-sequence ตามต้องการ + เปิด warnings เป็น errors (-warn-error +a)
  3. ใช้ opam lock เพื่อสร้าง reproducible build
  4. Profile ด้วย perf + landmarks ก่อน deploy เพื่อหา allocation hotspots
  5. ตั้งค่า GC parameters (minor_heap_size, space_overhead) ตาม workload — default อาจไม่เหมาะ
  6. Enable memtrace เมื่อ debug memory issue: MEMTRACE=./trace.ctf ./myapp
  7. สร้าง healthcheck endpoint ที่ expose Gc.stat () เป็น metrics
  8. ตั้ง systemd service ที่มี Restart=on-failure + resource limits
  9. Static link (ถ้าได้) ด้วย musl เพื่อลด runtime dependency บน target host
  10. เขียน runbook ที่ครอบคลุม GC tuning, profiling, และ common failure modes

ลงท้าย: OCaml 5 เปิดประตูสู่การใช้ภาษา functional ที่ type-safe สำหรับงาน systems ที่ต้องการ parallelism จริงๆ เป็นครั้งแรก การเรียนรู้โมเดล Domains + Effects + Eio ต้องใช้เวลา แต่สิ่งที่ได้กลับคือโค้ดที่สั้น ชัดเจน และพิสูจน์ได้ง่าย — คุณสมบัติที่ systems engineer มองหามาตลอด

หวังว่าภาคผนวกนี้จะเป็น reference ที่ใช้ได้จริงสำหรับทั้งการเริ่มต้นและการทำงานประจำวัน