riot-ml / riot

An actor-model multi-core scheduler for OCaml 5 🐫
https://riot.ml
Other
566 stars 36 forks source link

Add an example for Refs #32

Open leostera opened 9 months ago

leostera commented 9 months ago

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.

wonbyte commented 9 months ago

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.

leostera commented 9 months ago

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 :)

wonbyte commented 9 months ago

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)
dmmulroy commented 9 months ago

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

wonbyte commented 9 months ago

Thanks @dmmulroy. Symbol is perfect!

alexeyshch commented 9 months ago

For me, as an Erlang user, Ref is perfect, as it has the same purpose as Erlang's references.