OCaml choose partial application parameter

Viewed 117

I am making an http request using OCaml. To construct headers I have ended up with the following working code

let body =
  let headers = Header.init () in
  let headers =  Header.add headers "foo" "bar" in
  let uri = Uri.of_string "https://www.example.com/" in
  Client.get ~headers:headers uri >>= fun (resp, body) ->
  (* rest of client code irrelevant *)

However, lines 2 and 3 offend me (perhaps as I am very new to OCaml).

So I thought something like this would work, and be less offensive to my delicate sensibilities.

  let headers = Header.init |> Header.add "foo" "bar" in

However this fails to compile as the first arg for Header.add is expected to be of type Header.t and I provide a string. Is there a language feature to choose which args to partially apply? I have seen flip that can be found in Jane Street core and Batteries which would let me use the nicer syntax; however if I understand correctly this will flip all args and I will end up passing the value first instead of the key.

Is there any way to choose which arg to partially apply? For example in Scala we can produce a function using the 'placeholder' _;

val f1 = (v1: String, v2: String, v3: String) => 42
val f2 = f1("foo", _, "baz") // f2 is a String => Int
List("a", "b", "c").map(f1(_, "bar", "baz")) // this is fine. 

Thanks for reading.

3 Answers

However, lines 2 and 3 offend me

I believe that

let headers =  Header.add (Header.init ()) "foo" "bar" in

is more readable than your code. Otherwise, use different names, e.g.

let bareheaders = Header.init () in
let headers =  Header.add bareheaders "foo" "bar" in

Without knowing which HTTP library you use, I cannot explain the typing errors.

Did you consider using the Ocsigen framework?

Is there a language feature to choose which args to partially apply?

Maybe labels, or classes, or functors

First of all, these lines shouldn't really offend you, there is nothing wrong with this code, you're just refining the value of header. Imagine it is a history of the changes of the value. It is usually a bad idea to give different names to the same entity as it confusing (people who read your code may assume that you want the non-linear history of your value) and is error-prone (you can accidentally reference the wrong version of the entity).

With that said, when you have a long sequence of such assignments it becomes annoying (at least for its repetitiveness). In that case, I usually introduce a small DSL on the fly, e.g.,

let (:=) property value header = Header.add header property value

Now we can assign several properties, e.g.,

let setup = [
   "foo" := "bar";
   "joe" := "doe";
   ...
]

We now have a list of functions, and we can easily apply it to our header, with

let init_header cmds = List.fold cmds ~init:(Header.init ()) ~f:(|>)

Or, if we will put everything together,

let body = 
  let header = init_header [
   "foo" := "bar";
   "joe" := "doe";
  ] in
  ...

In this particular example, you can use just a list of pairs, instead of functions, e.g.,

 let header = init_header [
   "foo", "bar";
   "joe", "doe";
  ] in

But the approach with functions works better as it enables assigning values of different types, as long as at the end you can refine it to a function of type header -> header.

And as a bonus track, what we have just implemented is called a Writer monad :)

P.S.

Is there any way to choose which arg to partially apply? For example in Scala we can produce a function using the 'placeholder' _;

Sure, use fun, e.g.,

  let headers = 
    Header.init () |> fun hdr -> 
    Header.add hdr "foo" "bar" |> fun hdr -> 
    Header.add hdr "joe" "doe" |> fun hdr ->
    ... in

or, if we will translate from Scala, then

val f2 = f1("foo", _, "baz")

will be

let f2 = fun x -> f1 "foo" x "baz"

or, even more shortly,

let f2 x = f1 "foo" x "baz"

It looks like you are using the Cohttp module. You could define an auxiliary function header that builds a Header.t from a list of key/value pairs:

# let header = List.fold_left (fun h x -> (Cohttp.Header.add h (fst x) (snd x)))
                              (Cohttp.Header.init ());;
val header : (string * string) list -> Cohttp.Header.t = <fun>

This is the reverse of the existing to_list function (maybe it exists under a different name, I couldn't find it). Then you can use it as follows:

# Cohttp.Header.to_list (header [("h1","a");("h2","b")]);;
- : (string * string) list = [("h1", "a"); ("h2", "b")]
Related