Haskell初学者,试图输出一个列表

Cub*_*bic 5 haskell

我想这里的每个人都已经看过其中一个(或者至少是一个类似的)问题,我仍然需要问,因为我无法在任何地方找到这个问题的答案(主要是因为我不知道到底应该看到什么)对于)

我写了这个小脚本,其中printTriangle应该打印出pascal三角形.

fac = product . enumFromTo 2

binomial n k  = (product (drop (k-1) [2..n])) `div` (fac (n-k))

pascalTriangle maxRow = 
               do row<-[0..maxRow-1]
                  return (binomialRow row)
                  where
                  binomialRow row = 
                              do k<-[0..row]
                                 return (binomial row k)

printTriangle :: Int -> IO ()
printTriangle rows  = do row<-(triangle)
                         putStrLn (show row)
                         where 
                         triangle = pascalTriangle rows
Run Code Online (Sandbox Code Playgroud)

现在由于受过训练的眼睛可能显而易见的原因,但对我来说完全笼罩在神秘之中,当我尝试在ghci中加载时会出现以下错误:

   Couldn't match expected type `IO t0' with actual type `[[Int]]'
    In a stmt of a 'do' expression: row <- (triangle)
    In the expression:
      do { row <- (triangle);
           putStrLn (show row) }
    In 
an equation for `printTriangle':
            printTriangle rows
              = do { row <- (triangle);
                     putStrLn (show row) }
              where
                  triangle = pascalTriangle rows
Run Code Online (Sandbox Code Playgroud)

我试图做的是像我这样调用printTriangle:

printTriangle 3
Run Code Online (Sandbox Code Playgroud)

我得到这个输出:

[1]
[1,1]
[1,2,1]
Run Code Online (Sandbox Code Playgroud)

如果有人能够向我解释为什么我在这里做的事情不起作用(说实话,我不确定我在这里做了什么;我已经习惯了命令式语言,这整个函数编程的东西仍然令人困惑对我而言,以及如何以一种不那么愚蠢的方式做到这一点.

提前致谢.

Mat*_*ick 6

你在评论中说你认为列表是monad,但现在你不确定 - 嗯,你是对的,列表 monad!那么为什么你的代码不起作用呢?

好吧,因为IO也是一个单子.因此,当编译器看到printTriangle :: Int -> IO (),然后编写符号时,它会说"啊哈!我知道该怎么做!他正在使用IO monad!" 尝试想象它的冲击和绝望,当它发现而不是IO monads,它发现里面的列表单子!

这就是问题:打印和处理外部世界,你需要使用IO monad; 在函数内部,您尝试使用列表作为monad.

让我们看看这是一个什么问题.do-notation是Haskell的语法糖,用来引诱我们进入它的蛋糕屋并吃掉我们....我的意思是它的语法糖>>=(发音为bind)引诱我们使用monads(并享受它).所以让我们printTriangle用bind 编写:

printTriangle rows = (pascalTriangle rows) >>= (\row -> 
                     putStrLn $ show row)
Run Code Online (Sandbox Code Playgroud)

好的,这很简单.现在我们看到有什么问题吗?好吧,让我们来看看类型.绑定的类型是什么?霍格尔说:(>>=) :: Monad m => m a -> (a -> m b) -> m b.好的,谢谢Hoogle.所以基本上,bind需要一个monad类型包装一个类型的个性,一个将一个类型转换为一个类型的函数(相同)包含一个b型个性的monad类型,并以包含一个类型的(相同)monad类型结束 - b个性.

所以在我们这里printTriangle,我们有什么?

  • pascalTriangle rows :: [[Int]]- 所以我们的monad是[],而且个性是[Int]
  • (\row -> putStrLn $ show row) :: [Int] -> IO () - 这里的monad是IO,个性是 ()

好吧,废话.当Hoogle告诉我们必须匹配我们的monad类型时,Hoogle非常清楚,相反,我们给出>>=了一个列表monad,以及一个生成IO monad的函数.这使得Haskell表现得像个小孩子:它闭上眼睛,在地板上st脚尖叫"不!不!不!" 甚至不会看你的程序,更不用说编译它了.


那么我们如何安抚Haskell呢?好吧,其他人已经提到了mapM_.向顶级函数添加显式类型签名也是一个好主意 - 它有时可以帮助您更快地获得编译错误,而不是以后(并且你会得到它们;毕竟这是Haskell :)),这使得它更容易理解错误消息.

我建议写一个函数将你[[Int]]变成一个字符串,然后单独打印出来.通过将转换分离为IO-nastiness中的字符串,这将允许您继续学习Haskell而不必担心mapM_和朋友,直到您做好并准备好.

showTriangle :: [[Int]] -> String
showTriangle triangle = concatMap (\line -> show line ++ "\n") triangle
Run Code Online (Sandbox Code Playgroud)

要么

showTriangle = concatMap (\line -> show line ++ "\n")
Run Code Online (Sandbox Code Playgroud)

然后printTriangle更容易:

printTriangle :: Int -> IO ()
printTriangle rows = putStrLn (showTriangle $ pascalTriangle rows)
Run Code Online (Sandbox Code Playgroud)

要么

printTriangle = putStrLn . showTriangle . pascalTriangle
Run Code Online (Sandbox Code Playgroud)