繁体   English   中英

Haskell IO:将IO字符串转换为“其他类型”

[英]Haskell IO: convert IO String to “Other type”

我有一个Haskell程序,它将文件作为输入并将其转换为二叉搜索树。

import System.IO    

data Tree a = EmptyBST | Node a (Tree a) (Tree a) deriving (Show, Read, Eq)

ins :: Ord a => a -> (Tree a) -> (Tree a)
ins a EmptyBST                  = Node a EmptyBST EmptyBST
ins a (Node p left right)
    | a < p                             = Node p (ins a left) right
    | a > p                             = Node p left (ins a right)
    | otherwise                             = Node p left right



lstToTree :: Ord a => [a] -> (Tree a)
lstToTree                   = foldr ins EmptyBST

fileRead                    = do    file    <- readFile "tree.txt"
                            let a = lstToTree (conv (words file))
                            return a

conv :: [String] -> [Int]
conv                        = map read

但是,当我运行以下命令时:

ins 5 fileRead 

我收到以下错误:

<interactive>:2:7:
    Couldn't match expected type `Tree a0'
                with actual type `IO (Tree Int)'
    In the second argument of `ins', namely `fileRead'
    In the expression: ins 5 fileRead
    In an equation for `it': it = ins 5 fileRead

请有人帮我吗?

谢谢

如果您提供带有类型签名的fileRead ,您将能够立即看到问题。 让我们弄清楚GHC将在内部分配给fileRead的类型注释:

fileRead = do file <- readFile "tree.txt"
              let t = lstToTree $ map read $ words file
              return t

lstToTree :: Ord a => [a] -> Tree aread总是返回Read类型类的成员。 所以t :: (Read a, Ord a) => Tree a 具体类型取决于文件的内容。

return将其参数包装在monad中,因此return t的类型为Ord a, Read a => IO (Tree a) 由于return tdo块中的最后一个语句,因此它成为fileRead的返回类型,所以

fileRead :: (Read a, Ord a) => IO (Tree a)

所以fileRead是一个包含在IOTree ,你不能直接将它传递给ins因为它需要一个Tree自己。 您无法将TreeIO取出,但您可以将函数ins “提升”到IO monad中。

Control.Monad导出liftM :: Monad m => (a -> r) -> (ma -> mr) 它接受一个常规函数,并将其转换为一个像IO一样的monad。 它实际上是fmap的同义词(在标准Prelude中),因为所有monad都是fmap函数。 所以这个代码,大致相当于@ us202的,需要的结果fileRead ,插入5 ,并给你回包裹在一个结果IO

liftM (ins 5) fileRead
-- or --
fmap (ins 5) fileRead

我推荐fmap版本。 此代码仅使用IOliftM的事实,因此使用liftM意味着读者可能还需要它作为monad。

“提升”是在包含在monad或functor中的值上使用纯函数的一般技术。 如果你不熟悉解除(或者如果你对monad和functor感到困惑),我衷心地推荐Learn You A Haskell的第11-13章。


PS。 请注意, fileRead的最后两行可能应该合并,因为return实际上没有做任何事情:

fileRead :: (Read a, Ord a) => IO (Tree a)
fileRead = do file <- readFile "tree.txt"
           return $ lstToTree $ map read $ words file

或者,因为它是一个足够短的功能,你可以破除do完全符号和使用fmap再次:

fileRead :: (Read a, Ord a) => IO (Tree a)
fileRead = fmap (lstToTree . map read . words) (readFile "tree.txt")

编辑以回应您的评论:

Haskell是故意设计让执行IO与普通的代码代码分离。 这有一个非常好的哲学原因:大多数Haskell函数都是“纯粹的” - 也就是说,它们的输出仅取决于输入,就像数学中的函数一样。 你可以运行一百万次的纯函数,你总能得到相同的结果。 我们喜欢纯函数,因为它们不会意外地破坏程序的其他部分,它们允许懒惰,并且它们允许编译器为您积极地优化代码。

当然,在现实世界中我们需要一点点杂质。 getLine这样的IO代码不可能是纯粹的(并且不执行IO的程序是无用的!)。 getLine的结果取决于用户键入的内容:您可以运行getLine一百万次并且每次都获得不同的字符串。 Haskell利用类型系统来标记IO类型的不纯代码。

问题的关键在于:如果对不纯粹获得的数据使用纯函数,那么结果仍然不纯,因为结果取决于用户的行为 所以整个计算都属于IO monad。 当你希望把一个纯粹的功能分为IO必须抬起它,明确地(使用fmap )或隐式(带do记号)。

这是Haskell中一个非常常见的模式 - 看看我上面的fileRead版本。 我使用fmap来操作具有纯函数的不纯IO数据。

你不能真正逃脱IO monad(除了通过不安全的函数),但在你的情况下没有实际需要这样做:

main = do f <- fileRead
          let newtree = ins 5 f
          putStr $ show newtree

(现场演示: 这里

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM