函数应用在Haskell和()之间的区别。和$

时间:2018-03-31 09:50:10

标签: function 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>

为什么在这种情况下行为不同,只有括号()有效?

2 个答案:

答案 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不匹配。