3

我想知道 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)

请帮忙。

谢谢。

4

4 回答 4

8

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

请注意,我只进行了两次更改(并删除了一个空行)。首先,我改变了函数的类型,就像我上面说的那样。其次,我变成showread. 该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块,这是你从一个动作中提取的一个纯值,因此只在块内可见。你需要在外面的世界看到它之前把它举起来。/也是如此。returnusernamedoIOamounttempamount

只是为了完整起见:这背后有一些总体理论将它们联系在一起。但是对于开始 Haskell 编程来说,完全没有必要。我建议做的是将您的大部分代码构建为折叠、旋转和破坏数据的纯函数。IO然后构建一个与所述功能交互的薄(尽可能薄)的前层。您会惊讶地发现您需要的 IO 如此之少!

1:它实际上有一个更通用的类型,但目前不相关。

于 2010-06-18T03:59:08.173 回答
4

是的,你快到了,但我认为你想要签名:

investinput :: IO ([Char], Int)

...然后从调用函数中,您可以执行以下操作:

main = do
    (username, amount) <- investinput
    ....

我认为您想阅读临时量而不是显示。

于 2010-06-18T03:15:50.743 回答
2

生成元组的 IO 函数将具有 type IO (a, b),在这种情况下:

investinput :: IO ([Char], Int)

的签名IO () -> ([Char], Int)意味着该函数采用类型参数IO ()并从中生成一个元组,这不是您想要的。

通常对 IO 函数(或不同 monad 中的函数)可以返回的类型没有限制,您可以选择任何您喜欢的类型。

于 2010-06-18T03:21:43.633 回答
1

(String, Int)关于返回而不是返回的问题的答案IO (String, Int)很简单:你不能。一旦你进去,IO你就会被困在那里。这就是人们说 Haskell 是一种“纯”语言时的部分含义。

您想要做的与您在这里已经在做的类似getLine。的类型getLineIO String。当你写 时username <- getLine,你实际上是从 中String取出IO String,但这只是可能的,因为你在do表达式中。

investinput你可以用as with做同样的事情getLine。这是一个如何investinputmain函数中使用的示例:

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“另一个需要元组的函数”。

于 2010-06-18T04:03:53.797 回答