你为什么要这样写Haskell?

你为什么要这样写Haskell?,haskell,Haskell,我一直在阅读一些Haskell代码,并不断看到类似以下内容的函数: ok :: a -> Result i w e a ok a = Result $ \i w _ good ->      good i w a main = do x <- readLn case x of Just predx -> putStrLn $ "The predecessor is " ++ show predx Nothin

我一直在阅读一些Haskell代码,并不断看到类似以下内容的函数:

ok :: a -> Result i w e a
ok a =
    Result $ \i w _ good ->
        good i w a
main = do
    x <- readLn
    case x of
        Just predx -> putStrLn $ "The predecessor is " ++ show predx
        Nothing -> putStrLn "Can't take the predecessor"
ok :: a -> Result i w e a
ok value =
    Result $ continue index writer value
为什么使用lambda?你为什么不写下面的内容呢

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

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

这是或“CPS”

所以首先,你的另一个例子没有意义
good
i
w
在使用时是未知的,您将得到一个错误

延续传递样式的基本思想是,不返回相关信息,而是调用给定的函数(在本例中为
good
),将预期结果作为参数传递给它。据推测(基于命名),被忽略的参数
\uu
会被称为
坏的
,它是一个在出现故障时调用的函数

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

给我烤一批饼干

(我打算把饼干给Dave),以及

烤一批饼干,然后给戴夫

这可以完成同样的事情,但现在我不必再做中间人了。把我从中间人中剔除通常有性能优势,也意味着你可以做更多的事情,例如,如果这批饼干真的很好,你可能会决定把它给你妈妈而不是Dave(从而放弃Dave会用它们做的任何事情),或者烘焙两批饼干,然后把它们都给Dave(重复Dave会做的事情)。有时你需要这种能力,有时你不需要,这取决于上下文。(注意,在下面的示例中,类型足够通用,不允许这些可能性)

下面是一个非常简单的延续传球风格的例子。假设你有一个程序

pred :: Integer -> Maybe Integer
pred n = if n > 0 then Just (n-1) else Nothing
它从一个数字中减去1并返回它(在
Just
构造函数中),除非它变成负数,否则它将返回
Nothing
。您可以这样使用它:

ok :: a -> Result i w e a
ok a =
    Result $ \i w _ good ->
        good i w a
main = do
    x <- readLn
    case x of
        Just predx -> putStrLn $ "The predecessor is " ++ show predx
        Nothing -> putStrLn "Can't take the predecessor"
ok :: a -> Result i w e a
ok value =
    Result $ continue index writer value
用法变成:

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

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

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

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
写这篇文章行不通,因为变量
i
w
good
不在范围内:

ok :: a -> Result i w e a
ok a =
    Result $ good i w a
我想知道你困惑的根源是否是
I
w
ok
的签名中也被用作类型变量,但它们是不同的变量,恰好有相同的名称。就像你写了这样的东西:

ok :: a -> Result i w e a
ok a =
    Result $ \i w _ good ->
        good i w a
main = do
    x <- readLn
    case x of
        Just predx -> putStrLn $ "The predecessor is " ++ show predx
        Nothing -> putStrLn "Can't take the predecessor"
ok :: a -> Result i w e a
ok value =
    Result $ continue index writer value

这里应该很明显,
continue
index
writer
变量没有定义。

您是否尝试过进行建议的修改,看看会发生什么?另一个很好的答案,是的,这就是造成混淆的原因。因为您可以使用
来编写
或普通的
新类型CPS可能是r
,有什么区别吗?我对您编写它的方式的理解是,当您编写类型签名时,它不会打扰您,但编译器仍然能够推断出
r
的确切类型。对吗?