【问题标题】:Haskell : Infinite list when integer is pushed to stack implementationHaskell:将整数推送到堆栈实现时的无限列表
【发布时间】:2020-12-06 20:03:20
【问题描述】:

我正在尝试实现一个简单的堆栈,但我很困惑为什么当我将一个整数压入堆栈时会得到一个无限列表。

所有其他功能都按我的预期工作,但我不明白push 的问题。当我尝试将一个空堆栈分配给已推送如下变量的自身时,它会出错:

λ > a = makeStack
λ > push 3 a
[3]
λ > a
[]
λ > a = push 3 a
λ > a
[3,3,3,3,3,3,3,3,3,3^CInterrupted.
type Stack a = [a]

makeStack :: Stack a 
makeStack = []

push :: a -> Stack a -> Stack a
push a as = (a:as)

【问题讨论】:

    标签: haskell stack


    【解决方案1】:

    haskell 中(我猜,几乎)没有可变(感谢@amalloy 纠正我的术语)变量。

    如果你这样写:

    x = f x
    

    进入无限循环:f (f (f ...

    因此,a 没有过去值,3 可以在其中被推送。

    因此,您必须将 push 3 a 放入其他值(或直接放入 ghci 中)。

    这样的循环有时可能会派上用场。看看Data.Function.fix

    fix :: (a -> a) -> a
    fix f = let x = f x in x
    

    它可以用来做和你一样的事情:

    GHCi> fix (push 3)
    [3,3,3,3,^CInterrupted.
    

    然而,无限循环并非总是如此。看看:

    factFun rec n = if n == 0 then 1 else n * rec (n - 1)
    

    此函数可能看起来像阶乘,但非终止分支中的递归调用被替换为虚拟 (rec)。我们希望一遍又一遍地用factFun 替换这个虚拟对象以获得阶乘。 fix 这样做:

    GHCi> fix factFun 4
    24
    

    注意:我将在这里复制我的评论:如果读者还不知道fix,我想邀请他们进行一次漫长而有趣的旅行。我建议他们从 wikibook on denotational semantics 开始。

    【讨论】:

    • 这是一个非常有趣的功能!我去看看能不能用!
    • @JiangShi 这是一个很棒的功能。我建议您查看a wikibook on denotational semantics - 它说了很多。这几乎就是我开始的地方。祝你好运:)。
    • Haskell 中有很多变量。它们只是不可变的。
    • @amalloy 你是对的 - 对不起我的术语。我编辑了帖子。
    【解决方案2】:

    Haskell 不允许突变。在源文件中,如果您定义一个变量 a,然后尝试重新分配它,就像您在此处使用 a = push 3 a 所做的那样,您将收到编译错误。您不这样做的唯一原因是您在 GHCi 中工作,它确实允许您重新定义变量 - 这纯粹是一种方便,因此您不必在尝试不同的定义时不断思考新名称。

    而且,至关重要的是,a = push 3 a没有在前一个的基础上为a 赋予新值,因为它将是一种命令式语言。相反,它是a 的定义就其本身而言

    这就是你得到一个无限列表的原因 - 你的定义被处理如下:

    a = push 3 a
       = 3:a
       = 3:(push 3 a)
       = 3:(3:a)
    

    等等。由于 Haskell 的懒惰,这样的定义没有问题 - GHCi 会,当你要求完整的列表时,就像这里一样,一次只计算一个元素,因此一直打印 3s,直到你告诉它停止。

    要获得您想要的,您只需键入push 3 a,或者如果您需要为其指定一个名称,只需从a 中选择一个不同的名称。 b = push 3 a 后跟 b 将按照您的预期运行。

    【讨论】:

    • 我记得在 GHCi(或者更确切地说是任何 do 块)中,有一个特殊的技巧可以使用自身“重新分配”变量:a <- pure (push 3 a)
    • 是的,这将在 do 块中工作(如果您对其进行脱糖,左侧的“新”a 是 lambda 的参数,因此会遮盖“旧”的) - 但必须理解,这与 Haskell 中的正常“分配”(或更确切地说是定义)非常不同。我不确定 GHCi 是否允许这样做,并且必须尝试一下 - 我知道 GHCi 隐含地将所有事情解释为 IO monad 中发生的事情,但如果它允许单行带有“左箭头”,我会感到惊讶后面没有任何内容,因为这本身没有意义。
    • 我提到这个技巧只是因为我看到有人推荐它,因为do 中的绑定默认情况下是非递归的(do rec 显式递归)。 (作者认为 let 默认是递归的,这在 Haskell 中是不幸的。)我个人认为一个单一的“左箭头”绑定在 GHCi 中应该是有效的,这并不奇怪,否则就无法获得中间结果IO操作,并将其绑定到变量以供进一步使用。
    猜你喜欢
    • 1970-01-01
    • 2013-05-26
    • 2011-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-27
    • 2013-02-09
    相关资源
    最近更新 更多