【问题标题】:composing functions with higher arity组合具有更高元数的函数
【发布时间】:2013-09-19 22:21:13
【问题描述】:

我不明白用 arity > 1 组合函数。 在 ghci 7.4.1 我输入:

((*).succ) 3 4
> 16

我不完全理解数学转换,但很明显它是一样的

(*) (succ 3) 4

但是当我这样做时:

( (\x y z -> x).(\a b -> a*b) ) 2 3 4 5
> 10
( (\x y z -> y).(\a b -> a*b) ) 2 3 4 5
> No instance for (Num (a0 -> t0))

现在我完全迷路了。谁能解释会发生什么?附言。我知道 haskell 中的所有内容都只有 1 个参数,但它并没有真正帮助我:)

【问题讨论】:

  • 关于 Haskell 的另一个小知识是函数应用程序是左关联的。
  • 有关(.).(.) 的详细信息,请参阅this SO answer,这是一个用于组合多参数函数的有趣习语。

标签: haskell functional-programming higher-order-functions


【解决方案1】:

这样算:

(f . g) x = f (g x)
(f . g) x y = f (g x) y -- applying y

然后用(*)替换f,用succ替换g,用它们的值替换x和y:

((*) . succ) 3 4 = (*) (succ 3) 4
                 = (*) 4 4
                 = 16

