[英]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進行部分函數應用。
然后是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.