我想知道IO()函数是否可以返回元组,因为我想将这些函数作为另一个函数的输入。
investinput :: IO()->([Char], Int)
investinput = do
putStrLn "Enter Username : "
username <- getLine
putStrLn "Enter Invest Amount : "
tempamount <- getLine
let amount = show tempamount
return (username, amount)
请帮忙。
感谢。
答案 0 :(得分:8)
Haskell中的IO在您习惯的语言中不像IO那样工作。 Haskell中的所有函数都必须是纯粹的:也就是说,如果使用参数f
调用函数x
,则调用它一次,两次或一百次之间必须没有区别。考虑一下这对IO意味着什么。天真地,getLine
应该具有getLine :: String
类型,或者getLine :: () -> String
类型。 (()
是单位类型,其唯一值为()
;它有点像C语言中的void类型,但它有一个值。)但这意味着每次你写getLine
时,它都必须返回相同的字符串,这不是你想要的。这是IO
类型的目的:封装操作。这些行动与功能不同;它们代表不纯的计算(虽然它们本身是纯粹的)。类型IO a
的值表示一个操作,该操作在执行时返回类型a
的值。因此,getLine
具有类型getLine :: IO String
:每次评估操作时,都会生成String
(通过向用户读取)。同样,putStr
的类型为putStr :: String -> IO ()
;它是一个函数,它接受一个字符串并返回一个动作,当它运行时,它不返回任何有用的信息......但是,作为副作用,它会在屏幕上打印一些东西。
您正在尝试编写IO () -> ([Char], Int)
类型的函数。这将是一个函数,它将一个动作作为输入并返回一个元组,这是不你想要的。你想要一个IO (String, Int)
- 一个动作,当它运行时,产生一个由字符串(它是[Char]
的同义词)和一个整数组成的元组。你的当前代码也差不多了!这就是你需要的东西:
investinput :: IO (String, Int)
investinput = do
putStrLn "Enter Username : "
username <- getLine
putStrLn "Enter Invest Amount : "
tempamount <- getLine
let amount = read tempamount
return (username, amount)
请注意,我只进行了两次更改(并删除了一个空行)。首先,我已经改变了函数的类型,就像我上面所说的那样。其次,我将show
更改为read
。 show
函数的类型为Show a => a -> String
:它是一个函数,它接受任何可以显示的函数并生成表示它的字符串。 您想要read
,其类型为Read a => String -> a
:给定一个字符串,它会解析它并返回一些可读值。
您询问的另一件事是返回元组(String, Int)
而不是操作IO (String, Int)
。没有纯粹的方法可以做到这一点;换句话说,没有纯函数IO a -> a
。为什么是这样?因为IO a
代表了一种依赖于现实世界的不纯净行为。如果我们有这样的函数impossibleRunIO :: IO a -> a
,那么我们希望它是impossibleRunIO getLine == impossibleRunIO getLine
的情况,因为函数必须是纯的。但这没用,因为我们希望impossibleRunIO
能够真实地与现实世界互动!因此,这种纯粹的功能是不可能的。进入IO
的所有内容都永远不会离开。这是return
的作用:它是一个函数,在本例中为 1 ,类型为return :: a -> IO a
,它允许您将纯值放入IO
。对于任何x
,return x
是一项操作,在运行时始终生成x
。这就是您必须使用do
结束return
阻止的原因:username
是您从动作中提取的纯值,因此仅在do
中可见块。在外界看到它之前,您需要将其提升到IO
。 amount
/ tempamount
也是如此。
只是为了完整起见:这背后有一些总体理论将它联系在一起。但是开始Haskell编程根本没有必要。我建议你做的是将大部分代码构造成纯粹的函数,这些函数可以折叠,控制和毁坏你的数据。然后构造一个薄的(尽可能薄的)IO
前层,它与所述函数相互作用。你会感到惊讶的是你需要多少IO!
1:它实际上有一个更通用的类型,但目前并不相关。
答案 1 :(得分:4)
是的,你差不多了,但我觉得你想签名:
investinput :: IO ([Char], Int)
...然后从调用函数中你可以执行以下操作:
main = do
(username, amount) <- investinput
....
我认为你想阅读tempamount而不是显示。
答案 2 :(得分:2)
生成元组的IO函数将具有类型IO (a, b)
,在这种情况下:
investinput :: IO ([Char], Int)
IO () -> ([Char], Int)
的签名意味着该函数采用IO ()
类型的参数并从中生成一个元组,这不是您想要的。
一般来说,IO功能(或不同monad中的功能)可以返回的类型没有限制,你可以选择你喜欢的类型。
答案 3 :(得分:1)
关于回复(String, Int)
而不是IO (String, Int)
的问题的答案很简单:您不能。一旦你进入IO
,你就会被困在那里。当人们说Haskell是一种“纯粹的”语言时,这就是它的意思。
您想要做的事情与您在getLine
处所做的相似。 getLine
的类型为IO String
。当您撰写username <- getLine
时,您实际上已将String
从IO String
中删除,但这只是因为您位于do
表达式内。
您可以使用investinput
与getLine
完全相同的事情。以下是您在investinput
函数中使用main
的示例:
main = do
(name, amount) <- investinput
putStrLn $ name ++ " invested $" ++ show amount ++ "."
由于您在评论中提及liftM
,因此这是一个完整的工作版本,它使用liftM
和反向绑定运算符(=<<
)而不是do
执行相同的操作符号:
import Control.Monad (liftM)
investinput :: IO (String, Int)
investinput = do
putStrLn "Enter Username : "
username <- getLine
putStrLn "Enter Invest Amount : "
amount <- liftM read getLine -- We can also use liftM to get rid of tempamount.
return (username, amount)
summary :: (String, Int) -> String
summary (name, amount) = name ++ " invested $" ++ show amount ++ "."
main = putStrLn =<< liftM summary investinput
这显示了如何将investinput
与“需要元组的另一个函数”一起使用。