【讨论】:

    【解决方案2】:

    当你编写(\x y z -> x) . (\a b -> a*b) 时,你编写了以下签名的函数:

    (\x y z -> x) :: a -> b -> c -> a
    (\a b -> a*b) :: Num a => a -> a -> a
    

    (.)的签名是

    (.) :: (b -> c) -> (a -> b) -> a -> c
    

    现在,让我们把这些东西放在一起,为这些函数获取一个专用版本的签名 (.)

    1. 首先我们将(.)签名的(b -> c)部分专门化为a -> b -> c -> a

      (b -> (z -> x -> b)) -> (a -> b) -> a -> (z -> x -> b)
      

      明白了吗? c 变为 (z -> x -> b)

    2. 现在让我们将(.)签名的(a -> b)部分特化为a -> a -> a

      ((a -> a) -> (z -> x -> (a -> a))) -> (a -> (a -> a)) -> a -> (z -> x -> (a -> a))
      

      b 变为 (a -> a)

    3. 现在让我们去掉多余的大括号:

      ((a -> a) -> z -> x -> a -> a) -> (a -> a -> a) -> a -> z -> x -> a -> a
      
    4. 现在这里有一个来自 ghci 的会话,展示了签名如何变化,而我随后将所有参数应用于该签名的函数:

      > let f = undefined :: ((a -> a) -> z -> x -> a -> a) -> (a -> a -> a) -> a -> z -> x -> a -> a
      > :t f (\x y z -> x)
      f (\x y z -> x) :: (a -> a -> a) -> a -> z -> x -> a -> a
      > :t f (\x y z -> x) (\a b -> a*b)
      f (\x y z -> x) (\a b -> a*b) :: Num a => a -> z -> x -> a -> a
      > :t f (\x y z -> x) (\a b -> a*b) 2
      f (\x y z -> x) (\a b -> a*b) 2 :: Num a => z -> x -> a -> a
      > :t f (\x y z -> x) (\a b -> a*b) 2 3
      f (\x y z -> x) (\a b -> a*b) 2 3 :: Num a => x -> a -> a
      > :t f (\x y z -> x) (\a b -> a*b) 2 3 4
      f (\x y z -> x) (\a b -> a*b) 2 3 4 :: Num a => a -> a
      > :t f (\x y z -> x) (\a b -> a*b) 2 3 4 5
      f (\x y z -> x) (\a b -> a*b) 2 3 4 5 :: Num a => a    
      

    以上解释了( (\x y z -> x).(\a b -> a*b) ) 2 3 4 5 的工作原理。

    下面是( (\x y z -> y).(\a b -> a*b) ) 2 3 4 5 的翻译方式:

    ((a -> a) -> z -> x -> z) -> (a -> a -> a) -> a -> z -> x -> z
    

    以下是会议结果:

    > let f = undefined :: ((a -> a) -> z -> x -> z) -> (a -> a -> a) -> a -> z -> x -> z
    > :t f (\x y z -> x)
    f (\x y z -> x) :: (a -> a -> a) -> a -> (a -> a) -> x -> a -> a
    > :t f (\x y z -> x) (\a b -> a*b)
    f (\x y z -> x) (\a b -> a*b)
      :: Num a => a -> (a -> a) -> x -> a -> a
    > :t f (\x y z -> x) (\a b -> a*b) 2
    f (\x y z -> x) (\a b -> a*b) 2 :: Num a => (a -> a) -> x -> a -> a
    > :t f (\x y z -> x) (\a b -> a*b) 2 3
    f (\x y z -> x) (\a b -> a*b) 2 3
      :: (Num a, Num (a -> a)) => x -> a -> a
    

    最后一行解释了您的错误消息。 a -> a 显然不能有任何 Num 实例。

    【讨论】:

      【解决方案3】:

      既然您了解一切都是单参数函数,那么让我们从这一点开始。请记住,(\x y z -> x) 实际上是 (\x -> (\y z -> x)),而实际上是 (\x -> (\y -> (\z -> x)) ),但让我们在第一步停止,以降低括号中的噪音。

      (f . g) x = f (g x)

      因此

      ((\x -> (\y z -> x)) . (\a b -> a*b)) 2 =
      (\x -> (\y z -> x)) ((\a -> (\b -> a*b)) 2) =
      (\x -> (\y z -> x)) (\b -> 2*b) =
      (\y z -> (\b -> 2*b))
      

      现在记住第二步,展开 (\y z -> ...):

      (\y z -> (\b -> 2*b)) 3 4 =
      (\y -> (\z -> (\b -> 2*b))) 3 4 =
         -- \y -> ... given anything, returns a function \z -> ...
      (\z -> (\b -> 2*b)) 4 =
         -- \z -> ... given anything, returns a function \b -> ...
      (\b -> 2*b)
      

      最后是:

      (\b -> 2*b) 5 = 2*5 = 10
      

      如果第一个函数返回 y 而不是 x,故事会以不同的方式展开:

      ((\x -> (\y z -> y)) . (\a -> (\b -> a*b))) 2 =
      (\x -> (\y z -> y)) ((\a -> (\b -> a*b)) 2) =
      (\x -> (\y z -> y)) (\b -> 2*b) =
         -- \x -> ... given anything, returns a function \y z -> ...
      (\y z -> y)
      

      所以你得到:

      (\y -> (\z -> y)) 3 4 5 =
         -- \y -> ... given anything, returns a function \z -> ...
      (\z -> 3) 4 5 =
         -- \z -> ... given anything, returns a constant 3
      3 5  -- now still trying to apply 5 to 3
      

      它试图将3 视为可以接受5 的函数。

      【讨论】:

        【解决方案4】:

        组合器.function composition 运算符。让我们检查一下它的类型:

        (.) :: (b -> c) -> (a -> b) -> a -> c
        

        所以它把第二个函数的结果传递给第一个函数。

        在您的示例中,要考虑的重要一点是,我们可以将 * 视为一个单参数函数,其结果是一个函数:

        (*) :: Num a => a -> (a -> a)
        

        它接受一个数字并返回一个将其参数乘以数字的函数。 (这种方法称为Currying)。类型运算符 -> 关联到右侧,因此括号是可选的 - 如果我们将 (*) 视为返回数字的双参数函数或返回另一个函数的单参数函数,这只是在我们的脑海中。

        这有助于我们查看(*) . succ 的作用:它增加其参数(必须是Enum),然后将其转换为一个函数,将其参数乘以数字(因此参数也必须是Num )。结果就是

        (*) . succ :: (Enum a, Num a) => a -> (a -> a)
        

        同样,我们可以将其视为一个单参数函数,或者更方便的双参数函数:它将第一个参数递增并与第二个参数相乘。

        【讨论】:

          【解决方案5】:

          ( (\x y z -> x).(\a b -> a*b) ) 2 3 4 5
          
          1. 他们首先评估 (\a b -> a*b) 2 产生这样的函数 (\b -> 2*b)
          2. 然后计算 (\x y z -> x) (\b -> 2*b) 3 4 表示取函数取 3 取 4 并返回函数,如下所示: ((\b - > 2*b) 3 4 -> (\b -> 2*b))
          3. 只剩下 (\b -> 2*b) 5 结果 2*5 = 10

          但在

          ( (\x y z -> y).(\a b -> a*b) ) 2 3 4 5
          

          第二次评估将导致这个 ((\b -> 2*b) 3 4 -> 3) 剩余 3 5 导致错误

          【讨论】:

            猜你喜欢
            • 2017-04-14
            • 1970-01-01
            • 2015-12-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-08-25
            • 1970-01-01
            相关资源
            最近更新 更多