3

我开始学习 Haskell,所以我也需要了解柯里化(这也是我第一次看到这种技术)。我想我知道它在某些情况下是如何工作的,其中 currification 只“消除”了其中一个参数。就像在下一个示例中,我试图计算 4 个数字的乘积。这是未经处理的函数:

prod :: Integer->Integer->Integer->Integer->Integer
prod x y z t = x * y * z * t

这是咖喱函数:

prod' :: Integer->Integer->Integer->Integer->Integer
prod' x y z = (*) (x*y*z)

但我不明白我怎么能继续这种动态,例如只用两个参数做同样的函数等等:

prod'' :: Integer->Integer->Integer->Integer->Integer
prod'' x y =
4

2 回答 2

5

这是未经处理的函数:

prod :: Integer -> Integer -> Integer -> Integer -> Integer
prod x y z t = x * y * z * t

已经是一个柯里化函数。事实上,Haskell 中的所有函数都是自动柯里化的。实际上,您在这里编写了一个如下所示的函数:

prod :: Integer -> (Integer -> (Integer -> (Integer -> Integer)))

Haskell 将因此生成一个如下所示的函数:

prod :: Integer -> (Integer -> (Integer -> (Integer -> Integer)))
prod = \x -> (\y -> (\z -> (\t -> x * y * z * t)))

事实上,我们可以例如生成这样的函数:

prod2 = prod 2

这将具有类型:

prod2 :: Integer -> (Integer -> (Integer -> Integer))
prod2 = prod 2

我们可以继续:

prod2_4 :: Integer -> (Integer -> Integer)
prod2_4 = prod2 4

最终:

prod2_4_6 :: Integer -> Integer
prod2_4_6 = prod2_4 6

编辑

具有以下功能的功能prod'

prod'' x y = (*) ((*) (x*y))

因为这意味着你乘(*) (x*y)以下一个参数。而是(*) (x*y)一个函数。您只能将数字相乘。严格来说,您可以制作函数编号。但是 Haskell 编译器因此抱怨:

Prelude> prod'' x y = (*) ((*) (x*y))

<interactive>:1:1: error:
    • Non type-variable argument in the constraint: Num (a -> a)
      (Use FlexibleContexts to permit this)
    • When checking the inferred type
        prod'' :: forall a.
                  (Num (a -> a), Num a) =>
                  a -> a -> (a -> a) -> a -> a

因此,它表示您在这里的目标是以函数a -> a作为第一个操作数来执行操作,但该函数不是类型类的实例Num

于 2019-09-29T09:38:32.580 回答
4

你所拥有的是

prod x y z t  =    x * y * z * t
              =   (x * y * z) * t
              =  (*) (x * y * z) t

因此通过eta 减少(我们用foo x = bar x替换foo = bar

prod x y z    =  (*) (x * y * z)
              =  (*) ( (x * y) * z )
              =  (*) ( (*) (x * y) z )
              =  ((*) . (*) (x * y)) z 

这样通过 eta 再次减少,

prod x y      =   (*) . (*) (x * y)

(.)是函数组合运算符,定义为

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

你所问的是所谓的无点风格。“无点”意味着“没有明确提及[隐含的]论点”(“点”是数学家对“论点”的行话)。

“柯里化”是一个正交问题,尽管 Haskell 作为一种柯里化语言使得这样的定义——以及威廉的回答中显示的部分应用程序——更容易编写。“柯里化”意味着函数一次接受一个参数,因此很容易将函数部分应用于值。

我们可以继续提取最后一个参数的过程,以便可以通过 eta 减少进一步消除它。但它通常会迅速导致越来越多的混淆代码,例如prod = ((((*) .) . (*)) .) . (*).

那是因为书面代码是固有二维(甚至更高维)计算图结构的一维编码,

  prod =           
                  /
                 *
                / \
               *   
              / \
         <-- *   
              \

你可以在这里试验一下。例如,如果(*)是右关联的,我们会得到更复杂的代码

\x y z t -> x * (y * (z * t))
==
(. ((. (*)) . (.) . (*))) . (.) . (.) . (*)

表示看起来一样清晰,只是稍微重新排列的图形结构

              / 
         <-- *   
              \ /
               *
                \ /
                 *
                  \
于 2019-09-29T10:54:48.787 回答