rahilb
rahilb

Reputation: 716

OCaml choose partial application parameter

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.

Upvotes: 2

Views: 356

Answers (3)

coredump
coredump

Reputation: 38799

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")]

Upvotes: 0

ivg
ivg

Reputation: 35210

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"

Upvotes: 2

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

Upvotes: 2

Related Questions