我的代码中定义了以下TYPE:
type Pos = (Int, Int) -- position in 2 dimensions
我想创建一个包含Pos元素的列表(或堆栈或其他)。
我需要能够将一个Pos元素添加(推送)到列表的头部(当在该位置进行更改时),并且需要时从列表的头部拉出(弹出)一个Pos(到重播最后一次更改。)
我对Haskell真的很陌生,那么最好的方法是什么?我应该只使用一个列表并与HEAD一起玩吗?但是你如何去除"从头部(pop),或者是否有我可以使用的堆栈(对我来说听起来像堆栈类型的东西) - 如果是这样的话我如何用类型' Pos'?
答案 0 :(得分:6)
听起来你想在列表上执行“有状态”操作,将其视为堆栈。 Haskell有几种方法可以做到这一点,最基本的是简单地定义带有堆栈的函数并为每个“修改”返回一个新函数,因为Haskell具有不可变变量(从技术上讲,它根本没有变量,只有名称绑定到不可变的值)。你可以这样做
newtype Stack a = Stack [a] deriving (Eq, Show)
这只是在[a]
周围定义一个名为Stack a
的包装器,因此我们的类型签名更加严格和信息丰富。然后,我们可以非常轻松地定义push
和pop
:
pop :: Stack a -> (a, Stack a)
pop (Stack (x:xs)) = (x, Stack xs)
pop (Stack []) = error "Empty stack"
push :: a -> Stack a -> Stack a
push x (Stack xs) = Stack (x:xs)
这样可行,但我真的不喜欢pop
能够引发error
,这可能会导致程序崩溃。我们最好使用可以表示失败的数据类型,在这种情况下Maybe
将是一个很好的选择:
pop :: Stack a -> (Maybe a, Stack a)
pop (Stack (x:xs)) = (Just x, Stack xs)
pop (Stack []) = (Nothing, Stack [])
我们可以像
一样使用它main :: IO ()
main = do
let start = Stack [] :: Stack Int
step1 = push 1 start -- Stack [1]
step2 = push 2 step1 -- Stack [2, 1]
(_, step3) = pop step2 -- Stack [1]
step4 = push 10 step3 -- Stack [10, 1]
print step4
但这真的很烦人,不能很好地构成,并要求我们编写大量的中间语句。当然,我们可以将这些push
中的一些组合在一起,但它不会让我们受益匪浅。如果Haskell可以为我们处理那些中间值,那就更好了,事实上它可以。我们可以使用State
monad来简化这一过程。 State
monad代表一系列修改纯数据结构的计算。从本质上讲,它只是处理我们的所有组合和中间值,以便我们可以专注于算法,而不是细节。如果我们将pop
重命名为pop_
和push
重命名为push_
,我们可以编写以下代码:
type StackState a b = State (Stack a) b
pop :: StackState a (Maybe a)
pop = state push_
-- The `state` function has the type (s -> (a, s)) -> State s a,
-- so applying it to `push_ :: Stack a -> (Maybe a, Stack a)` gives
-- us `State (Stack a) (Maybe a)`. (It actually has a bit more general
-- type, but it simplifies to this)
push :: a -> StackState a ()
push x = modify (push_ x)
-- The `modify` function has the type (s -> s) -> State s ()
现在我们可以更轻松地构建计算:
stackTransform :: StackState Int ()
stackTransform = do
push 1
push 2
pop
push 10
我们甚至可以编写更复杂的操作
test :: StackState Int Int
test = do
mapM_ push [1..10]
Just x <- pop
push $ x * 10
return x
然后这些可以从main
作为
main :: IO ()
main = do
let start = Stack [] :: Stack Int
print $ execState stackTransform start
虽然这个解决方案有点复杂并且需要一些monad知识,但它确实让我们更清晰地编写堆栈操作,而不必担心中间步骤,这是使用monad的一个好处。 State
的实现细节有点棘手,所以我现在不会讨论它们,但是在某些时候它们非常好学习。
我认为还值得一提的是,还有另一种选择可以让你拥有“可变变量”,但你的所有行动都必须存在于IO
monad中,而且它将是一种效率较低的实现比上面的任何一个。您可以使用充当可变指针的IORef
来实现此行为:
import Data.IORef
newtype Stack a = Stack [a] deriving (Eq, Show)
type IOStack a = IORef (Stack a)
popIO :: IOStack a -> IO (Maybe a)
popIO iostack = do
-- Read the current stack
stack <- readIORef iostack
case stack of
Stack [] -> return Nothing
Stack (x:xs) -> do
-- Put the new stack back into the IORef
writeIORef iostack (Stack xs)
-- Return the top value in the stack
return (Just x)
pushIO :: a -> IOStack a -> IO ()
pushIO x iostack = do
-- Read the current stack
(Stack xs) <- readIORef iostack
-- Write the new stack back into the IORef
writeIORef (Stack (x:xs))
然后您可以使用main
作为
main :: IO ()
main = do
iostart <- newIORef (Stack [] :: Stack Int)
pushIO 1 iostart
pushIO 2 iostart
popIO iostart
pushIO 10 iostart
final <- readIORef iostart
print final
但是这仍然比State
版本的代码更多,并且它肯定更容易出错并且会更慢。