简体   繁体   English

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

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

I have a Haskell program which takes a file as an input and convert it into a binary search tree. 我有一个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

However, when I run the following command: 但是,当我运行以下命令时:

ins 5 fileRead 

I got the following error: 我收到以下错误:

<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

Please does anyone can help me? 请有人帮我吗?

Thanks 谢谢

You'd be able to see the problem right away if you supplied fileRead with a type signature. 如果您提供带有类型签名的fileRead ,您将能够立即看到问题。 Let's figure out the type annotation that GHC will internally assign to fileRead : 让我们弄清楚GHC将在内部分配给fileRead的类型注释:

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

lstToTree :: Ord a => [a] -> Tree a , and read always returns a member of the Read typeclass. lstToTree :: Ord a => [a] -> Tree aread总是返回Read类型类的成员。 So t :: (Read a, Ord a) => Tree a . 所以t :: (Read a, Ord a) => Tree a The concrete type depends on the contents of the file. 具体类型取决于文件的内容。

return wraps its argument in a monad, so return t has the type Ord a, Read a => IO (Tree a) . return将其参数包装在monad中,因此return t的类型为Ord a, Read a => IO (Tree a) Since return t is the final statement in the do block, it becomes the return type of fileRead , so 由于return tdo块中的最后一个语句,因此它成为fileRead的返回类型,所以

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

So fileRead is a Tree wrapped in an IO , and you can't pass it directly into ins because it expects a Tree on its own. 所以fileRead是一个包含在IOTree ,你不能直接将它传递给ins因为它需要一个Tree自己。 You can't take the Tree out of the IO , but you can 'lift' the function ins into the IO monad. 您无法将TreeIO取出,但您可以将函数ins “提升”到IO monad中。

Control.Monad exports liftM :: Monad m => (a -> r) -> (ma -> mr) . Control.Monad导出liftM :: Monad m => (a -> r) -> (ma -> mr) It accepts a regular function, and turns it into one that acts on monads like IO . 它接受一个常规函数,并将其转换为一个像IO一样的monad。 It's actually a synonym for fmap (in the standard Prelude), since all monads are functors. 它实际上是fmap的同义词(在标准Prelude中),因为所有monad都是fmap函数。 So this code, roughly equivalent to @us202's, takes the result of fileRead , inserts 5 , and gives you back the result wrapped in an IO . 所以这个代码,大致相当于@ us202的,需要的结果fileRead ,插入5 ,并给你回包裹在一个结果IO

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

I'd recommend the fmap version. 我推荐fmap版本。 This code only makes use of the fact that IO is a functor, so using liftM implies to the reader that you might need it to be a monad too. 此代码仅使用IOliftM的事实,因此使用liftM意味着读者可能还需要它作为monad。

'Lifting' is the general technique for using pure functions on values wrapped in monads or functors. “提升”是在包含在monad或functor中的值上使用纯函数的一般技术。 If you're unfamiliar with lifting (or if you're confused by monads and functors in general), I heartily recommend chapters 11-13 of Learn You A Haskell . 如果你不熟悉解除(或者如果你对monad和functor感到困惑),我衷心地推荐Learn You A Haskell的第11-13章。


PS. PS。 Note that the last two lines of fileRead should probably be combined, since return doesn't really do anything: 请注意, fileRead的最后两行可能应该合并,因为return实际上没有做任何事情:

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

Or, since it's a short enough function, you could do away with do notation altogether and use fmap again: 或者,因为它是一个足够短的功能,你可以破除do完全符号和使用fmap再次:

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

Edit in response to your comment: 编辑以回应您的评论:

Haskell is deliberately designed to keep code that performs IO separate from regular code. Haskell是故意设计让执行IO与普通的代码代码分离。 There's a very good philosophical reason for this: most Haskell functions are "pure" - that is, their output depends only on the input, just like functions in maths. 这有一个非常好的哲学原因:大多数Haskell函数都是“纯粹的” - 也就是说,它们的输出仅取决于输入,就像数学中的函数一样。 You can run a pure function a million times and you'll always get the same result. 你可以运行一百万次的纯函数,你总能得到相同的结果。 We like pure functions because they can't accidentally break other parts of your program, they permit laziness, and they allow the compiler to aggressively optimise your code for you. 我们喜欢纯函数,因为它们不会意外地破坏程序的其他部分,它们允许懒惰,并且它们允许编译器为您积极地优化代码。

Of course, in the real world we need a little bit of impurity. 当然,在现实世界中我们需要一点点杂质。 IO code like getLine can't possibly be pure (and a program that doesn't do IO is useless!). getLine这样的IO代码不可能是纯粹的(并且不执行IO的程序是无用的!)。 The result of getLine depends on what the user typed: you could run getLine a million times and get a different string every time. getLine的结果取决于用户键入的内容:您可以运行getLine一百万次并且每次都获得不同的字符串。 Haskell leverages the type system to label impure code with the type IO . Haskell利用类型系统来标记IO类型的不纯代码。

Here's the crux of the matter: if you use a pure function on data obtained impurely then the result is still impure, because the outcome depends on what the user did . 问题的关键在于:如果对不纯粹获得的数据使用纯函数,那么结果仍然不纯,因为结果取决于用户的行为 So the whole calculation belongs in the IO monad. 所以整个计算都属于IO monad。 When you want to bring a pure function into IO you have to lift it, either explicitly (using fmap ) or implicitly (with do notation). 当你希望把一个纯粹的功能分为IO必须抬起它,明确地(使用fmap )或隐式(带do记号)。

This is a really common pattern in Haskell - look at my version of fileRead above. 这是Haskell中一个非常常见的模式 - 看看我上面的fileRead版本。 I've used fmap to operate on impure IO data with a pure function. 我使用fmap来操作具有纯函数的不纯IO数据。

You can't really escape the IO monad (except through unsafe functions) but there's no actual need to do that in your case: 你不能真正逃脱IO monad(除了通过不安全的函数),但在你的情况下没有实际需要这样做:

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

(live demo: here ) (现场演示: 这里

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

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