获取功能getLine
- 它具有以下类型:
getLine :: IO String
我如何String
从这个IO
动作中提取?
更一般地说,我如何转换它:
IO a
对此:
a
如果这是不可能的,那我为什么不能这样做?
在 Haskell 中,当您想要使用“困”在 中IO
的值时,您不会将值从IO
. 相反,您也将要执行的操作放入IO
!
例如,假设您想使用PreludegetLine :: IO String
中的函数检查将产生多少字符。length
存在一个名为的辅助函数fmap
,当专门用于 时IO
,它的类型为:
fmap :: (a -> b) -> IO a -> IO b
它需要一个函数,该函数适用于未陷入的“纯”值IO
,并为您提供一个函数,该函数适用于陷入的值IO
。这意味着代码
fmap length getLine :: IO Int
表示IO
从控制台读取一行然后给你它的长度的动作。
<$>
是一个中缀同义词fmap
,可以让事情变得更简单。这相当于上面的代码:
length <$> getLine
现在,有时您要使用IO
-trapped 值执行的操作本身会返回IO
-trapped 值。简单的例子:你想用putStrLn :: String -> IO ()
.
在那种情况下,fmap
是不够的。您需要使用(>>=)
运算符,当它专门用于 时IO
,它的类型为IO a -> (a -> IO b) -> IO b
。万一:
getLine >>= putStrLn :: IO ()
使用(>>=)
to 链式IO
操作具有命令式的、顺序的风格。有一种称为“do-notation”的语法糖有助于以更自然的方式编写这样的顺序操作:
do line <- getLine
putStrLn line
请注意,<-
here 不是运算符,而是 do 表示法提供的语法糖的一部分。
不谈任何细节,如果你在一个do
块中,你可以(非正式地/不准确地)考虑<-
从 IO 中获取价值。
例如,以下函数从 中获取一行getLine
,并将其传递给只接受一个字符串的纯函数
main = do
line <- getLine
putStrLn (wrap line)
wrap :: String -> String
wrap line = "'" ++ line ++ "'"
如果将其编译为wrap
, 并在命令行运行
echo "Hello" | wrap
你应该看到
'Hello'
如果您知道 C,那么请考虑“我如何从 中获取字符串gets
?”这个问题。AnIO String
不是一些难以获取的字符串,它是一个可以返回字符串的过程 - 就像从网络或标准输入中读取一样。您要运行该过程以获取字符串。
按顺序运行 IO 操作的常用方法是do
符号:
main = do
someString <- getLine
-- someString :: String
print someString
在上面,您运行getLine
操作以获取一个String
值,然后根据需要使用该值。
所以“一般”,不清楚为什么你认为你需要这种类型的功能,在这种情况下,它会产生很大的不同。
为了完整起见,应该注意它是可能的。IO a -> a
在基础库中确实存在一个名为unsafePerformIO
.
但是这unsafe
部分存在是有原因的。很少有情况认为它的使用是合理的。这是一个非常谨慎使用的逃生舱口——大多数时候你会让怪物进来而不是让自己出去。
为什么你通常不能从IO a
到a
?好吧,至少它允许您通过拥有一个看似纯粹但根本不纯粹的函数来打破规则 - 哎呀!如果这样做是一种常见的做法,那么类型签名和编译器为验证它们所做的所有工作将毫无意义。所有的正确性保证都会消失。
Haskell 之所以有趣,部分原因是因为这(通常)是不可能的。
有关如何解决您的getLine
问题的具体方法,请参见其他答案。