你为什么要写这样的Haskell?

时间:2018-03-03 00:08:27

标签: haskell

我一直在阅读一些Haskell代码并继续看到类似这样的函数:

ok :: a -> Result i w e a
ok a =
    Result $ \i w _ good ->
        good i w a

为什么使用lambda?你为什么不写下以下内容?:

ok :: a -> Result i w e a
ok a =
    Result $ good i w a

2 个答案:

答案 0 :(得分:16)

这是continuation passing style或" CPS"。

首先,您的替代示例没有意义。 goodiw在使用它们时未知,您将收到错误消息。

继续传递样式的基本思想是,不是返回相关信息,而是调用您给定的函数(在本例中为good),将您的预期结果作为参数传递给它。据推测(基于命名),被忽略的参数_将被称为bad,它是一个在失败的情况下调用的函数。

如果您是ok函数,那就像要求您

之间的区别一样
  

给我烤一批饼干。

(我打算给Dave提供cookies),

  

烘焙一批饼干然后交给戴夫。

完成同样的事情,但现在我不再需要成为中间人了。作为中间人切断我的性能通常有性能优势,也意味着你可以做更多的事情,例如,如果一批饼干非常好,你可能决定把它交给你的妈妈而不是Dave(因此中止任何Dave他们会用它们完成),或者将两个批次烘焙给Dave(重复Dave会做的事情)。有时你想要这种能力,有时你不想要这种能力,这取决于背景。 (以下示例中的N.B.类型足以排除这些可能性)

以下是继续传递样式的一个非常简单的示例。假设你有一个程序

pred :: Integer -> Maybe Integer
pred n = if n > 0 then Just (n-1) else Nothing

从数字中减去1并返回它(在Just构造函数中),除非它变为负数然后返回Nothing。您可以这样使用它:

main = do
    x <- readLn
    case x of
        Just predx -> putStrLn $ "The predecessor is " ++ show predx
        Nothing -> putStrLn "Can't take the predecessor"

我们可以通过继续传递样式对此进行编码,而不是返回Maybe,让pred为每种情况下的操作提出一个参数:

pred :: Integer -> (Integer -> r) -> r -> r
--                 ^^^^^^^^^^^^^^    ^
--                   Just case       |
--                              Nothing case
pred n ifPositive ifNegative = 
    if n > 0 
        then ifPositive (n-1) 
        else ifNegative

用法变为:

main = do
    x <- readLn
    pred x (\predx -> putStrLn $ "The predecessor is " ++ show predx)
           (putStrLn "Can't take the predecessor)

看看它是如何工作的? - 这是我们获得结果的第一种方式,然后进行案例分析;在第二种方式中,每个案例成为函数的一个参数。在此过程中,对pred的调用变为tail call,无需堆栈帧和中间Maybe数据结构。

唯一剩下的问题是pred的签名有点令人困惑。通过将CPS内容包装在自己的类型构造函数中,我们可以使它更清晰:

newtype CPSMaybe a = CPSMaybe (forall r. (a -> r) -> r -> r)

pred :: Integer -> CPSMaybe Integer
pred n = CPSMaybe $ \ifPositive ifNegative -> 
    if n > 0
        then ifPositive (n-1)
        else ifNegative

其签名看起来更像第一个,但代码看起来像第二个(CPSMaybe新类型包装器除外,它在运行时没有效果)。现在,您可以在问题中看到与代码的连接。

答案 1 :(得分:4)

在第一个示例中,goodwi是lambda表达式的本地定义参数。在第二个样本中,它们是自由变量。我希望第二个示例失败,并显示错误,指出这些标识符不在范围内。 Result显然是一种包含有关如何使用给定数据和处理程序的信息的类型。 ok表示获取数据并应用处理程序以指示其良好结果。在第二个示例中,不清楚是否有人引用了Result包装的可用参数,或者哪些名称引用了哪些参数。