Just reinvented OOP

let ( .![] ) obj f = f obj

type person = { id : int; name : string }

let id { id; _ } = id

let bob = { id = 1; name = "Bob" }
let next_id = bob.![id].![succ]

==> 2

8 Likes

Haha, what a coincidence, just did the same very recently while translating a rust library to OCaml: ego/generic.ml at 5daf312f8a444f9abcde5996c671b9282727a972 · Gopiandcode/ego · GitHub

    let eclasses = eg.@[eclasses] in
    let cost_map = Id.Map.create 10 in
    let node_total_cost node =
      let has_cost id = Id.Map.mem cost_map (eg.@[find] id) in
      if List.for_all has_cost (L.children node)
      then let cost_f id = fst @@ Id.Map.find cost_map (eg.@[find] id) in Some (E.cost cost_f node)
      else None in
      (* ... *)

with .@[] defined as:

  let (.@[]) self fn = fn self [@@inline always]

for bonus(?) points, you can name the first parameter self:

  let add_enode self (node: Id.t L.shape) =
    let node = self.@[canonicalise] node in
    (* ... *)

I don’t normally write code like this in OCaml, but in this case, it made porting from rust easier, because the code mostly looked the same.

3 Likes

Huh, interesting! I made mine mostly as a ‘Lol I can’t believe I can do this’ kind of thing, although I did make a couple of useful Yojson helper ops that let me do this:

match json.$["reasons"].@[0].$["message"] with
| `String message -> Error (`Msg message)
| _ -> Error (`Msg "Failed to get JSON decode error reason")
4 Likes

You can use the multiple-indexing syntax to implement slicing (well, technically subs) sugar:

let (.:[;..]) s = function
  | [|start; finish|] -> String.sub s start (finish - start)
  | _ -> raise (Invalid_argument "slice takes exactly two indexes")
# "hello world".:[1;5];;
- : string = "ello"

The new indexing syntax is quite versatile :>

9 Likes

Yeah, same - normally I just use it for simplifying lookups:

    let (.@[]) vl key = List.assoc key vl 

I guess that’s the idiomatic usage probably - feels a bit more proper than using them to implement a sort of OOP.

3 Likes

Oh wow, this is perfect! brb, off to reimplement the python slicing semantics in OCaml:

let (.@[;..]) ls = function[@warning "-8"]
  | [| start; -1 |] ->
    List.to_iter ls
    |> Iter.zip_i
    |> Iter.drop_while (Pair.fst_map ((>) start))
    |> Iter.map snd
  | [| start; finish |] ->
    List.to_iter ls
    |> Iter.zip_i
    |> Iter.drop_while (Pair.fst_map ((>) start))
    |> Iter.take_while (Pair.fst_map ((>) finish))
    |> Iter.map snd
  | [| start; finish; step |] ->
    List.to_iter ls
    |> Iter.zip_i
    |> Iter.drop_while (Pair.fst_map ((>) start))
    |> Iter.take_while (Pair.fst_map ((>) finish))
    |> Iter.filter (Pair.fst_map (fun ind -> (ind - start) mod step = 0))
    |> Iter.map snd
5 Likes

Wow! And to think I was thinking of asking the OCaml devs to implement it like F# did! :smiley:

This reminds me, I once cooked up this usage with lenses:

#require "lens.ppx_deriving";;

module Address = struct
  type t = { street : string; city : string }
  [@@deriving lens]
end

module Person = struct
  type t = { id : string; name : string; address : Address.t }
  [@@deriving lens]
end

let ( /: ) first second = Lens.compose second first
let ( .:[] ) obj lens = lens.Lens.get obj
let ( .:[]<- ) obj lens value = lens.Lens.set value obj

let bob = {
  Person.id = "1";
  name = "Bob";
  address = {
    Address.street = "1 Way St";
    city = "Cityville";
  };
}

let bob2 = bob.:[Person.address /: Address.street] <- "Rodeo Dr"

==>

val bob2 : Person.t = {
  Person.id = "1";
  name = "Bob";
  address = {
    Address.street = "Rodeo Dr";
    city = "Cityville";
  };
}

EDIT: with a little difflist magic, this could probably become just:

let bob2 = bob.:[[Person.address; Address.street]] <- "Rodeo Dr"
5 Likes

There’s something similar in the language already, in the form of an infix operator:

let next_id = bob |> id |> succ
4 Likes