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

时间:2011-11-11 22:01:09

标签: 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

现在由于受过训练的眼睛可能显而易见的原因,但对我来说完全笼罩在神秘之中,当我尝试在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

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

printTriangle 3

我得到了这个输出:

[1]
[1,1]
[1,2,1]

如果有人能向我解释为什么我在这里所做的事情不起作用(说实话,我不确定我在这里做了什么;我习惯了命令式语言,这整个函数式编程都是仍然让我感到困惑),以及我怎么能以一种不那么愚蠢的方式做到这一点。

提前致谢。

4 个答案:

答案 0 :(得分:6)

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

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

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

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

printTriangle
printTriangle rows = (pascalTriangle rows) >>= (\row -> 
                     putStrLn $ show row)

好的,那很简单。现在我们看到有什么问题吗?好吧,让我们来看看类型。绑定的类型是什么?霍格尔说:(>>=) :: 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类型时,我们非常清楚,相反,我们给了>>=一个列表monad,以及一个生成IO monad的函数。这使得Haskell表现得像个小孩子:它闭上眼睛,在地板上st脚尖叫“不!不!不!”甚至不会看你的程序,更不用说编译它了。


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

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

showTriangle :: [[Int]] -> String
showTriangle triangle = concatMap (\line -> show line ++ "\n") triangle

showTriangle = concatMap (\line -> show line ++ "\n")

然后printTriangle更容易:

printTriangle :: Int -> IO ()
printTriangle rows = putStrLn (showTriangle $ pascalTriangle rows)

printTriangle = putStrLn . showTriangle . pascalTriangle

答案 1 :(得分:4)

如果您想在新行上打印列表元素,您应该会看到this问题。

所以

printTriangle rows  = mapM_ print $ pascalTriangle rows

λ> printTriangle 3
[1]
[1,1]
[1,2,1]

最后,你所要求的似乎是mapM_

答案 2 :(得分:3)

每当我在Haskell中编码时,我总是尝试声明至少顶级定义的类型。它不仅可以记录您的功能,还可以更轻松地捕获类型错误。所以pascalTriangle有以下类型:

pascalTriangle :: Int -> [[Int]]

当编译器看到这些行时:

row<-(triangle)
...
where 
triangle = pascalTriangle rows

它会推断出三角形有类型:

triangle :: [[Int]]

&lt; - 运算符期望它的右手参数是monad。因为您声明了您的函数在IO monad上工作,所以编译器期望三角形具有类型:

triangle :: IO something

显然与[[Int]]类型不匹配。这就是编译器试图用它自己扭曲的方式来表达的东西。

正如其他人所说,这种编码风格不是惯用的Haskell。它看起来像我在早​​期的Haskell时代会产生的那种代码,当时我还有一种“以命令为导向”的思想。如果你试图抛开势在必行的思维方式,并开始思考功能风格,你会发现你可以以非常优雅和整洁的方式解决你的大部分问题。

答案 3 :(得分:0)

从ghci-prompt中尝试以下内容:

> let {pascal 1 = [1]; pascal n = zipWith (+) (l++[0]) (0:l) where l = pascal (n-1)}
> putStr $ concatMap ((++"\n") . show . pascal) [1..20]

你的代码非常unidiomatic Haskell。在Haskell中,您使用更高阶函数来构建其他函数。这样你就可以编写非常简洁的代码。

在这里,我使用zipWith懒惰地组合两个列表,以产生下一行帕斯卡三角形,就像你手动计算它一样。然后使用concatMap生成由putStr打印的三角形的可打印字符串。