Share your crazy OCaml code snippet!

This is a nice bit of code.

Iā€™m donā€™t like the |> operator. Iā€™ve seen it in some programs and Iā€™ve never liked how it was used.

Going just a bit crazyā€¦ Letā€™s write "Hello world!!" to hello.txt. Easy


let ( /> ) str filename = 
  let ch = open_out filename in
    Fun.protect
      ~finally:(fun () -> close_out ch)
      (fun () -> output_string ch str)

"Hello, world!" /> "hello.txt"

But that (fun () -> ..) feels a bit repetitive. Well we can get rid of one.


let ( let$$ ) finally work = 
  Fun.protect ~finally work

let ( /> ) str filename =
  let ch = open_out filename in
  let$$ _ = fun () -> close_out ch in
    output_string ch str
1 Like

I thought this code in dune was quite fun:

The module rec can create the module from the module type automatically, as Ast has no values.

7 Likes

:exploding_head: :exploding_head:

1 Like

In the same idea, I like this line:

It permits to redefine the type t (with an arity) and include the whole implementation to describe an other interface - which supports capabilities. By this way, we donā€™t need to copy/paste the implementation if we want to improve the interface.

4 Likes

considering your nick, I was sure, youā€™re a fan of it.

This is especially useful with first class modules as you can do

module rec M : sig
  module type Map = sig
    type key
    (* this is the bad recursive bit *)
    val clone : unit -> (module M.Map with type key = key)
  end
end = M

This was exactly the reason for the Fun.protect design, and it was nice that we managed to agree on the design principles since as I remember it, it was not at all clear a priori that a consensus could be reached.

Good exception handling is surprisingly involved, my talk at the OCaml workshop on Friday will talk about it.

1 Like

