如何将数据从IO读入数据结构然后处理数据结构?

时间:2011-03-05 17:40:05

标签: haskell

首先抱歉做了“我从哪里开始”的典型事情,但我完全迷失了。

我一直在阅读'了解你是一个伟大的好人'网站,感受现在感觉像是一个时代(差不多半个学期。我即将完成'输入和输出'一章,我仍然不知道如何编写多行程序。

我已经看过do语句了,你只能使用它来将IO动作连接到一个函数中,但我看不出我将如何编写一个真实的应用程序。

有人能指出我正确的方向。

我来自C背景,基本上我在本学期的uni中使用haskell作为我的一个模块,我想比较C ++和haskell(在很多方面)。我正在寻找创建一系列搜索和排序程序,以便我可以评论它们在各自语言中的容易程度与它们的速度。

然而,我真的开始放弃使用Haskell已经六周了,我仍然不知道如何编写一个完整的应用程序,我正在阅读的网站中的章节似乎得到了越来越长。

我基本上需要创建一个将存储在结构中的基本对象(我知道该怎么做),更多我正在努力的是,如何创建一个从某些文本文件中读取数据的程序,并首先使用该数据填充结构,然后继续处理它。由于haskell似乎拆分IO和其他操作,它不会让我在程序中写多行,我正在寻找这样的东西:

main = data <- getContent
       let allLines = lines data
       let myStructure = generateStruct allLines
       sort/search/etc
       print myStructure

我该如何解决这个问题?任何有助于我使用现实程序的好教程?

-A

4 个答案:

答案 0 :(得分:10)

您提到了do符号,现在是学习如何使用do的时候了。考虑一下您的示例mainIO,您应该使用do语法或绑定:

main = do
  dat <- getContent
  let allLines = lines dat
      myStructure = generateStruct allLines
      sorted = mySort myStructure
      searchResult = mySearch myStructure
  print myStructure
  print sorted
  print searchResult

所以现在你有一个获得stdin的main,通过[String]将其转换为lines,可能会将其解析为一个结构并对该结构进行排序和搜索。请注意,有趣的代码都是纯粹的 - mySortmySearch,而generateStruct不需要是IO(也不能是,在let绑定中)所以你实际上是正确使用纯粹而有效的代码。

我建议你看看bind是如何工作的(>>=)以及如何将符号desugars绑定到bind中。 This SO question应该有所帮助。

答案 1 :(得分:4)

另见Neil Mitchell的Explaining Haskell IO without Monads

答案 2 :(得分:4)

我将尝试从一个简化的例子开始。让我们说这就是我们想要做的事情:

  1. 打开一个包含整数列表的文件并将其返回。
  2. 对此列表进行排序
  3. 让我们也反转清单
  4. 在屏幕上打印结果
  5. 我们还要说我们可以使用这些功能:

    getContent :: IO [Int]
    sort :: [Int] -> [Int]
    reverse :: [Int] -> [Int]
    show :: a -> String
    putStrLn :: String -> IO ()
    

    我们很清楚,我会对这些功能有所了解:

    • getContent:我编写了这个函数,但是如果有这样的函数 将是它的签名(你可以使用getContent = return [3,7,2,1]进行测试)。我确定你之前见过这样的签名,至少含糊地理解,因为它确实是IO,它的签名不能只是getContent :: [Int]
    • sort:这是Data.List模块中定义的函数,用法很简单:sort [3,1,2]返回[1,2,3]
    • reverse:也在Data.List模块中定义:reverse [1,3,2]返回[2,3,1]
    • show:不需要导入任何内容,只需使用它:show 11返回字符串"11"; show [1,2,3]返回字符串"[1,2,3]"
    • putStrLn:接受一个字符串,将其放在屏幕上并再次返回IO(),因为它执行IO,其签名不能只是putStrLn :: Stiring -> ()

    好的,现在我们已经拥有创建程序所需的一切,现在的问题是将这些功能连接在一起。让我们从连接函数开始:

    带有getContent :: IO [Int]

    sort :: [Int] -> [Int]

    我想如果你得到这个部分,你也可以很容易地得到其余部分。因此,问题是,由于getContent返回IO [Int]而不仅仅是[Int],因此您不能忽略或删除IO部分并将其推入{{} 1}}。也就是说,这是无法连接这些功能所做的事情:

    sort

    这是 sort (getRidOfIO getContent) 操作拯救的地方。现在请注意,>>= :: m a -> (a -> m b) -> m bma类型变量,因此如果我们将b替换为m,{{1} } IOa [Int],我们得到了信号:

    b

    再次查看这些[Int]>>= :: IO [Int] -> ([Int] -> IO [Int]) -> IO [Int]函数及其签名,并尝试考虑它们如何适合getContent。我相信你会注意到你可以直接使用sort作为>>=的第一个参数。到目前为止,getContent将执行>>= >>=并将其推送到作为第二个参数提供的函数中。但是第二个论点中的功能是什么?我们不能直接使用[Int],我们可以尝试的下一个最好的事情是

    getContent

    但是仍然有签名sort :: [Int] -> [Int],所以没有多大帮助。这是另一位英雄来到剧中的地方,

    \listOfInts -> sort listOfInts

    同样,[Int] -> [Int]return :: a -> m a是类型变量,让我们替换它们,我们会得到

    a

    所以我们将mreturn :: [Int] -> IO [Int]加在一起,我们会得到:

    \listOfInts -> sort listOfInts

    这正是我们想要作为return的第二个参数。因此,最后使用我们的粘合剂连接\listOfInts -> return $ sort listOfInts :: [Int] -> IO [Int]>>=

    getContent

    与(使用sort表示法)相同:

    getContent >>= (\listOfInts -> return $ sort listOfInts)

    那里,这是最可怕部分的结束。现在可能是其中一个 aha 时刻,试着考虑我们刚刚组成的连接的结果类型是什么。我会为你破坏它,......

    的类型

    do再次do listOfInts <- getContent return $ sort listOfInts

    让我们总结一下:我们采用getContent >>= (\listOfInts -> return $ sort listOfInts)类型的东西和IO [Int]类型的东西,将这两个东西粘在一起,再次得到IO [Int]类型的东西!

    现在继续尝试完全相同的事情:获取我们刚刚创建的[Int] -> [Int]对象,并将其IO [Int]IO [Int]>>=粘合在一起。

    我认为我写得太多了,但如果有什么不清楚或者你需要其他人的帮助,请告诉我。

    到目前为止,我所描述的内容可能是这样的:

    return

