10

我不明白在 ghci 7.4.1 中用 arity > 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 个参数,但它并没有真正帮助我:)

4

5 回答 5

13

以这种方式解决:

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

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

((*) . succ) 3 4 = (*) (succ 3) 4
                 = (*) 4 4
                 = 16
于 2013-09-19T22:50:05.937 回答
5

当您编写时,(\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

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

于 2013-09-19T23:11:18.230 回答
3

既然您了解一切都是单参数函数,那么让我们从这一点开始。请记住,(\xyz -> x) 实际上是 (\x -> (\yz -> 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))

现在记住第二步并展开 (\yz -> ...):

(\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

它试图35.

于 2013-09-20T09:21:07.387 回答
2

Combinator.是一个函数组合运算符。让我们检查一下它的类型:

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

所以它获取第二个函数的结果并将其传递给第一个函数。

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

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

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

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

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

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

于 2013-09-20T07:35:43.373 回答
0

( (\x y z -> x).(\a b -> a*b) ) 2 3 4 5
  1. 他们首先评估 (\ab -> a*b) 2 导致这样的函数 (\b -> 2*b)
  2. 然后评估 (\xyz -> 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 导致错误

于 2013-09-20T01:54:34.690 回答