我完全是Haskell的初学者,虽然熟悉Python、F#、Java、C#和C++等语言中的函数式编程范式(在有限的程度上)。
有一件事情总是让我困扰,那就是Haskell中的IO。我尝试了多次,甚至在尝试解决这个问题期间学习了C#和F#。
更具体地说,我指的是在没有使用do符号时进行IO操作,在使用do符号后,IO变得微不足道。这可能是不好的实践方式,但在我的业余时间里,我喜欢看看是否可以在一个连续的表达式中完成任务。尽管这样做是不好的实践方式,但很有趣。
这样的表达式通常是这样的(伪Haskell代码):
main = getStdinContentsAsString
>>= ParseStringToDataStructureNeeded
>>= DoSomeComputations
>>= ConvertToString
>>= putStrLn
我对后面四部分没有问题。我学习F#的原因之一就是想看看除了IO外是否还有什么东西我没有理解,但是一旦我使用方便的Console.ReadLine()函数返回一个普通的字符串,基本上就很顺利了。
这让我重新尝试了Haskell,但再次被IO机制卡住了。
我已经成功(使用这里的另一个问题)从控制台读取一个整数,并打印出“Hello World!”这么多次。
main = (readLn :: IO Int) >>= \n -> mapM_ putStrLn $ replicate n "Hello World!"
我希望至少能够以一种“通用”的方式读取stdin的全部内容(可能包括多行),所以getContents函数是我的首选,然后我可以使用其他函数如unlines和map来处理字符串。
我已经尝试了一些方法:
正如我所说,除非有等价的方法,否则我需要getContents函数。
使用逻辑,因为
getContents :: IO String
然后我需要一个将IO字符串转换为普通字符串的函数。据我所知,这就是它的功能。
unsafePerformIO :: IO a -> a
然而由于某些原因,ghc并不满意:
* Couldn't match type `[Char]' with `IO (IO b)'
Expected type: String -> IO b
Actual type: IO (IO b) -> IO b
* In the second argument of `(>>=)', namely `unsafePerformIO'
In the expression: getContents >>= unsafePerformIO
我尝试了另一件事情:这个没有问题地工作;
main = getContents >>= putStrLn
尽管getContents返回的类型是IO动作,而不是putStrLn需要的字符串本身。
getContents :: IO String
putStrLn :: String -> IO ()
不知何故,操作会自动执行,并将生成的字符串传递到put函数中。
但是当我尝试添加一些内容时,例如仅在打印之前附加“ hello”:
main = getContents >>= (++ " hello") >>= putStrLn
我突然遇到了类型不匹配的问题:
Couldn't match type `[]' with `IO'
Expected type: String -> IO Char
Actual type: [Char] -> [Char]
* In the second argument of `(>>=)', namely `(++ " hello")'
In the first argument of `(>>=)', namely
`getContents >>= (++ " hello")'
In the expression: getContents >>= (++ " hello") >>= putStrLn
一些IO操作不再执行了(或者我只是不理解)。
我也尝试了很多事情,使用了
getLine
,readLn
,getContents
,unsafePerformIO
,read
,fmap
的组合,但都没有成功。这只是一个非常基本的例子,但它完美地说明了让我放弃Haskell几次的问题(可能不只是我一个人),尽管想要理解这个几乎是“函数式编程语言之王”的倔强使我一直回来。
总之:
1. 我有什么没搞懂的吗?(99%是)
2. 如果是的话,那是什么?
3. 我应该如何阅读整个stdin并在一个连续的表达式中处理它?(如果我只需要一行,我猜无论解决方案是什么,它也会与getLine一起工作,因为它基本上是getContents的姊妹)
提前感谢!
IO
单子中的值和简单值。如果您执行m >>= f
,则传递给f
的是包装在IO
中的内容。f
具有签名f :: a-> IO b
,因此它会生成另一个(可能是相同的)包装在IO
单子中的值。 因此,getContents >>= (++ " hello")
没有意义,因为(++ "hello")
不返回IO b
。 但是,您可以使用getContents >>= putStrLn . (++ " hello")
。 - Willem Van OnsemgetContents >>= (++ " hello")
中,应该将String
(或[Char]
) 作为参数传递到(++ " hello")
中,对吗?(++ " hello")
的类型为:: [Char] -> [Char]
,因此如果将一个字符串传递给(++ " hello")
,问题在哪里? - Rares Dimainteract
函数。main = interact (++ " hello")
- 4castledo
符号写出您的函数,然后手动展开它,或者思考一下您手动使用>>=
编写的表达式在do
符号中会是什么样子。例如,您错误的表达式getContents >>= (++ " hello") >>= putStrLn
对应于do
符号do { x <- getContents; y <- x ++ " hello"; putStrLn y }
,这清楚地说明了错误所在:您有一个y <-
,因此右侧必须是一个IO
操作,但实际上它只是一个String
。解决方案之一是将其包装在pure
(或return
)中,使其成为一个操作。 - Jon Purdy