Haskell 中的 IO 与您习惯的语言中的 IO 不同。 Haskell 中的所有函数都必须是纯函数:也就是说,如果一个函数f
被调用时带有参数x
,调用一次、两次、一百次肯定没有区别。不过,考虑一下这对 IO 意味着什么。天真地说,getLine
应该有类型getLine :: String
, 也许getLine :: () -> String
. (()
是单位类型,其唯一值为()
;它有点像类 C 语言中的 void 类型,但它只有一个值。)但这意味着每次你写getLine
,它必须返回相同的字符串,这不是你想要的。这就是该组织的目的IO
类型:封装actions。这些动作与功能不同;他们代表impure计算(尽管它们本身是纯粹的)。类型的值IO a
表示一个动作,执行时返回一个类型的值a
。因此,getLine
有类型getLine :: IO String
:每次评估动作时,String
生成(通过从用户处读取)。相似地,putStr
有类型putStr :: String -> IO ()
;它是一个函数,它接受一个字符串并返回一个操作,该操作在运行时不会返回任何有用的信息……但是,作为副作用,会在屏幕上打印一些内容。
您正在尝试编写类型的函数IO () -> ([Char], Int)
。这将是一个函数,它将一个动作作为输入并返回一个元组,即not你想要什么。你想要一个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
into read
. The show
函数具有类型Show a => a -> String
:它是一个函数,它接受任何可以显示的内容并生成表示它的字符串。You wanted read
,其类型为Read a => String -> a
:给定一个字符串,它会解析它并返回一些可读的值。
The other thing you asked about is returning a tuple (String, Int)
instead of an action IO (String, Int)
. There is no pure way to do this; in other words, there is no pure function IO a -> a
. Why is this? Because IO a
represents an impure action which depends on the real world. If we had such a function impossibleRunIO :: IO a -> a
, then we would want it to be the case that impossibleRunIO getLine == impossibleRunIO getLine
, since the function must be pure. But this is useless, as we would want impossibleRunIO
to be able to actually interact with the real world! Thus, this pure function is impossible. Everything that enters IO
can never leave. This is what return
does: it is a function with, in this case1, the type return :: a -> IO a
, which enables you to place pure values into IO
. For any x
, return x
is an action which, when run, always produces x
. This is why you have to end your do
block with the return
: username
is a pure value you extracted from an action, and as such is only visible within the do
block. You need to lift it into IO
before the outside world can see it. The same is true of amount
/tempamount
.
为了完整起见:这背后有一些总体理论将其联系在一起。但对于开始 Haskell 编程来说根本没有必要。我建议你将大部分代码构建为纯函数,这些函数会折叠、旋转和破坏你的数据。然后构建一个薄的(尽可能薄的)IO
与所述功能交互的前层。您会惊讶地发现您需要的 IO 如此之少!
1:它实际上有一个更通用的类型,但目前不相关。