在F#中,使用管道转发运算符|>
非常常见。但是,在Haskell中,我只看到过正在使用的函数组合(.)
。我知道它们是related,但有没有语言原因在Haskell中没有使用管道转发,还是其他的呢?
答案 0 :(得分:82)
在F#(|>)
中,由于从左到右的类型检查很重要。例如:
List.map (fun x -> x.Value) xs
通常不会进行类型检查,因为即使xs
的类型已知,lambda的参数x
的类型在类型检查器看到它时也是未知的,所以它不知道如何解决x.Value
。
相比之下
xs |> List.map (fun x -> x.Value)
可以正常使用,因为xs
的类型会导致知道x
的类型。
由于名称解析涉及x.Value
等结构,因此需要从左到右的类型检查。 Simon Peyton Jones编写了proposal来为Haskell添加类似的名称解析,但他建议使用局部约束来跟踪类型是否支持特定操作。因此,在第一个示例中,x
需要Value
属性的要求将继续执行,直到看到xs
并且可以解决此要求。但这确实使类型系统复杂化。
答案 1 :(得分:60)
我有点投机......
文化:我认为|>
是F#“文化”中的重要运算符,也许与Haskell的.
类似。 F#有一个函数组合运算符<<
,但我认为F#社区比Haskell社区使用points-free style更少。
语言差异:我不太了解要比较的两种语言,但是推广let-bindings的规则可能会有很大不同,从而影响到这一点。例如,我知道在F#中有时写作
let f = exp
将无法编译,您需要明确的eta转换:
let f x = (exp) x // or x |> exp
使其编译。这也引导人们远离无点/组合风格,并走向流水线风格。此外,F#类型推断有时需要流水线操作,因此左侧会显示已知类型(请参阅here)。
(就个人而言,我觉得无点的风格难以理解,但我认为每一个新的/不同的东西似乎都不可读,直到你习惯了它。)
我认为两种语言都可能存在,历史/文化/事故可能会定义为什么每个社区都选择不同的“吸引者”。
答案 2 :(得分:42)
更多猜测,这一次来自Haskell占主导地位......
($)
是(|>)
的翻转,当你无法编写无点代码时,它的使用非常普遍。因此,(|>)
未在Haskell中使用的主要原因是($)
已经占据了它的位置。
另外,从F#的一些经验来看,我认为(|>)
在F#代码中非常流行,因为它类似于OO的Subject.Verb(Object)
结构。由于F#旨在实现平滑的功能/ OO集成,Subject |> Verb Object
对于新的功能程序员来说是一个非常平滑的过渡。
就个人而言,我也喜欢从左到右思考,所以我在Haskell中使用(|>)
,但我认为很多其他人都没有。
答案 3 :(得分:30)
我认为我们混淆了事情。 Haskell(.
)相当于F#(>>
)。不要与F#(|>
)混淆,后者只是反函数应用程序,就像Haskell($
) - 反转:
let (>>) f g x = g (f x)
let (|>) x f = f x
我相信Haskell程序员经常使用$
。也许不像F#程序员倾向于使用|>
那样频繁。另一方面,一些F#家伙使用>>
达到了荒谬的程度:http://blogs.msdn.com/b/ashleyf/archive/2011/04/21/programming-is-pointless.aspx
答案 4 :(得分:19)
如果您想在Haskell中使用F#的|>
,则Data.Function中的&
运算符(自base 4.8.0.0
起)。
答案 5 :(得分:16)
有些人也在Haskell中使用从左到右(消息传递)的方式。例如,请参阅Hackage上的mps库。一个例子:
euler_1 = ( [3,6..999] ++ [5,10..999] ).unique.sum
我认为这种风格在某些情况下看起来不错,但是它更难阅读(需要知道库及其所有运算符,重新定义的(.)
也令人不安)。
基础包的一部分Control.Category中还有从左到右和从右到左的合成运算符。分别比较>>>
和<<<
:
ghci> :m + Control.Category
ghci> let f = (+2) ; g = (*3) in map ($1) [f >>> g, f <<< g]
[9,5]
有时候选择从左到右的构图是有充分理由的:评估顺序遵循阅读顺序。
答案 6 :(得分:15)
我已经看到>>>
用于flip (.)
,我经常使用它,特别是对于从左到右最好理解的长链。
>>>
实际上来自Control.Arrow,并且不仅仅是函数。
答案 7 :(得分:13)
除了风格和文化,这归结为优化纯语言或不纯代码的语言设计。
|>
运算符在F#中很常见,主要是因为它有助于隐藏以主要不纯的代码出现的两个限制:
请注意,OCaml中不存在前一个限制,因为子类型是结构而不是名义,因此随着类型推断的进展,结构类型很容易通过统一来细化。
Haskell采取了不同的权衡,选择专注于主要纯粹的代码,可以解除这些限制。
答案 8 :(得分:1)
这是我尝试Haskell的第一天(在Rust和F#之后),我能够定义F#&#39; s |&gt;操作者:
(|>) :: a -> (a -> b) -> b
(|>) x f = f x
infixl 0 |>
似乎有效:
factorial x =
case x of
1 -> 1
_ -> x * factorial (x-1)
main =
5 |> factorial |> print
我敢打赌,Haskell专家可以为您提供更好的解决方案。
答案 9 :(得分:0)
我认为
F#的管道前移运算符( |>
)应该与haskell中的(&)相对。
// pipe operator example in haskell
factorial :: (Eq a, Num a) => a -> a
factorial x =
case x of
1 -> 1
_ -> x * factorial (x-1)
// terminal
ghic >> 5 & factorial & show
如果您不喜欢( &
)运算符,则可以像F#或Elixir一样自定义它:
(|>) :: a -> (a -> b) -> b
(|>) x f = f x
infixl 1 |>
ghci>> 5 |> factorial |> show
为什么infixl 1 |>
?请参阅Data-Function (&)
infixl =中缀+左关联性
infixr =中缀+右关联性
( .
)表示功能组合。在数学中表示(f.g)(x) = f(g(x))。
foo = negate . (*3)
// ouput -3
ghci>> foo 1
// ouput -15
ghci>> foo 5
等于
// (1)
foo x = negate (x * 3)
或
// (2)
foo x = negate $ x * 3
( $
)运算符也在Data-Function ($)中定义。
( .
)用于创建Hight Order Function
或closure in js
。参见示例:
// (1) use lamda expression to create a Hight Order Function
ghci> map (\x -> negate (abs x)) [5,-3,-6,7,-3,2,-19,24]
[-5,-3,-6,-7,-3,-2,-19,-24]
// (2) use . operator to create a Hight Order Function
ghci> map (negate . abs) [5,-3,-6,7,-3,2,-19,24]
[-5,-3,-6,-7,-3,-2,-19,-24]
哇,更少(代码)更好。
|>
和.
ghci> 5 |> factorial |> show
// equals
ghci> (show . factorial) 5
// equals
ghci> show . factorial $ 5
left —> right
和right —> left
之间是不同的。 ⊙﹏⊙|||
|>
和&
比.
因为
ghci> sum (replicate 5 (max 6.7 8.9))
// equals
ghci> 8.9 & max 6.7 & replicate 5 & sum
// equals
ghci> 8.9 |> max 6.7 |> replicate 5 |> sum
// equals
ghci> (sum . replicate 5 . max 6.7) 8.9
// equals
ghci> sum . replicate 5 . max 6.7 $ 8.9
它支持: