Reputation: 716
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
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
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
Reputation: 1
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