Last active
August 16, 2025 18:16
-
-
Save shonfeder/a87d7d92626be06d17d2e795c6481a1e to your computer and use it in GitHub Desktop.
"Dependency injection" using OCaml 5's effect system: example for https://news.ycombinator.com/item?id=44896063
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module EffEx = struct | |
(** Register our new effects *) | |
type _ Effect.t += | |
| Print_endline : string -> unit Effect.t | |
| Read_line : string Effect.t | |
(** helpers to perform the effects *) | |
let println s = Effect.perform (Print_endline s) | |
let readln () = Effect.perform Read_line | |
(** A computation is just a function from the unit type to some derived | |
result*) | |
type 'result computation = unit -> 'result | |
(** An effect handler that handles our effects with normal I/0 operations *) | |
let io_handler (progn : 'a computation): 'a computation = | |
fun () -> | |
match progn () with | |
| result -> | |
(* No effects to handle *) | |
result | |
| effect Print_endline s, k -> | |
(* Actually print the string *) | |
Printf.printf "%s\n" s; | |
(* Continue the computation *) | |
Effect.Deep.continue k () | |
| effect Read_line, k -> | |
(* Actually read a line *) | |
let s = read_line () in | |
(* Continue the computation with the string read *) | |
Effect.Deep.continue k s | |
(** An effect handler that just handles prints, by instead gathering them into a list *) | |
let mocked_print_handler (progn : 'a computation) : (string list * 'a) computation = | |
fun () -> | |
match progn () with | |
| result -> | |
(* No outputs left to gather once we have computed the result *) | |
([], result) | |
| effect Print_endline s, k -> | |
(* Keep running the computation to gather all the outputs *) | |
let outputs, result = Effect.Deep.continue k () in | |
(* Add the intended output to our collection *) | |
s :: outputs, result | |
(** An effect handler that just handles reads, by instead taking them from a supplied list *) | |
let mocked_read_handler (inputs : string list) (progn : 'a computation): 'a computation = | |
fun () -> | |
let input_q = ref inputs in | |
match progn () with | |
| result -> result | |
| effect Read_line, k -> | |
match !input_q with | |
| [] -> invalid_arg "Not enough inputs" | |
| s :: inputs' -> | |
input_q := inputs'; | |
Effect.Deep.continue k s | |
(** Compose a mock for both reads and prints *) | |
let mocked_handler inputs (progn : unit -> 'a) : (string list * 'a) computation = | |
mocked_print_handler | |
@@ mocked_read_handler ["first input"; "second input"] | |
@@ progn | |
end | |
(** A normal, direct-style program phat performs our new effects *) | |
let program () = | |
let open EffEx in | |
println "foo"; | |
println (readln ()); | |
let s = readln () in | |
println "read something"; | |
println "done"; | |
s | |
let () = | |
Printf.printf ">> Run the program with normal io operation and interactive input:\n"; | |
EffEx.io_handler program () | |
|> Printf.printf "result read: %s\n\n" | |
let () = | |
Printf.printf ">> Run the program with mocked operations:\n"; | |
let outputs, result = EffEx.mocked_handler ["first input"; "second input"] program () in | |
assert (outputs = ["foo"; "first input" ;"read something"; "done"]); | |
assert (result = "second input"); | |
Printf.printf "Tests passed\n\n" | |
let () = | |
Printf.printf ">> Run the program with mocked input but normal output:\n"; | |
let program' = | |
EffEx.io_handler | |
@@ EffEx.mocked_read_handler ["first input"; "second input"] | |
@@ program | |
in | |
Printf.printf "result read: %s\n\n" (program' ()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here an example of running the program: