是否可以仅使用lambda表达式实现堆栈?

时间:2014-10-28 05:40:55

标签: haskell lambda-calculus

这可能不是一个非常实际的问题,我只是好奇我是否可以实现只有lambda表达式的堆栈。

一个堆栈支持3个操作:toppoppush,所以我首先将堆栈定义为3元组:

data Stack a = Stack a (a -> Stack a) (Stack a)
             | Empty

这里Empty代表空堆栈,所以我们至少要有一个居民开始。

根据这个定义,除了push操作外,eveything看起来很好:

import Control.Monad.State
import Control.Monad.Writer
import Data.Maybe

data Stack a = Stack a (a -> Stack a) (Stack a)
             | Empty

safePop :: Stack a -> Maybe (Stack a)
safePop Empty = Nothing
safePop (Stack _ _ s) = Just s

safeTop :: Stack a -> Maybe a
safeTop Empty = Nothing
safeTop (Stack x _ _) = Just x

push :: a -> Stack a -> Stack a
push x s = _

stackManip :: StateT (Stack Int) (Writer [Int]) ()
stackManip = do
    let doPush x = modify (push x)
        doPop    = do
            x <- gets safeTop
            lift . tell . maybeToList $ x
            modify (fromJust . safePop)
            return x
    doPush 1
    void doPop
    doPush 2
    doPush 3
    void doPop
    void doPop

main :: IO ()
main = print (execWriter (execStateT stackManip Empty))

因此,当我完成代码时,我应该能够运行它并获得类似[1,3,2]

的内容

然而,我发现自己正在扩展push infintely的定义:

push应该构造一个新的堆栈,第一个元素是刚推入堆栈的项目,第三个元素是当前堆栈:

push :: a -> Stack a -> Stack a
push x s = Stack x _ s

要填补空洞,我们需要创建堆栈,所以我需要一个let-expression:

push :: a -> Stack a -> Stack a
push x s = let s1 = Stack x (\x1 -> Stack x1 _ s1) s
           in s1

要填补新洞,我需要另一个let-expression:

push :: a -> Stack a -> Stack a
push x s = let s1 = Stack x (\x1 ->
                             let s2 = Stack x1 _ s1
                             in s2) s
           in s1

所以你可以看到我的push定义中总是有一个漏洞,但我扩展了它。

我有点理解Data.Function.fix背后的魔力,并猜测这里可以应用一些类似的魔法,但无法弄明白。

我想知道

  • 这可能吗?
  • 如果答案是肯定的,背后的魔力是什么?

2 个答案:

答案 0 :(得分:7)

您可以使用具有Church编码的函数类型完全实现它:

{-# LANGUAGE Rank2Types #-}

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

cons :: a -> Stack a -> Stack a
cons x (Stack f) = Stack (\g nil -> _)

peek :: Stack a -> Maybe a
peek (Stack f) = f (\x _ -> Just x) Nothing

这表示Stack是一个函数,它接受一个函数,该函数将顶部元素和堆栈的其余部分作为其参数。 Stack函数的第二个参数是堆栈为空时使用的默认值。我实现了peek函数,但我离开了cons,剩下的就是练习(如果你需要更多的帮助,请告诉我。另外,你留下我放入cons的下划线,GHC将告诉你它期望的类型并列出一些可能相关的绑定。)

rank-2类型表示,给定Stack a,我们可以给它一个返回任何类型值的函数,不受a类型变量的约束。这很方便,因为我们可能不想使用相同的类型。考虑一堆列表,我们想要使用Stack中的函数来获取顶部元素的长度。更重要的是,它表示像cons这样的函数无法以任何方式操纵结果。它必须返回从函数中获取的r类型值(如果堆栈为空,则返回默认值),不变。

另一个好的练习是实现toList :: Stack a -> [a]fromList :: [a] -> Stack a并表明这两个函数形成同构(意味着它们彼此相反)。

事实上,据我所知,所有Haskell数据类型都有一个表示为Church编码。您可以在此Stack类型中看到三种组合类型(总和类型,产品类型和&#34;类型递归&#34;)的基本方法。

答案 1 :(得分:4)

push的结果正是您想要保留push的结果,因此您可以像这样打结:

push :: a -> Stack a -> Stack a
push x s = let s' = Stack x (flip push s') s in s'

如果你想通过Data.Function.fix打结,你可以像这样改变上面的定义:

push :: a -> Stack a -> Stack a
push x s = fix $ \s' -> Stack x (flip push s') s