mattjbray / ocaml-decoders

Elm-inspired decoders for Ocaml
https://mattjbray.github.io/ocaml-decoders/
Other
84 stars 8 forks source link

add `uncons`? #12

Closed c-cube closed 5 years ago

c-cube commented 5 years ago

when encoding sum types into json, sometimes one may do ["foo", 1, ["hello", "world"]] to represent `Foo (1, ["hello"; "world"])). I'm not sure how this is handled by the current decoder API (it's more a tuple than a list).

So would such an operator be relevant?

val uncons : (value * value list) decoder
val uncons2 : (value * value * value list) decoder
val uncons3 : (value * value * value * value list) decoder

(other names might be better)

usage along the following lines:

let decode_foo =
  uncons3 >>= fun (f,x,y,tl) ->
  (string f <*> int y <*> list string <$> return tl) >>= function
  | "foo", x, y, [] -> Foo (x,y)
  | _ -> fail "expected foo"
c-cube commented 5 years ago

maybe it's more like having tuple0, tuple1, tuple2, tuple3, tuple4 for matching lists of exact size with heterogeneous elements, and tuple4_tl : (value * value * value * value * value list) to get the rest of the list (so you can have 10 elements if you chain tuple4_tl twice and then tuple2).

mattjbray commented 5 years ago

So in #7 I ran into the same problem (decoding dune sexps) and went with this:

https://github.com/mattjbray/ocaml-decoders/blob/dfb7e448a423eac648308a941f7d5e5ccd252c12/src/decode.mli#L60-L92

c-cube commented 5 years ago

That seems pretty neat, indeed. Perhaps we could also have exact matchers (i.e. val exact_string : string -> unit decoder and exact_int : int -> unit decoder) that allow you to write:

type t = Foo of bool * int list | Bar of int

let decode_t =
  one_of [
    "foo", (exact_string "foo" >>=:: fun () ->
      bool >>=:: fun x -> list int >>= fun y -> success (Foo(x,y)));
    "bar", (exact_string "bar" >>=:: fun () -> int >>= fun x -> success (Bar x));
  ]
mattjbray commented 5 years ago

Yes that sounds useful. I wonder if an API that re-uses the existing combinators would work, something like:

val literal : eq:('a -> 'a -> bool) -> 'a decoder -> 'a -> unit decoder

let foo_decoder = literal string "foo" ~eq:String.eq
c-cube commented 5 years ago

I'd make eq an optional parameter, I think, but this looks quite nice indeed.

Another point is that instead of one_of one could dispatch the decoder based on the first literal (first, uncons, then decode a string, and then matching over that.) But it can be done in pure OCaml I think:

uncons string >>= function
| "foo", tl -> …
| "bar", tl -> …
| s -> failf "unknown constructor %s" s
mattjbray commented 5 years ago

Yep. A lot of my decoders look like this:

field "tag" string >>= function
| "foo" -> decode_foo >|= fun x -> Foo x
| "bar" -> decode_bar >|= fun y -> Bar y
| _ -> fail "Expected a Foo or a Bar"

Note that you don't need to capture the s in the fail case - decoders will capture the full context and display it in the error message for you.