【问题标题】:Haskell Input Return TupleHaskell 输入返回元组
【发布时间】:2011-03-05 06:18:03
【问题描述】:

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

请帮忙。

谢谢。

【问题讨论】:

  • 尝试删除类型签名,将show 更改为read,并在添加read 的行末尾添加:: Int。然后在 GHCi 中加载它,试一试,并询问带有:t 的类型。它不会是你在这里所拥有的。

标签: haskell io interactive


【解决方案1】:

Haskell 中的 IO 不像您习惯的语言中的 IO 那样工作。 Haskell 中的所有函数都必须是纯函数:也就是说,如果函数fx 为参数调用,那么调用它一次、两次或一百次必须没有区别。不过,考虑一下这对 IO 意味着什么。天真地,getLine 应该具有 getLine :: String 类型,或者可能是 getLine :: () -&gt; String。 (() 是单位类型,其唯一值是 ();它有点像类 C 语言中的 void 类型,但它只有一个值。)但这意味着每次你写getLine,它必须返回相同的字符串,这不是你想要的。这就是IO 类型的目的:封装动作。这些动作不同于功能;它们代表不纯计算(尽管它们本身是纯的)。 IO a 类型的值表示一个操作,该操作在执行时返回 a 类型的值。因此,getLine 具有getLine :: IO String 类型:每次评估操作时,都会生成String(通过从用户处读取)。同样,putStr 的类型为 putStr :: String -&gt; IO ();它是一个函数,它接受一个字符串并返回一个动作,该动作在运行时不会返回任何有用的信息……但是,作为副作用,会在屏幕上打印一些东西。

