OCaml 选择部分应用程序参数

OCaml choose partial application parameter

我正在使用 OCaml 发出 http 请求。为了构造 headers,我得到了以下工作代码

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 *)

然而,第 2 行和第 3 行冒犯了我(也许因为我对 OCaml 很陌生)。

所以我认为这样的事情会起作用,并且不会冒犯我脆弱的情感。

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

但是编译失败,因为 Header.add 的第一个 arg 应该是 Header.t 类型,我提供了一个字符串。是否有语言功能可以选择部分应用哪些参数?我已经看到 flip 可以在 Jane Street core 和 Batteries 中找到,这可以让我使用更好的语法;但是,如果我理解正确,这将翻转所有参数,我最终将首先传递值而不是键。

有什么方法可以选择部分应用哪个参数?例如,在 Scala 中,我们可以使用 '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. 

感谢阅读。

However, lines 2 and 3 offend me

我相信

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

比您的代码更具可读性。否则,使用不同的名称,例如

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

不知道您使用的是哪个 HTTP 库,我无法解释输入错误。

您是否考虑过使用 Ocsigen 框架?

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

也许labels, or classes, or functors

首先,这些行不应该真的冒犯你,这段代码没有任何问题,你只是在提炼 header 的值。想象它是一个价值变化的历史。给同一个实体起不同的名字通常是个坏主意,因为它会混淆(阅读你的代码的人可能会假设你想要你的值的 non-linear 历史)并且是 error-prone(你可能不小心引用了错误的实体版本)。

话虽如此,当您有一长串此类作业时,它会变得很烦人(至少因为它的重复性)。在那种情况下,我通常会即时引入一个小型 DSL,例如

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

现在我们可以分配几个属性,例如,

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

我们现在有了一个函数列表,我们可以很容易地将它应用到我们的header,

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

或者,如果我们将所有内容放在一起,

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

在此特定示例中,您可以仅使用对列表而不是函数,例如,

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

但是使用函数的方法效果更好,因为它可以分配不同类型的值,只要最后您可以将其细化为 header -> header.

类型的函数

作为奖励曲目,我们刚刚实现的称为 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' _;

当然,使用fun,例如

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

或者,如果我们从 Scala 翻译,那么

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

将会

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

或者,更简短地说,

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

您似乎在使用 Cohttp 模块。您可以定义一个辅助函数 header,它从 key/value 对的列表中构建 Header.t

# 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>

这是现有 to_list 函数的逆向函数(也许它以不同的名称存在,我找不到它)。然后就可以这样使用了:

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