我想这里的每个人都已经看过其中一个(或者至少是一个类似的)问题,我仍然需要问,因为我无法在任何地方找到这个问题的答案(主要是因为我不知道究竟是什么应该寻找)
我写了这个小脚本,其中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]
如果有人能向我解释为什么我在这里所做的事情不起作用(说实话,我不确定我在这里做了什么;我习惯了命令式语言,这整个函数式编程都是仍然让我感到困惑),以及我怎么能以一种不那么愚蠢的方式做到这一点。
提前致谢。
答案 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,个性是()
>>=
一个列表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打印的三角形的可打印字符串。