【发布时间】:2026-01-25 14:20:23
【问题描述】:
我完全是 Haskell 的初学者,虽然熟悉 Python、F#、Java、C# 和 C++ 等语言的函数范式(在有限的程度上)。
一直在逃避我的是haskell中的IO。我尝试了几次,甚至在尝试绕过它的过程中学习了 C# 和 F#。
更具体地说,我指的是在没有 do 表示法的情况下获取 IO,使用 do 表示法,IO 变得微不足道。这可能是不好的做法,但在业余时间,我喜欢看看我是否可以用一个连续的表情完成事情。尽管这是一种糟糕的做法,但它很有趣。
这样的表达式通常是这样的(在伪haskell中):
main = getStdinContentsAsString
>>= ParseStringToDataStructureNeeded
>>= DoSomeComputations
>>= ConvertToString
>>= putStrLn
我对最后四个部分没有问题。我学习 F# 的原因之一是只是想看看除了 IO 之外是否有什么我没有想到的,但是一旦我有了方便的 F# Console.ReadLine() 它返回一个普通的旧字符串,它基本上就是一帆风顺。
这让我又回到了haskell,再次被IO机制阻止。
我已经设法(在这里使用另一个问题)从控制台读取 int,并打印“Hello World!”很多次
main = (readLn :: IO Int) >>= \n -> mapM_ putStrLn $ replicate n "Hello World!"
我想至少获得一些“通用”方式来读取标准输入的全部内容(可能是多行,所以 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 想要的 String 本身
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 的问题(而且可能我不是唯一一个),尽管我很固执地想了解它,并且了解什么是函数式编程语言让我不断回头。
总结:
有什么我没有得到的吗?(99% 是)
如果是,那是什么?
我应该如何阅读整个标准输入并在一个连续的表达式中处理它?(如果我只需要 1 行,我想无论解决方案是什么,它也可以与 getLine 一起使用,因为它基本上是getContents 的妹妹)
提前致谢!
【问题讨论】:
-
是的。您将包装在
IOmonad 中的值和简单值混合在一起。如果您执行m >>= f,则包裹在IO中的内容 将传递给f。f具有签名f :: a -> IO b,因此它会产生另一个(很可能相同)值,包装在IOmonad 中。所以getContents >>= (++ " hello")没有意义,因为(++ "hello")不会返回IO b。但是,您可以使用getContents >>= putStrLn . (++ " hello") -
@WillemVanOnsem 很抱歉,但我仍然不清楚。如果仅将 IO 中包装的内容作为参数传递,那么在
getContents >>= (++ " hello")中,String(或[Char])应该是传递给(++ " hello")的内容,对吧?而(++ " hello")的类型为:: [Char] -> [Char],所以如果将String 传递给(++ " hello"),有什么问题? -
我认为你关于 do 表示法的结论是错误的,它只是一个语法扩展。如果你不明白如何编写没有 do 符号的程序,那么你就不会理解它。
-
您似乎在寻找
interact函数。main = interact (++ " hello") -
未来可能对您有所帮助的一件事是用
do表示法编写您的函数,然后手动对其进行脱糖,或者考虑一下您使用>>=手动编写的表达式会是什么样子do符号。例如,您的错误表达式getContents >>= (++ " hello") >>= putStrLn对应于do表示法do { x <- getContents; y <- x ++ " hello"; putStrLn y },明确错误在哪里:您有y <-,因此右侧必须是IO操作,而是它只是一个String。一种解决方案是将其包装在pure(或return)中以使其成为一个动作。