···11+{0 OxCaml Porting Workshop: From OCaml Library Code to OxCaml-Compatible Code}
22+33+@x-ocaml.universe ./universe-oxcaml
44+@x-ocaml.worker ./universe-oxcaml/worker.js
55+66+This page is a hands-on tutorial for common migration patterns seen in real
77+ports from the OxCaml opam overlay (e.g. [re], [uutf], [zarith], [dune],
88+[ppxlib], [notty-community]).
99+1010+{1 How to use this page}
1111+1212+- Read each pattern.
1313+- Run the example cells.
1414+- Edit the {e exercise} cells and re-run.
1515+- Compare with the {e test} cells.
1616+1717+{1 Pattern 1: Eta-expansion of partial applications}
1818+1919+A frequent OxCaml fix is turning a stored partial application into an explicit
2020+function value.
2121+2222+{2 Before (classic style)}
2323+2424+{@ocaml interactive run-on=click[
2525+let add x y = x + y
2626+let add10 = add 10
2727+let () = Printf.printf "add10 7 = %d\n" (add10 7)
2828+]}
2929+3030+{2 After (eta-expanded style)}
3131+3232+{@ocaml interactive[
3333+let add x y = x + y
3434+let add10 y = add 10 y
3535+let () = Printf.printf "add10 7 = %d\n" (add10 7)
3636+]}
3737+3838+{2 Exercise}
3939+4040+Rewrite [mk_printer] to be eta-expanded.
4141+4242+{@ocaml exercise id=eta1[
4343+let with_prefix p s = p ^ s
4444+let mk_printer p = with_prefix p
4545+(* TODO: eta-expand mk_printer *)
4646+]}
4747+4848+{@ocaml test for=eta1[
4949+let f = mk_printer "[INFO] " in
5050+assert (f "ready" = "[INFO] ready")
5151+]}
5252+5353+{1 Pattern 2: Portable API boundaries}
5454+5555+In real ports, many interfaces add [@@ portable] and related mode-safe
5656+annotations on externals and public signatures.
5757+5858+This toy module shows the shape.
5959+6060+{@ocaml interactive[
6161+module Stable_string : sig
6262+ type t = string
6363+ val concat : t -> t -> t
6464+end = struct
6565+ type t = string
6666+ let concat a b = a ^ b
6767+end
6868+6969+let () = print_endline (Stable_string.concat "mode-" "safe")
7070+]}
7171+7272+In real libraries this often appears on externals and mli declarations
7373+(e.g. [re], [spawn], [uutf]).
7474+7575+{2 Exercise}
7676+7777+Separate API boundary from implementation for later portability annotation work.
7878+7979+{@ocaml exercise id=portable_api[
8080+module type S = sig
8181+ type t
8282+ val make : string -> t
8383+ val show : t -> string
8484+end
8585+8686+module M : S = struct
8787+ type t = string
8888+ let make s = s
8989+ let show t = t
9090+end
9191+]}
9292+9393+{@ocaml test for=portable_api[
9494+let v = M.make "ok" in
9595+assert (M.show v = "ok")
9696+]}
9797+9898+{1 Pattern 3: [local_] parameters and non-escaping use}
9999+100100+In OxCaml ports, [local_] is used where values must not escape their region.
101101+102102+{@ocaml interactive[
103103+let sum_pair (p @ local) =
104104+ let a, b = p in
105105+ a + b
106106+107107+let () =
108108+ let p = (20, 22) in
109109+ Printf.printf "sum_pair = %d\n" (sum_pair p)
110110+]}
111111+112112+Real examples: [uutf] callback folder types, [zarith] conversion APIs.
113113+114114+{2 Exercise}
115115+116116+Implement a local consumer that does not let its argument escape.
117117+118118+{@ocaml exercise id=local1[
119119+let use_point (p @ local) =
120120+ let x, y = p in
121121+ (* TODO: return Manhattan norm *)
122122+ x + y
123123+]}
124124+125125+{@ocaml test for=local1[
126126+assert (use_point (3, 4) = 7)
127127+]}
128128+129129+{1 Pattern 4: Heap-mutable vs stack-local style}
130130+131131+A common migration theme is reducing shared mutable state and making data flow
132132+more explicit.
133133+134134+{@ocaml interactive[
135135+let sum_ref n =
136136+ let total = ref 0 in
137137+ for i = 1 to n do
138138+ total := !total + i
139139+ done;
140140+ !total
141141+142142+let sum_mutable n =
143143+ let mutable total = 0 in
144144+ for i = 1 to n do
145145+ total <- total + i
146146+ done;
147147+ total
148148+149149+let () =
150150+ Printf.printf "sum_ref 10 = %d\n" (sum_ref 10);
151151+ Printf.printf "sum_mutable 10 = %d\n" (sum_mutable 10)
152152+]}
153153+154154+Real-world analogues include [re] and [notty-community], where updates can be
155155+much deeper (atomics, immutable arrays, lock-free structures).
156156+157157+{1 Pattern 5: Runtime-aware compatibility shims}
158158+159159+Some ports add runtime4/runtime5 compatibility shims.
160160+161161+Toy shape:
162162+163163+{@ocaml interactive[
164164+module Runtime = struct
165165+ let is_multicore = false
166166+ let recommended_domains () = if is_multicore then 8 else 1
167167+end
168168+169169+let () =
170170+ Printf.printf "recommended domains: %d\n" (Runtime.recommended_domains ())
171171+]}
172172+173173+Real examples: [backoff], [re].
174174+175175+{1 Pattern 6: PPX / AST-heavy libraries need dedicated migration}
176176+177177+If your package pattern-matches deeply on Parsetree nodes, expect a larger
178178+migration (as seen in [ppxlib], [sedlex], [gen_js_api], [lwt_ppx]).
179179+180180+This workshop does not emulate full AST migration, but the practical takeaway
181181+is:
182182+183183+- budget extra time,
184184+- upgrade wildcard matches and constructors carefully,
185185+- isolate AST adapters in one place.
186186+187187+{1 Migration checklist (copy/paste)}
188188+189189+{@ocaml interactive run-on=click[
190190+let checklist = [
191191+ "1. Build under OxCaml and collect first errors";
192192+ "2. Apply eta-expansion fixes";
193193+ "3. Add/adjust portable API boundaries";
194194+ "4. Introduce local_/global_ where lifetimes matter";
195195+ "5. Audit mutable shared state";
196196+ "6. Add runtime compatibility shims if needed";
197197+ "7. For PPX/AST code: do focused migration pass";
198198+] in
199199+List.iter print_endline checklist
200200+]}
201201+202202+{1 Next step}
203203+204204+If you want, create a package-specific workshop page by copying this file and
205205+replacing toy examples with snippets from your real library diff.
+162
doc/demo7_oxcaml_porting_real.mld
···11+{0 OxCaml Porting: Real Snippets from Real Libraries}
22+33+@x-ocaml.universe ./universe-oxcaml
44+@x-ocaml.worker ./universe-oxcaml/worker.js
55+66+This workshop uses real migration snippets taken from the OxCaml opam overlay.
77+Each section links to the exact patch file.
88+99+{1 Notes}
1010+1111+- Cells are runnable where practical.
1212+- Some snippets are simplified for teaching.
1313+- For library-dependent examples, use [#require "..."] first.
1414+1515+{1 1) Eta-expansion (Dune ecosystem)}
1616+1717+Real patch:
1818+{{:https://github.com/oxcaml/opam-repository/blob/main/packages/dune/dune.3.21.0%2Box/files/oxcaml-dune.patch}dune.3.21.0+ox / oxcaml-dune.patch}
1919+2020+Core change pattern from the patch:
2121+2222+{[
2323+- let unlink_exn = if Stdlib.Sys.win32 then win32_unlink else Unix.unlink
2424++ let unlink_exn = if Stdlib.Sys.win32 then win32_unlink else fun s -> Unix.unlink s
2525+2626+- let create_process = ... Unix.create_process prog args
2727++ let create_process stdin stdout stderr = ... Unix.create_process prog args stdin stdout stderr
2828+]}
2929+3030+Runnable miniature:
3131+3232+{@ocaml[
3333+let unlink_like win32_unlink is_win =
3434+ if is_win then win32_unlink else fun s -> "unlink " ^ s
3535+3636+let () = print_endline (unlink_like (fun s -> "win-unlink " ^ s) false "tmp.txt")
3737+]}
3838+3939+{1 2) [local_] callback shape (uutf)}
4040+4141+Real patch:
4242+{{:https://github.com/oxcaml/opam-repository/blob/main/packages/uutf/uutf.1.0.3%2Box/files/uutf-locals.patch}uutf.1.0.3+ox / uutf-locals.patch}
4343+4444+Core change pattern from the patch:
4545+4646+{[
4747+- type 'a folder = 'a -> int -> [ `Uchar of Uchar.t | `Malformed of string ] -> 'a
4848++ type 'a folder = 'a -> local_ (int -> [ `Uchar of Uchar.t | `Malformed of string ] -> 'a)
4949+5050+- val fold_utf_8 : ... -> 'a folder -> 'a -> string -> 'a
5151++ val fold_utf_8 : ... -> local_ 'a folder -> 'a -> string -> 'a
5252+]}
5353+5454+Pedagogical simplified version:
5555+5656+{@ocaml[
5757+type 'a folder = 'a -> local_ (int -> char -> 'a)
5858+5959+let fold_chars (f : local_ 'a folder) acc s =
6060+ let rec go i a =
6161+ if i = String.length s then a
6262+ else go (i + 1) (f a i s.[i])
6363+ in
6464+ go 0 acc
6565+6666+let () =
6767+ let count acc _i _c = acc + 1 in
6868+ Printf.printf "len=%d\n" (fold_chars count 0 "oxcaml")
6969+]}
7070+7171+{1 3) [global_] fields + local comparators (zarith)}
7272+7373+Real patch:
7474+{{:https://github.com/oxcaml/opam-repository/blob/main/packages/zarith/zarith.1.12%2Box/files/zarith-local.patch}zarith.1.12+ox / zarith-local.patch}
7575+7676+Core change pattern from the patch:
7777+7878+{[
7979+- type t = { num: Z.t; den: Z.t }
8080++ type t = { global_ num: Z.t; global_ den: Z.t }
8181+8282+- let compare x y = ...
8383++ let compare__local (local_ x) (local_ y) = ...
8484++ let compare x y = compare__local x y
8585+]}
8686+8787+Teaching-scale analogue:
8888+8989+{@ocaml[
9090+type frac = { global_ num : int; global_ den : int }
9191+9292+let compare__local (local_ a) (local_ b) = compare (a.num * b.den) (b.num * a.den)
9393+let compare_frac a b = compare__local a b
9494+9595+let () =
9696+ let a = { num = 1; den = 2 } in
9797+ let b = { num = 2; den = 3 } in
9898+ Printf.printf "compare_frac=%d\n" (compare_frac a b)
9999+]}
100100+101101+{1 4) Portability + runtime compatibility shims (re/backoff family)}
102102+103103+Representative sources:
104104+105105+- {{:https://github.com/oxcaml/opam-repository/blob/main/packages/re/re.1.14.0%2Box/files/re%2Blib%2Bimport.ml.patch}re.1.14.0+ox / import shim}
106106+- {{:https://github.com/oxcaml/opam-repository/blob/main/packages/backoff/backoff.0.1.1%2Box/files/backoff.patch}backoff.0.1.1+ox / runtime shim}
107107+108108+Typical shape: runtime probing + portability-safe fallback.
109109+110110+{@ocaml[
111111+module Runtime = struct
112112+ let runtime5 = false
113113+ let recommended_domain_count () = if runtime5 then 8 else 1
114114+end
115115+116116+let () = Printf.printf "domains=%d\n" (Runtime.recommended_domain_count ())
117117+]}
118118+119119+{1 5) Optional [#require] cells for real libraries}
120120+121121+Use these when your universe includes the package.
122122+123123+{@ocaml run-on=click[
124124+#require "re";;
125125+let r = Re.(compile (seq [str "ox"; str "caml"]))
126126+let () = Printf.printf "re ok: %b\n" (Re.execp r "oxcaml")
127127+]}
128128+129129+{@ocaml run-on=click[
130130+#require "uutf";;
131131+let n = Uutf.String.fold_utf_8 (fun acc _ _ -> acc + 1) 0 "hello"
132132+let () = Printf.printf "uutf count=%d\n" n
133133+]}
134134+135135+{1 6) Exercise: classify a snippet}
136136+137137+Given a change, classify it as one of:
138138+139139+- eta-expansion
140140+- portability annotation
141141+- local/global lifetime annotation
142142+- runtime shim
143143+144144+{@ocaml exercise id=classify1[
145145+let classify_change s =
146146+ if String.contains s '@' then "portability annotation"
147147+ else if String.contains s '_' then "eta-expansion"
148148+ else "runtime shim"
149149+]}
150150+151151+{@ocaml test for=classify1[
152152+assert (classify_change "let f x = g a x" = "eta-expansion")
153153+]}
154154+155155+{1 7) Where to browse more real patches}
156156+157157+- OxCaml opam overlay packages directory:
158158+ {{:https://github.com/oxcaml/opam-repository/tree/main/packages}github.com/oxcaml/opam-repository/packages}
159159+- Deep taxonomy notes generated locally:
160160+ [/cache/jons-agent/oxcaml-research/oxcaml-opam/TAXONOMY.md]
161161+- Deep fork+portability audit:
162162+ [/cache/jons-agent/oxcaml-research/oxcaml-opam/DEEP-DIVE.md]
+18
doc/dune
···11(documentation
22 (package odoc-interactive-extension))
33+44+; Build a jtw universe with yojson for the demo pages.
55+; The universe directory contains worker.js, findlib_index.json,
66+; and lib/ with CMIs, META files, and .cma.js archives.
77+;
88+; Usage:
99+; dune build odoc-interactive-extension/doc/universe
1010+;
1111+; After building, deploy to the doc HTML output (see below).
1212+(rule
1313+ (targets
1414+ (dir universe))
1515+ (action
1616+ (run jtw opam -o universe yojson)))
1717+1818+; After building, deploy to the doc HTML output:
1919+; cp -r _build/default/odoc-interactive-extension/doc/universe \
2020+; _build/default/_doc/_html/odoc-interactive-extension/