在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>
为什么在这种情况下行为不同,只有括号()
工作?
其中只有一个实际上是内置函数应用程序语法:您调用()
的语法。(请注意,它实际上不需要任何括号,只需并列即可。所有 Haskell 表达式最终都归结为这种语法;中缀运算符样式只是它的语法糖。您尝试过脱糖的例子如下:
〖 〗: 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])
≡ 〖 〗
>(.)
是一个函数组合运算符,而不是函数应用程序。(.)
的优先级为 9,函数应用甚至更高。因此,在第二个示例中,length [1..10]
首先计算Int
类型10
:
fromIntegral . length [1..10]
减少为:
fromIntegral . 10
但是(.)
的类型(b -> c) -> (a -> b) -> a -> c
,即Int
和(a -> b)
不匹配。