Haskell中的函数应用以及(),.和$之间的差值



在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)不匹配。

最新更新