Haskell问题:IO字符串-> [int]

9

大家好,我希望你们能成为优秀程序员。现在我正在尝试学习Haskell并遇到了一个令我困惑的函数:

import Data.List.Split
getncheck_guesslist = do
    line <- getLine
    let tmp = splitOneOf ",;" line
    map read tmp::[Int]

splitOneOf 函数在 Data.List.Split 中(我使用 cabal install split 安装了它)

splitOneOf :: (Eq a) => [a] -> [a] -> [[a]]

从错误信息可以看出,存在一些类型不匹配的问题 - 但是我不知道如何解决这些冲突,因为对于我来说,IO 还是一个谜。

我想读取由逗号或分号分隔的整数输入,并获取整数列表:

  • 如何检查用户输入是否为 Int 类型
  • 如何将类型为 "IO String" 的输入“转换”为 [Int]

感谢您提前的思考和提示 - 您的 ε/2


6
如果输入输出(IO)是个谜,你有两个选择:要么通过学习来揭开它的面纱,要么在没有IO的情况下编写函数。我建议您先采用第二种选择一段时间。 - R. Martinho Fernandes
4个回答

13
当你编写使用IO单子的函数时,你想要从函数中返回的任何值也必须在IO单子中。
这意味着,你不能仅返回类型为[Int]的值,而是必须返回类型为IO [Int]的值。为了做到这一点,需要使用return函数,它将值“封装”成IO(实际上可以用于任何单子)。
只需将最后一行更改为使用return将你的值包装起来,就像这样:
getncheck_guesslist = do
    line <- getLine
    let tmp = splitOneOf ",;" line
    return (map read tmp :: [Int])

7
在Haskell中,正确的做法是将IO与其他事物分开。您的代码的直接翻译应该是这样的:
getncheck_guesslist :: IO [Int]
getncheck_guesslist = do line <- getLine               -- get
                         return (check_guesslist line) -- check

check_guesslist :: String -> [Int]
check_guesslist line = let tmp = splitOneOf ",;" line
                       in map read tmp

请注意,getncheck_guesslist 只是一个 IO 动作。虽然它需要从 getLine 获取 (IO) 输入,但该函数没有输入 参数
另请注意,getncheck_guesslist 是对 getLine IO 动作的简单修改。有没有组合子可以让我将一个函数推到单子内部的值上?停一下,Hoogle 时间!
我有一个函数 (a -> b)。我有一个输入类型的值,但它被困在单子 m a 中。我想在单子内执行该函数,所以结果也必然被困在单子中 m b。把这些放在一起,我们搜索 (a -> b) -> m a -> m b。看哪,fmap 正是我们要找的东西。
get_guesslist = check_guesslist `fmap` getLine
-- or, taking it a step further
get_guesslist = (map read . splitOneOf ",;") `fmap` getLine :: IO [Int]

作为最后的说明,当你编写一个名为somethingAndSomethingElse的方法时,通常更好的编码风格是将somethingsomethingElse编写为两个单独的方法并调用它们。对于最终版本,我只将其重命名为get_guesslist,因为从概念上讲,它就是这样做的。它获取猜测作为Ints列表。
最后的最后,我留在了barsoap开始的地方。 ;) fmap<$>相同。

2
import Data.List.Split
import Control.Applicative

getncheck_guesslist :: IO [Int]
getncheck_guesslist = map read . splitOneOf ",;" <$> getLine

每次代码陷入到foo >>= return . bar时,你通过展开do-block(并修正类型错误)来实现,这样做只使用了monad中的functor部分,而没有使用其monadic特性。简单地说,你没有处理类型中的IO部分,而是处理了IO a中的a部分。请注意保留HTML标签。
(<$>) :: (Functor f) => (a -> b) -> f a -> f b

(fmap<$>的非中缀名称。两者与map密切相关。)

上面的代码几乎是临时代码的惯用法,但干净的代码应该像下面这样:

import Data.Maybe

maybeRead :: Read a => String -> Maybe a
maybeRead = fmap fst . listToMaybe . reads 
                               -- That fmap is using "instance Functor Maybe"

parseGuessList :: String -> [Int]
parseGuessList =  catMaybes . map maybeRead . splitOneOf ",;"

getncheck_guesslist = parseGuessList <$> getLine

或者,如果您不想忽略非整数输入但希望出现错误提示:

parseGuessList xs = if success then Just . catMaybes $ ys else Nothing 
  where ys :: String -> [Mabye Int]
        ys = map maybeRead . splitOneOf ",;" $ xs
        success = all isJust ys

请注意,我只证明了代码的正确性,并没有实际尝试过。

如果它比那更复杂,你可能需要使用一个合适的解析库。


这个超出了我的理解范围。我不会给它点踩,但我觉得原帖作者也可能没看懂。 - Zach

2
如果某个东西在 IO monad 中,你就不能将其带到纯粹的外部世界进行进一步处理。相反,你需要将纯函数传递给 IO 内部的函数来工作。
最终,你的程序会读取一些输入并输出一些输出,所以你的主函数将使用 IO,否则你将无法输出任何内容。不要读取 IO String 并创建一个 [Int],而是将消耗该 [Int] 的函数传递到主函数中,并在 do 中使用它。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接