答案 3 :(得分:1)

如果这是一个从stdin阅读并将结果写入stdout的问题,而没有进一步的用户输入 - 正如您提到getContents所暗示的那样 - 那么古代interact :: (String -> String) -> IO ()或其他几个版本,例如只需要Data.ByteString.interact :: (ByteString -> ByteString) -> IO ()Data.Text.interact :: (Text -> Text) -> IO()interact基本上是'使用此函数创建一个小的unix工具'功能 - 它将正确类型的函数映射到可执行操作(即{{1}类型的值所有Haskell教程都应该在第三或第四页上提及它,并附有编译说明。

所以,如果你写

IO()

并使用main = interact arthur arthur :: String -> String arthur = reverse 进行编译,然后管道到ghc --make -O2 Reverse.hs -o reverse的任何内容都将被理解为字符列表并反转出现。同样,无论你管道什么

./reverse

将在省略空行的情况下出现。更有趣的是,

main = interact (unlines . meredith  . lines)

meredith :: [String] -> [String]
meredith = filter (not.null)

将使用换行符分隔的字符流,将其读取为main = interact ( unlines . map show . luther . map read . lines) luther :: [Int] -> [Int] luther = filter even s,删除奇数字符,然后生成适当过滤的流。

Int

将打印换行符分隔数字的平方和。

在最后两种情况下,main = interact ( unlines . map show . emma . map read . lines) emma :: [Int] -> Int emma = sum . map square where square x = x * x luther内部'数据结构'是[Int],这是非常沉闷的,当然,应用它的函数很简单。重点是让emma中的一种形式处理所有IO,从而获得像“填充结构”和“处理它”的图像。要使用interact,您需要使用合成来使整个产生某种interact函数。但即使在这里,就像在第一个例子String -> String中那样,你在更像数学意义的东西中定义了一个真正的函数。类型arthur:: String -> StringString中的值与ByteStringBool中的值一样纯。

在这个基本Int类型的更复杂的情况下,首先,您的任务是思考您将要关注的函数的所需值如何映射到interact值(此处,对于Int,它只是String,对于show,它只是unlines . map show[Int]知道用字符串“做”的内容。 - 然后找出如何定义一个纯映射从Strings或ByteString(它将包含你的'原始'数据)到你的主要函数作为参数的类型或类型的值。在这里,我只使用interact生成map read . lines。如果您正在处理一些更复杂的树状结构,那么您需要一个从[Int][Int]的函数。当然,更精细的功能就是解析器。

然后你可以去城里,在这种情况下:根本没有理由把自己想象为“编程”,“填充”和“处理”。这就是MyTree Int所有酷炫设备的用武之地。您的职责是定义特定定义学科内的映射。在最后两种情况中,这些案例来自LYAH[Int]以及[Int][Int],但这里是一个类似的例子,来源于excellent, still incomplete, tutorial super-excellent Vector package正在处理的初始数字结构是Int

Vector Int

这里再次{-# LANGUAGE BangPatterns #-} import qualified Data.ByteString.Lazy.Char8 as L import qualified Data.Vector.Unboxed as U import System.Environment main = L.interact (L.pack . (++"\n") . show . roman . parse) where parse :: L.ByteString -> U.Vector Int parse bytestr = U.unfoldr step bytestr step !s = case L.readInt s of Nothing -> Nothing Just (!k, !t) -> Just (k, L.tail t) -- now the IO and stringy nonsense is out of the way -- so we can calculate properly: roman :: U.Vector Int -> Int roman = U.sum 是愚蠢的,从Int的矢量到Int的任何函数,无论多么复杂,都可以取代它。写一个更好的roman永远不会是“填充”“多行编程”“处理”等问题,当然我们这样说;这只是通过Data.Vector和其他地方的函数组合来定义真正函数的问题。天空是极限,请查看教程。