簡體   English   中英

reasonml中的 - >和|>有什么區別?

[英]What's the difference between -> and |> in reasonml?

一段激烈的谷歌搜索為我提供了一些例子,人們在一個代碼中使用兩種類型的運算符,但通常它們看起來就像兩種方式做一件事,他們甚至有相同的名字

tl; dr:定義的區別是->管道到第一個參數,而|>管道到最后一個。 那是:

x -> f(y, z) <=> f(x, y, z)
x |> f(y, z) <=> f(y, z, x)

不幸的是,有一些細微之處和含義使得這在實踐中變得更加復雜和混亂。 當我試圖解釋它背后的歷史時,請耐心等待。

在管道時代之前

在有任何管道運算符之前,大多數函數式程序員使用“對象”設計大多數函數,該函數作為最后一個參數運行。 這是因為部分函數應用使函數組合變得更容易,並且如果未應用的參數最后,則在curry語言中部分函數應用變得更容易。

嘩眾取寵

在一種curry語言中,每個函數只需要一個參數。 一個看起來帶有兩個參數的函數實際上是一個接受一個參數的函數,但是然后返回另一個接受另一個參數的函數,然后返回實際的結果。 因此這些是等價的:

let add = (x, y) => x + y
let add = x => y => x + y

或者更確切地說,第一種形式只是第二種形式的語法糖。

部分功能應用

這也意味着我們可以通過提供第一個參數來輕松地部分應用函數,這將使它返回一個在生成結果之前接受第二個參數的函數:

let add3 = add(3)
let result = add3(4) /* result == 7 */

如果沒有curry,我們必須將它包裝在一個函數中,這更加麻煩:

let add3 = y => add(3, y)

巧妙的功能設計

現在事實證明,大多數函數都在“主”參數上運行,我們可以稱之為函數的“對象”。 List函數通常在特定列表上運行,例如,不是一次運行(當然,確實也會發生)。 因此,將主要參數放在最后使您可以更輕松地編寫函數。 例如,通過一些設計良好的函數,定義一個函數將可選值列表轉換為具有默認值的實際值列表,就像這樣簡單:

let values = default => List.map(Option.defaultValue(default)))

雖然首先使用“對象”設計的函數需要您編寫:

let values = (list, default) =>
  List.map(list, value => Option.defaultValue(value, default)))

管道時代的曙光(具有諷刺意味的是,它不是管道先發的)

根據我的理解,在F#中玩游戲的人發現了一種常見的管道模式,並認為要么為中間值提供命名綁定,要么使用太多該死的括號以反向順序嵌套函數調用,這很麻煩。 所以他發明了管道前移算子, |> 有了這個,管道可以寫成

let result = list |> List.map(...) |> List.filter(...)

代替

let result = List.filter(..., List.map(..., list))

要么

let mappedList = List.map(..., list)
let result = List.filter(..., mapped)

但這只有在主要參數是最后一個時才有效,因為它依賴於通過currying進行部分函數應用。

然后...... BuckleScript

然后是Bob,他首先創作了BuckleScript,以便將OCaml代碼編譯為JavaScript。 Reason收錄了BuckleScript,然后Bob繼續為BuckleScript創建一個名為Belt的標准庫。 Belt忽略了幾乎所有的東西我已經把主要的論點第一如上所述。 為什么? 這還有待解釋,但我可以收集它主要是因為它對JavaScript開發人員來說更為熟悉1

然而,Bob確實認識到管道操作員的重要性,因此他創建了自己的管道優先運營商, |. ,僅適用於BuckleScript 2 然后原因開發人員認為看起來有點丑陋且缺乏方向,所以他們提出了->運算符,轉換為|. 並且工作完全像它...除了它有不同的優先權,因此不能與其他任何東西好玩。 3

結論

管道優先運算符本身並不是一個壞主意。 但它在BuckleScript和Reason中實現和執行的方式引起了很多困惑。 它有意想不到的行為,鼓勵糟糕的功能設計,除非一個人全力以赴4 ,根據你所調用的功能,在不同的管道操作員之間切換時會產生沉重的認知稅。

因此,如果你需要管道到“對象” - 第一個函數,我建議避免使用管道優先運算符( ->|. ),而是使用帶有占位符參數的管道轉發( |> )(也不包括Reason) ,例如list |> List.map(...) |> Belt.List.keep(_, ...)


1這與類型推斷的交互方式也存在一些微妙的差異,因為類型是從左到右推斷出來的,但這對IMO的風格都沒有明顯的好處。

2因為它需要語法轉換。 與管道前進不同,它不能僅作為普通的操作員實現。

3例如, list |> List.map(...) -> Belt.List.keep(...) 無效,正如您所期望的那樣

4這意味着無法使用幾乎所有在管道優先運算符存在之前創建的庫,因為這些庫當然是在考慮原始管道運算符的情況下創建的。 這有效地將生態系統分為兩部分。

|>通常稱為“管道前進”。 它是一個輔助功能,用於更廣泛的OCaml社區,而不僅僅是ReasonML。 它將左側的參數“注入”作為右側函數的最后一個參數:

0 |> f       == f(0)
0 |> g(1)    == g(1, 0)
0 |> h(1, 2) == h(1, 2, 0)
// and so on

->被稱為'pipe-first',它是一個新的語法糖,它將左邊的參數注入到右邊的函數數據構造函數的第一個參數位置:

0 -> f       == f(0)
0 -> g(1)    == g(0, 1)
0 -> h(1, 2) == h(0, 1, 2)
0 -> Some    == Some(0)

請注意->特定於BuckleScript,即編譯為JavaScript時。 在編譯為本機時它不可用,因此不可移植。 更多細節: https//reasonml.github.io/docs/en/pipe-first

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM