【问题标题】:Haskell Couldn't match type `Stack' with `IO'Haskell 无法将类型“堆栈”与“IO”匹配
【发布时间】:2014-07-30 19:11:21
【问题描述】:

这是我第一次使用 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

问题如下(当我将 push 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

【问题讨论】:

    标签: haskell io


    【解决方案1】:

    这里的问题是main 的类型为IO (),这意味着do 块内的任何语句对于某些类型a 必须具有类型IO a。您的数据类型是Stack a,与IO a 不匹配。您看起来还希望堆栈具有某种“可变状态”,但是您的所有函数都是纯函数,这意味着它们只是返回一个新值。 Haskell 中的值是不可变的,这意味着它们在被声明后不能被修改。在大多数情况下,Haskell 没有变量,只有命名值。

    您可能真正想要的是使用State monad。您可以修改 pushpop 函数以改为在该 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]
    

    【讨论】:

      【解决方案2】:

      push 0 mystack 返回一个新堆栈。你没有得到返回值,你把这一行写成一个“动作”。 “动作”是改变系统全局状态的东西,它们由返回 IO 的函数标记。由于push 不会改变全局状态,haskell 告诉你没有理由像你一样调用它。

      你的意思可能是:

      let newStack = push 0 mystack
      

      【讨论】:

        【解决方案3】:

        do 块内,连续的行如:

        do
          print list
          print "something else"
        

        翻译成:

        print list >> print "something else"
        

        当你在这里使用IO 时,&gt;&gt; 的类型为 IO a -&gt; IO b -&gt; IO b

        所以对于:

        print list
        push 0 mystack
        

        要编译,push 0 mystack 必须为某些类型 a 返回一个 IO a,但是 push 0 mystack 返回一个 Stack Integer,因此会出现错误。

        如果您想在 do 块内绑定常规值,您可以使用 let 例如

        let stack' = push 0 mystack
        

        【讨论】:

          【解决方案4】:

          其他答案已经提出了一些解决方案。在这里,让我评论一下您的代码的实际含义。让我们关注你的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 是一种纯语言,其中每个定义的值(例如pushmystack)都可以用自己的定义替换,而不会影响程序的含义。此功能通常称为“参考透明度”。然后我们可以声明这条线

          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 值,但它没有指定 what 对此要做什么。例如,我们可以使用

          print (Stack [0])  -- or, equivalently, print (push 0 mystack)
          

          或者我们可以用这样的值定义一个变量

          let s = Stack [0]   -- or, equivalently, let s = push 0 mystack
          ...
          print s
          

          一般来说,我们可以用这样的值做任何事情。


          如果您试图“修改”mystack 的值,请知道您不能像 Haskell 那样使用纯语言。实际上,通过引用透明性修改mystack 的值与修改Stack [] 的值一样有意义。 Haskell 变量表示值而不是可变的内存单元。一开始也许令人惊讶的是,人们通常可以在没有可变变量的情况下进行编程。当一个人长期以来一直使用命令式语言进行编程时,这可能会让人觉得不可能或不切实际,但是一旦习惯了纯函数式编程,这会感觉很自然,并且会以多种方式得到回报(例如,允许对代码进行等式推理,更少)对并发编程等的关注)。当你真的需要可变状态时,你可以使用State monad,或者IO moand 和IORefs,或者STSTRef,例如。但是,当有状态方法确实是表达算法的最佳、最简单的方法时,应该使用这些方法。在实践中,这种情况很少发生,即使是这样,通常仍然可以编写大部分子例程而不会对状态产生副作用。

          【讨论】:

            猜你喜欢
            • 2021-03-24
            • 2014-10-17
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-02-19
            • 1970-01-01
            相关资源
            最近更新 更多