首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >你为什么要这样写Haskell?

你为什么要这样写Haskell?
EN

Stack Overflow用户
提问于 2018-03-03 00:08:27
回答 3查看 204关注 0票数 3

我一直在阅读一些Haskell代码,并不断地看到类似这样的函数:

代码语言:javascript
复制
ok :: a -> Result i w e a
ok a =
    Result $ \i w _ good ->
        good i w a

为什么用灯笼?你为什么不写以下几句话呢?

代码语言:javascript
复制
ok :: a -> Result i w e a
ok a =
    Result $ good i w a
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2018-03-03 00:57:12

这是https://en.wikipedia.org/wiki/Continuation-passing_style或"CPS“。

所以首先,你的另一个例子是没有意义的。goodiw在使用时不知道,您将得到一个错误。

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

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

给我烤一批饼干。

(我打算把饼干给戴夫),

烤一批饼干,然后给戴夫。

做同样的事情,但现在我不需要做中间人了。把我切成中间人通常在性能上有优势,而且这也意味着你可以做更多的事情,例如,如果这批饼干真的很好,你可能会决定把它给你妈妈,而不是戴夫(这样就放弃了戴夫会做的任何事情),或者把两批饼干都给戴夫(重复戴夫会做的事情)。有时你想要这种能力,而有时你不想,这取决于背景。(注:在下面的例子中,这些类型具有足够的普遍性,不允许出现这些可能性)

这里是一个非常简单的延续传递样式的例子。假设你有一个程序

代码语言:javascript
复制
pred :: Integer -> Maybe Integer
pred n = if n > 0 then Just (n-1) else Nothing

它从一个数字中减去1并返回它(在Just构造函数中),除非它变成负数,否则它返回Nothing。你可以这样用它:

代码语言:javascript
复制
main = do
    x <- readLn
    case x of
        Just predx -> putStrLn $ "The predecessor is " ++ show predx
        Nothing -> putStrLn "Can't take the predecessor"

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

代码语言:javascript
复制
pred :: Integer -> (Integer -> r) -> r -> r
--                 ^^^^^^^^^^^^^^    ^
--                   Just case       |
--                              Nothing case
pred n ifPositive ifNegative = 
    if n > 0 
        then ifPositive (n-1) 
        else ifNegative

其用法如下:

代码语言:javascript
复制
main = do
    x <- readLn
    pred x (\predx -> putStrLn $ "The predecessor is " ++ show predx)
           (putStrLn "Can't take the predecessor)

看到它是如何工作的吗?--首先我们得到结果,然后进行案例分析;在第二种方式中,每个案例成为函数的一个参数。在这个过程中,对pred的调用变成了一个尾叫,消除了对堆栈帧和中间Maybe数据结构的需求。

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

代码语言:javascript
复制
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新类型包装器,它在运行时不起作用)。现在也许你可以在你的问题中看到与代码的连接。

票数 16
EN

Stack Overflow用户

发布于 2018-03-03 01:06:04

显然,Result类型封装了一个函数,因此在这里使用lambda是很自然的事情。如果您想避免使用lambda,可以使用本地定义而不是使用letwhere,例如:

代码语言:javascript
复制
ok a = let
  proceed i w _ good = good i w a
  in Result proceed

-- or --

ok a = Result proceed
  where
    proceed i w _ good = good i w a

由于变量iwgood不在作用域内,编写此操作将无法工作:

代码语言:javascript
复制
ok :: a -> Result i w e a
ok a =
    Result $ good i w a

我想知道,您混淆的原因是否是iw也被用作ok签名中的类型变量,但它们是不同的变量,它们的名称恰好相同。就好像你写了这样的东西:

代码语言:javascript
复制
ok :: a -> Result i w e a
ok value =
    Result $ continue index writer value

在这里,应该很明显没有定义continueindexwriter变量。

票数 7
EN

Stack Overflow用户

发布于 2018-03-03 00:15:38

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

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/49079255

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档