您正在尝试编写IO () -&gt; ([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 =&gt; a -&gt; String 类型:它是一个函数,它接受任何可以显示的内容并生成一个表示它的字符串。 想要read,它的类型是Read a =&gt; String -&gt; a:给定一个字符串,它会解析它并返回一些可读的值。

你问的另一件事是返回一个元组(String, Int),而不是一个动作IO (String, Int)。没有纯粹的方法可以做到这一点。换句话说,没有纯函数IO a -&gt; a。为什么是这样?因为IO a 代表了一个依赖于现实世界的不纯行为。如果我们有这样的函数impossibleRunIO :: IO a -&gt; a,那么我们希望它是impossibleRunIO getLine == impossibleRunIO getLine,因为函数必须是纯函数。但这没有用,因为我们希望impossibleRunIO 能够与现实世界进行实际交互!因此,这个纯函数是不可能的。任何进入IO 的东西都不能离开。这就是return 所做的:它是一个函数,在本例中是1,类型为return :: a -&gt; IO a,它使您能够将纯值放入IO。对于任何xreturn x 是一个动作,它在运行时总是产生x。这就是为什么您必须以 return 结束您的 do 块:username 是您从操作中提取的纯值,因此仅在 do 块中可见。您需要将其提升到IO 才能让外界看到它。 amount/tempamount也是如此。

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

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

【讨论】:

  • 其实我想要的是这样的东西 test = savefileinvestinput 投资输入 IO 字符串传递给 savefile 以保存investinput :: IO ([Char], Int) main = do (username,金额)
  • 我不太明白你写的内容,但我会尽力回复。您说您不希望main 具有IO () 类型。让我们想想main 是什么。它代表了你的整个程序,所以它一定是某种不纯的动作;并且因为它是你的整个程序,它不能返回任何有用的东西。因此,它必须具有IO () 类型。要求它有另一种类型就像要求你的 C 编译器接受double main(char* x, void (*y)())——这是被禁止的。如果这还没有足够的信息,您为什么不编辑您的问题以解释您要做什么?
  • 您的观点当然是正确的,但根据报告,main 只需要具有“对于某些类型 t 的类型 IO t”。所以main = getLine 是一个有效(且无用)的程序。这与 peterwkc 的问题完全无关,但这是一个有趣的小事实,当我遇到它时让我感到惊讶。
【解决方案2】:

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

investinput :: IO ([Char], Int)

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

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

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

【讨论】:

  • 感谢它有效,但为什么这种类型签名不起作用。投资输入 :: IO() -> ([Char], Int)
  • 我需要返回元组而不是返回 IO 元组。 nvestinput :: IO([Char], Int)investinput = do putStrLn "输入用户名:" username
  • 我开始尝试使用 IO 盒类比来描述这里发生的事情,但我认为快速描述并没有真正的帮助。没有什么可以替代阅读 monad 来理解为什么 IO ([Char], Int) 是这种类型。 book.realworldhaskell.org/read 是一个很好的资源
【解决方案3】:

产生元组的 IO 函数的类型为 IO (a, b),在这种情况下:

investinput :: IO ([Char], Int)

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

一般来说,IO 函数(或不同 monad 中的函数)可以返回的类型没有限制,你可以选择你喜欢的类型。

【讨论】:

  • IO () -> ([Char], Int) 的签名意味着该函数采用 IO () 类型的参数并从中生成一个元组,这不是您想要的。我需要一个 IO 输入并返回元组。谢谢。这就是我想要的。
  • 不,IO () -&gt; ... 意味着您将一些 IO 函数 作为参数investinput,就像用 investinput (print 1) 调用它一样。您的函数使用从控制台读取的另一个函数(如 getLine)不会反映在类型签名中。类型签名只是说明函数调用需要什么类型的参数以及它将产生什么类型的返回值。
  • 我需要 IO() -> ([Char, Int]) 的原因是因为我有另一个函数需要一个元组来进行计算。我还看到了一些像这样的例子 lifM index (readFile "input.txt") liftM 有这个签名: liftM :: Monad m => (a -> b) -> ma -> mb 它需要一个非单子函数并转换它变成一个单子函数。 fmap index $ readFile "input.txt" readFile "input.txt" >>= return . index 请帮忙。
  • @peterwkc:如果有另一个函数需要一个元组,那么这个函数应该把一个元组作为参数吗?这如何影响此函数采用的参数?查看问题中函数的定义,它不带任何参数,因此它的签名也不能包含任何参数。
  • 在investinput 中执行IO 后,investinput 应该将元组返回给另一个函数。请帮忙。谢谢。
【解决方案4】:

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

您想要做的与您在此处使用getLine 所做的类似。 getLine 的类型是 IO String。当您编写 username &lt;- getLine 时,实际上是在将 StringIO String 中取出,但这只有在您位于 do 表达式中时才有可能。

你可以用investinputgetLine 做同样的事情。下面是如何在 main 函数中使用 investinput 的示例:

main = do
  (name, amount) <- investinput
  putStrLn $ name ++ " invested $" ++ show amount ++ "."

既然您在评论中提到了liftM,这里有一个完整的工作版本,它使用liftM 和反向绑定运算符(=&lt;&lt;)而不是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 与“另一个需要元组的函数”一起使用。

【讨论】:

  • 我尝试了代码,但它没有编译。 import Monad -- 接收 IO 输入做某事汇总 :: ([Char], [Char]) -> String summary (name, amount) = putname ++ "invested $" ++ amount ++ "." investinput :: IO ()investinput = do putStrLn "输入用户名:" username
  • 谢谢。你能解释一下这几行代码吗? liftM 摘要投资输入 什么是 liftM ?它说将函数提升为monad。这是什么意思?
  • 我想要 maxinvestinput 的输出作为 maximuminvest 的输入。谢谢。 maxinvest = liftM maximuminvest maxinvestinput maxinvestinput :: IO() maxinvestinput = do str [(String, Integer)] converttuple (x:y:z) = (x, read y):converttuple z getint :: [(String, Integer)] -> [Integer] getint (x:xs) = snd (x) : getint xs maximuminvest :: (Ord a) => [a] -> a maximuminvest (x:xs) | x > 最大尾 = x |否则 = maxTail 其中 maxTail = maximuminvest xs
猜你喜欢
  • 2019-03-12
  • 1970-01-01
  • 1970-01-01
  • 2014-04-02
  • 1970-01-01
  • 1970-01-01
  • 2012-10-24
  • 2020-06-26
  • 2023-04-10
相关资源
最近更新 更多