This does not look crazy to me. But I thought the OCaml community preferred to tie the resource acquisition to the destructor initialisation using a higher-order function with_file : string -> (in_channel -> 'a) -> 'a instead of open_in. Is there a benefit to defer (i.e. some additional flexibility)?

Something like defer I would consider a more low-level or on-off approach than a with_file function, which captures a specific use case.

One of my first :camel: OCaml code snippets written a year ago is to get 4 random words from a word-list file without reading it in full and thus having constant complexity with growing list length:

open Bigarray

let rec step arr dx look stop idx =
  match idx = stop with
  | true -> stop
  | _ -> (
      match arr.{idx + look} with
      | '\n' -> idx
      | _ -> step arr dx look stop (idx + dx) )

(* walk back until we hit the begin of the line *)
let back arr idx = step arr (-1) (-1) 0 idx

(* walk forward until we hit the end of the line *)
let forw arr len idx = step arr 1 0 len idx

let line arr len idx =
  let off = back arr idx and pos = forw arr len idx in
  pos - off |> Array1.sub arr off |> Bigstring.to_string

let () =
  Random.self_init ();
  let fd = Unix.openfile "words.txt" [ Unix.O_RDONLY ] 0 in
  let mf = Unix.map_file fd char c_layout false [| -1 |] in
  let arr = array1_of_genarray mf in
  let len = Array1.dim arr in
  (fun _ -> len |> Random.int |> Lines.line arr len)
  |> List.init 4 |> String.concat "-" |> print_endline;
  Unix.close fd

See the complete project at mro/xkcd936: ā™Šļø Mirror of https://code.mro.name/mro/xkcd936 | šŸ« šŸ” Generate word combinations like in xkcd.com/936 - xkcd936 - Codeberg.org

1 Like

Some more ā€œMinistry of Silly Codeā€ stuff

let [@inline] fn f x = fun () -> f x
let ( => ) = fn

let ( /> ) str filename =
  let ch = open_out filename in
  Fun.protect ~finally:(close_out => ch)
    (output_string ch => str)

On a more serious note, if more of the Stdlib API will require a thunk as argument (fun () -> .. ) a more
lightweight way of creating them is kind of missing.

Fun.protect is nice example of where such stuff will be handy, as well as some of the Seq and Option stuff.

I like the idea, but I do not like the operator order. output_string ch => str is difficult for me to understand (the example is trivia here, but this will blow my mind out of this context)

I didnā€™t checked if we have composition issues, but this seem more natural for me reversed:

let [@inline] fn x f = fun () -> f x

1 Like

It will work

let ( /> ) str filename =
  let ch = open_out filename in
    Fun.protect ~finally:(ch => close_out)
      (str => output_string ch)

A prefix operator is also possible. Iā€™m reminded of the lazy operator $ used in Okasakiā€™s PFDS, so we can define an operator ~$ that is some approximation of that:

# let ( ~$ ) f x () = f x ;;
val ( ~$ ) : ('a -> 'b) -> 'a -> unit -> 'b = <fun>

# let f = ~$ Printf.sprintf "delayed print" ;;
val f : unit -> string = <fun>

# f () ;;
- : string = "delayed print"
4 Likes

Iā€™m not sure whether this already exists somewhere, but Iā€™ve never seen it, so here I go.

In the eternal quest of offsetting as many issues from runtime to compile time, Iā€™ve always encoded the fact a list cannot be empty in its type. Historically as something akin to type 'a list_nonempty = 'a * 'a list, which does the job decently: you get a compile time guarantee that the list has at least a head instead of asserting that List.hd is not None at runtime because you ā€œjust know it canā€™t be emptyā€. It also enforces clearly in the signature that an empty list cannot be passed. The drawback being that the type differs from a standard list, so client code must be adapted and becomes a bit more convoluted around the first element, but I had accepted that as the cost of such type safety.

More recently I toyed around the idea of using a GADT to enable using the actual list literal syntax and constructor for pattern matching:

module List_nonempty = struct
  type ('a, 'empty) maybe =
    | ( :: ) :
        'a * ('a, [< `Nonempty | `Empty ]) maybe
        -> ('a, [ `Nonempty ]) maybe
    | [] : ('a, [ `Empty ]) maybe

  let hd = function
    | hd :: _ -> hd

  let tl = function
    | _ :: tl -> tl

  let rec to_list = function
    | [ last ] -> Caml.List.[ last ]
    | hd :: next :: tl -> Caml.List.cons hd (to_list (next :: tl))

  type 'a t = ('a, [ `Nonempty ]) maybe
end

This makes using literal syntax possible, List_nonempty.(hd [0; 1; 2]) has type int as expected, List_nonempty.([ 0 ] |> tl |> hd) is a type error as expected, etc. However the type differs entirely from List.t, while with the previous solution at least the tail was a standard list. This made operating on the rest of the nonempty-list a breeze, and to_list was very efficient (just prepend the head to the tail). Here because the :: constructors differ, to_list allocates an entirely new list, which may be unacceptable performance-wise. I had a mixed feeling about it.

Earlier today however, it struck me that since OCaml has become much better at resolving the correct constructs when it has a full type constraint on an expression, maybe I could get a :: b :: _ to resolve to two different :: constructors in a single expression. Actually it should work out of the box ā€¦ and lo and behold, this completely trivial and much simpler solution entirely does the trick:

module List_nonempty = struct
  type 'a t = ( :: ) : 'a * 'a list -> 'a t

  let map (hd :: tl) ~f = f hd :: List.map ~f tl

  let hd = function
    | hd :: _ -> hd

  let tl = function
    | _ :: tl -> tl

  let to_list = function
    | hd :: tl -> List.cons hd tl
end

This has all the static typing aforementioned properties, while being essentially transparent syntax-wise:

# let l = List_nonempty.[0; 1; 2];;
val l : int List_nonempty.t = List_nonempty.(::) (0, [1; 2])
# List_nonempty.hd l;;
- : int = 0
# List_nonempty.tl l;;
- : int list = [1; 2] (* the tail is a standard list *)
# List_nonempty.(hd (tl l));;
Error: This expression has type int list
       but an expression was expected of type 'a List_nonempty.t

And of_list is trivial and performant.

No more List.hd |> Option.value_exn without significantly complexifying the codebase !

17 Likes

Sometimes, I think a feature-full List_nonempty module would be cool in batteries, or as a standalone library.
It should support most BatList operations (and only have tail rec. code).

1 Like

My chance to plug core_kernel/nonempty_list at master Ā· janestreet/core_kernel Ā· GitHub.

One extra neat trick up its sleeve: it uses the (::) constructor instead of a tuple, so you can write something like:

let x : _ Nonempty_list.t = [ 1; 2; 3 ]
3 Likes

Ah, so it does exist in Core, thanks!

Regarding the :: constructor, that was the final iteration of my post too :slight_smile:

1 Like