riot
riot copied to clipboard
Add an example for Refs
At the moment refs are used in a few places like Mint Tea but there's no good explanation of what they are.
We should add a little example and document them, and maybe consider renaming them to something like Rune
or some word that isn't taken in OCaml (like "ref") is, so we can introduce it as new concept.
Could you point me to an example in Mint Tea? I looked up Ocaml refs and they seem to just be Pointers but when I see Rune
I think of Golang rune
and Code Point.
Here's an example from minttea: https://github.com/leostera/minttea/blob/4f9938c212230898360d009d60689787e51a632f/examples/stopwatch/main.ml#L15-L16
The gist is that a Ref
is unique reference across the entire execution of your program. No two calls to Ref.make ()
will ever return the same 'a Ref.t
.
If you look at how they're implemented, a 'a Ref.t
is really just an int64
(so a potentially really large int) with an associated type parameter.
What's good about them is that if you send them somewhere, and you get it back, then you can be 100% certain that this was the exact same value that you created first.
Which has some cool implications for static typing of values, because if we can guarantee that two refs are the same, then we know that the type parameter they both have must be the same.
We use this so that if we send and receive a message and the refs are the same, we can cast a message type to the expected type. A little dynamic type-checking for when the OCaml type system doesn't help.
Hope this helps!
Renaming to Rune was just a random idea, maybe its enough to document Refs better :)
What about naming it some along the lines of Marker
, which "marks" a unique piece of memory? As someone new to Ocaml seeing:
(** `Ref` here *)
let ref = Riot.Ref.make ()
(** `ref` here *)
let init _ = Command.Seq [ Set_timer (ref, 1.0); Enter_alt_screen ]
it may be somewhat confusing to call it Ref
especially since "casing" matters in Ocaml.
So something like
(** a [Marker] is unique reference across the entire execution of your program.
No two calls to [Marker.make ()] will ever return the same ['a Marker.t]. *)
type 'a t = Marker : int64 -> 'a t [@@unboxed]
let __current__ = Atomic.make 0L
let pp ppf (Marker pid) = Format.fprintf ppf "#Marker<%s>" (Int64.to_string pid)
let rec make () =
let last = Atomic.get __current__ in
let current = last |> Int64.succ in
if Atomic.compare_and_set __current__ last current then Marker last else make ()
let equal (Marker a) (Marker b) = Int64.equal a b
let type_equal : type a b. a t -> b t -> (a, b) Type.eq option =
fun a b ->
match (a, b) with
| Marker a', Marker b' when Int64.equal a' b' -> Some (Obj.magic Type.Equal)
| _ -> None
let is_newer (Marker a) (Marker b) = Int64.compare a b = 1
let hash (Marker a) = Int64.hash a
module Map = Util.Dashmap.Make (struct
type key = unit t
let hash = hash
let equal = equal
end)
This is a similar concept as Symbols
[^1] in the JavaScript world. Names I like for this feature in order of preference:
Symbol
Brand
Rune
[^1]: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol
Thanks @dmmulroy. Symbol
is perfect!
For me, as an Erlang user, Ref
is perfect, as it has the same purpose as Erlang's references.