这是我第一次使用Haskell,我已经阅读了很多关于它的教程。但是在实践中,出现了许多问题。 我试图建立一个堆栈数据结构并在Do块中使用它。但是当我这样做的时候。它说不能匹配类型' Stack'和' IO',我不知道这个问题。以下是我的代码:
import Data.Array.IO
main::IO()
main = do
arr <- newArray((0,0),(8,13)) 0 ::IO (IOArray(Int,Int) Int)
list <- getElems arr
print list
push 0 mystack --here is the problem
return()
data Stack a = Stack [a] deriving Show
empty :: Stack a
empty = Stack []
push :: a -> Stack a -> Stack a
push x (Stack xs)= Stack (x:xs)
pop :: Stack a -> (Maybe a, Stack a)
pop (Stack []) = (Nothing, Stack [])
pop (Stack (x:xs)) = (Just x, Stack xs)
mystack = empty
问题如下(当我将推送0 mystack放入它显示的Do块时)
Couldn't match type `Stack' with `IO'
Expected type: IO Integer
Actual type: Stack Integer
In the return type of a call of `push'
In a stmt of a 'do' block: push 0 mystack
答案 0 :(得分:5)
此处的问题是main
的类型为IO ()
,这意味着do
块中的任何语句都必须为IO a
类型的a
类型。您的数据类型为Stack a
,与IO a
不匹配。你也看起来像是想要某种可变状态&#34;使用您的堆栈,但所有功能都是纯粹的,这意味着它们只返回一个新值。 Haskell中的值是 immutable ,这意味着它们在声明后不能被修改。在大多数情况下,Haskell没有变量,只是命名值。
你真正想要的是使用State
monad。您可以修改push
和pop
函数以在该monad中工作,然后使用execState
运行有状态计算:
import Control.Monad.State
data Stack a = Stack [a] deriving (Eq, Show)
push' :: a -> Stack a -> Stack a
push' x (Stack xs) = Stack (x:xs)
push :: a -> State (Stack a) ()
push x = modify (push' x)
pop' :: Stack a -> (Maybe a, Stack a)
pop' (Stack []) = (Nothing, Stack [])
pop' (Stack (x:xs)) = (Just x, Stack xs)
pop :: State (Stack a) (Maybe a)
pop = state pop'
请注意直接使用已编写的函数来实现它是多么容易!您甚至让pop
返回元组的第一个元素中的Maybe a
以直接进入state
函数。然后,您可以将其用作
main :: IO ()
main = do
let resultStack = flip execState empty $ do
push 1
push 2
push 3
pop -- pop off 3
Just x <- pop -- pop off 2
push $ 2 * x -- push 4
mapM_ push [1..10] -- Pushes 1 through 10 onto the stack in that order
pop -- pop off 10
pop -- pop off 9
pop -- pop off 8
print resultStack
这将打印出来
Stack [7, 6, 5, 4, 3, 2, 1, 4, 1]
答案 1 :(得分:2)
push 0 mystack
返回一个新堆栈。您没有获得返回值,并且您将此行写为“操作”。 “动作”是改变系统全局状态的东西,它们由返回IO的函数标记。由于push
不会改变全局状态,因此haskell告诉你没有理由像你一样调用它。
你的意思是:
let newStack = push 0 mystack
答案 2 :(得分:2)
在do
块内,连续的行如:
do
print list
print "something else"
转换为:
print list >> print "something else"
>>
使用IO a -> IO b -> IO b
时,IO
的类型为print list
push 0 mystack
。
所以:
push 0 mystack
要编译,IO a
必须为某种类型a
返回push 0 mystack
,但是Stack Integer
会返回do
,因此会出错。
如果要在let
块中绑定常规值,可以使用let stack' = push 0 mystack
,例如
{{1}}
答案 3 :(得分:0)
其他答案已经提出了一些解决方案。在这里,让我评论一下你的代码实际意味着什么。让我们关注您的main
:
main = do
arr <- newArray((0,0),(8,13)) 0 ::IO (IOArray(Int,Int) Int)
list <- getElems arr
print list
push 0 mystack --here is the problem
return()
正如错误所指出的那样,问题在于push 0 mystack
行。现在,Haskell是一种纯语言,其中每个定义的值(例如push
或mystack
)都可以用自己的定义替换,而不会影响程序的含义。此功能通常称为&#34;参考透明度&#34;。然后我们可以说明该行
push 0 mystack
相当于(根据mystack
的定义)
push 0 empty
相当于(根据empty
的定义)
push 0 (Stack [])
相当于(根据push
的定义)
Stack (0:[])
可以使用更常见的语法
编写Stack [0]
因此,您的main
实际上意味着
main = do
arr <- newArray((0,0),(8,13)) 0 ::IO (IOArray(Int,Int) Int)
list <- getElems arr
print list
Stack [0] --here is the problem
return()
现在很容易看出问题线确实如此。它提到了Stack
值,但没有指定要对其执行的操作。例如,我们可以使用
print (Stack [0]) -- or, equivalently, print (push 0 mystack)
或者我们可以定义一个具有此值的变量
let s = Stack [0] -- or, equivalently, let s = push 0 mystack
...
print s
一般来说,我们可以用这样的价值做任何事情。
如果你试图&#34;修改&#34; mystack
的值,知道你不能用纯语言作为Haskell。实际上,修改mystack
的值与通过引用透明度修改Stack []
的值一样有意义。 Haskell变量表示值而不是可变存储单元。也许首先令人惊讶的是,人们通常可以编程而没有可变变量。这可能感觉不可能或不切实际,因为很久以来一直用命令式语言进行编程,但是一旦习惯了纯函数编程,这种感觉非常自然,并且在很多方面得到回报(例如,允许对代码进行等式推理,更少的对并发编程的关注等)。当您确实需要可变状态时,您可以使用State
monad,或IO
moand和IORef
,或ST
和STRef
。但是,当有状态方法确实是表达算法的最佳,最简单的方法时,应该使用这些方法。实际上,这种情况很少发生,即使是这样,通常人们仍可以编写大部分子程序,而不会对状态产生副作用。