Sometimes you get an iter function over something, but you want to plug it into some other interface.
It used to be impossible to convert an iterator into a sequence without putting every element in a datastructure. Now that effects are released it is.
I wrote this a while back, I thought it was fun and maybe useful.
let seq_of_iter : type a. ((a -> unit) -> unit) -> a Seq.t =
fun iter ->
let module E = struct
type _ Effect.t += Yield : a -> unit Effect.t
end in
let open Effect in
let open Effect.Deep in
let continue : a continue ref =
ref
(C
(fun () ->
try_with
(fun () ->
iter (fun v -> Effect.perform (E.Yield v));
None)
()
{
effc =
(fun (type a) (eff : a t) ->
match eff with
| E.Yield v ->
Some
(fun (k : (a, _) continuation) ->
let continue = C (fun () -> continue k ()) in
Some (v, continue))
| _ -> None);
}))
in
Seq.of_dispenser (fun () ->
let (C continue_func) = !continue in
match continue_func () with
| None -> None
| Some (v, new_continue) ->
continue := new_continue;
Some v)
match eff with
| E.Yield v ->
Some
(fun (k : (a, _) continuation) ->
let continue = C (fun () -> continue k ()) in
Some (v, continue))
| _ -> None
I think it should be:
match eff with
| E.Yield v ->
Some
(fun (k : (a, _) continuation) ->
let continue = C (fun () -> continue k ()) in
Some (v, continue))
| e -> Effect.perfom e
When I tested this there was no effect leaked from the iter function.
let invert (type a) ~(iter : (a -> unit) -> unit) : a Seq.t =
let module M = struct
type _ Effect.t += Yield : a -> unit t
end in
let yield v = perform (M.Yield v) in
fun () -> match iter yield with
| () -> Seq.Nil
| effect M.Yield v, k -> Seq.Cons (v, continue k)
I wonder if we could have let type += in the same way that we have let exception, so that we could write:
let invert (type a) ~(iter : (a -> unit) -> unit) : a Seq.t =
let type _ Effect.t += Yield : a -> unit t in
let yield v = perform (M.Yield v) in
fun () -> match iter yield with
| () -> Seq.Nil
| effect M.Yield v, k -> Seq.Cons (v, continue k)
Indeed. One difference is that Standard ML implementations tend to allow references to new types that go out of scope, like this:
let datatype t = T in T end
or this
local
datatype t = T
in
val x = T
end
whereas similar programs in OCaml are rejected; e.g.:
let module M = struct type t = T end in M.T (* invalid *)
or
include struct
open struct type t = T end
let x = T
end (* invalid *)
One nice thing about the restricted let type += over the more general let type construct is that it doesn’t introduce any more of these kinds of scoping issues because it doesn’t create fresh type constructors. It’s just a new way of allocating new data constructors that can’t be named outside the scope, i.e. of generating shared secrets.