Haskell 中的 IO 不像您习惯的语言中的 IO 那样工作。Haskell 中的所有函数都必须是纯函数:也就是说,如果f
使用参数调用函数x
,则调用一次、两次或一百次必须没有区别。不过,考虑一下这对 IO 意味着什么。天真地,getLine
应该有 type getLine :: String
,或者也许getLine :: () -> String
. (()
是单位类型,它的唯一值是()
;它有点像类 C 语言中的 void 类型,但它只有一个值。)但这意味着每次你写getLine
时,它都必须返回相同的字符串,这不是你想要的。这是IO
类型的目的:封装动作. 这些动作不同于功能;它们代表不纯的计算(尽管它们本身是纯的)。type 的值IO a
表示一个动作,它在执行时返回一个 type 的值a
。因此,getLine
has type getLine :: IO String
: 每次评估动作时,String
都会产生 a (通过从用户那里读取)。同样,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:它实际上有一个更通用的类型,但目前不相关。