Haskell输入返回元组

时间:2010-06-18 02:57:59

标签: haskell io interactive

我想知道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 个答案:

答案 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更改为readshow函数的类型为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。对于任何xreturn x是一项操作,在运行时始终生成x。这就是您必须使用do结束return阻止的原因:username是您从动作中提取的纯值,因此仅在do中可见块。在外界看到它之前,您需要将其提升到IOamount / 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时,您实际上已将StringIO String中删除,但这只是因为您位于do表达式内。

您可以使用investinputgetLine完全相同的事情。以下是您在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与“需要元组的另一个函数”一起使用。