Haskell中有不同的函数应用方法,但似乎每个方法都有其特殊性。我想了解$
,.
和()
之间的差异:
Prelude> 100 / fromIntegral( length [1..10] )
10.0
Prelude> 100 / fromIntegral . length [1..10]
<interactive>:193:22: error:
* Couldn't match expected type `a -> Integer'
with actual type `Int'
* Possible cause: `length' is applied to too many arguments
In the second argument of `(.)', namely `length [1 .. 10]'
In the second argument of `(/)', namely
`fromIntegral . length [1 .. 10]'
In the expression: 100 / fromIntegral . length [1 .. 10]
* Relevant bindings include
it :: a -> c (bound at <interactive>:193:1)
Prelude> 100 / fromIntegral $ length [1..10]
<interactive>:194:1: error:
* Non type-variable argument
in the constraint: Fractional (Int -> b)
(Use FlexibleContexts to permit this)
* When checking the inferred type
it :: forall b. (Num b, Fractional (Int -> b)) => b
Prelude>
为什么在这种情况下行为不同,只有括号()
有效?
答案 0 :(得分:4)
其中只有一个是内置的函数应用程序语法:你调用的()
。 (请注意,它实际上并不需要任何括号,只是并列。)所有Haskell表达式最终都归结为这种语法;中缀 - 运算符风格只是它的语法糖。你试过desugar的例子:
〖〗: 100 / fromIntegral( length [1..10] )
≡ (/) 100 (fromIntegral (length [1..10]))
〖〗: 100 / fromIntegral . length [1..10]
≡ (/) 100 ((.) fromIntegral (length [1..10]))
〖〗: 100 / fromIntegral $ length [1..10]
≡ ($) ((/) 100 fromIntegral) (length [1..10])
他们如此不同的原因是中缀优先规则:每个中缀运算符都附带一个固定声明。与此相关的是:
Prelude> :i .
(.) :: (b -> c) -> (a -> b) -> a -> c -- Defined in ‘GHC.Base’
infixr 9 .
Prelude> :i $
($) ::
forall (r :: GHC.Types.RuntimeRep) a (b :: TYPE r).
(a -> b) -> a -> b
-- Defined in ‘GHC.Base’
infixr 0 $
Prelude> :i /
class Num a => Fractional a where
(/) :: a -> a -> a
...
-- Defined in ‘GHC.Real’
infixl 7 /
这告诉我们的是:.
比/
绑定得更紧密,$
的绑定比w . x / y $ z
更紧密。因此,例如((w . x) / y) $ z
被解析为 ($) ((/) ((.) w x) y) z
,这是
()
(函数应用程序本身 - 你调用的那个〖〗 ≡ 100 / (fromIntegral (length [1..10]))
〖〗 ≡ 100 / (fromIntegral . (length [1..10]))
〖〗 ≡ (100 / fromIntegral) $ (length [1..10])
- 总是比任何中缀运算符更紧密地绑定。)
好的,上面的饮食表达看起来令人困惑,所以让我们再次回到糖果状态,但仍然使用明确的括号分组:
$
应该立即明确〖〗不对,不管操作者实际做了什么:你试图用一个函数划分一个数字,这没有意义! .
运算符的低优先级专门用于分隔表达式 - 基本上每当你看到这个运算符时,你可以想象每个操作数都有围绕它的括号。
〖〗看起来很明智,但是它在类型方面没有意义:(length [1..10])
不是函数应用程序操作符,而是函数组合操作符。即它的两个操作数都应该是函数,但是〖′〗: 100 / (fromIntegral . length) [1..10]
≡ 100 / (\x -> fromIntegral (length x)) [1..10]
≡ 100 / fromIntegral (length [1..10])
≡ 〖〗
已经是将函数应用到其(唯一)参数的结果,所以你试图用一个数字来组成一个函数
你实际上可以使用合成运算符编写这个表达式,但是你必须在将中的任何一个应用于参数之前编写函数(并且只将参数应用于合成 - 链):
$
现在和/
一样,它实际上是一个函数应用程序操作符,所以你也可以使用它。只是,您需要正确使用其低固定性,并确保它不会干扰优先级较高的〖′〗: 100 / (fromIntegral $ length [1..10])
≡ 100 / ((fromIntegral) (length [1..10]))
≡ 100 / fromIntegral (length [1..10])
≡ 〖〗
运算符:
{{1}}
答案 1 :(得分:1)
(.)
是一个函数组合运算符,而不是函数 application 。 (.)
定义优先级为9,函数应用程序甚至更高。因此,在您的第二个示例中,length [1..10]
首先评估为10
类型的Int
:
fromIntegral . length [1..10]
简化为:
fromIntegral . 10
但(.)
的类型为(b -> c) -> (a -> b) -> a -> c
。即<{1}}和Int
不匹配。