2013-03-26 130 views
3

我有一个Haskell程序,它将文件作为输入并将其转换为二进制搜索树。Haskell IO:将IO字符串转换为“其他类型”

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 

请没有人能帮助我吗?

感谢

回答

4

你真的无法逃脱IO单子(除了通过不安全的功能),但有没有实际需要做的是,在你的情况:

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

(现场演示:here

+0

谢谢,但我希望用户输入他想添加到列表中的任何元素。我不希望元素被修复 – modafarhan 2013-03-26 17:00:24

+0

然后修改上面的内容:通过'getLine'读取元素并使用它,添加某种循环来添加从标准输入读取的许多元素,无论您喜欢什么。 – us2012 2013-03-26 17:02:59

8

如果您使用类型签名提供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 t是在do块中的最后声明,就成了fileRead返回类型,所以

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

所以fileReadTree包裹在一个IO,因为它希望你不能直接将它传递到ins一个Tree自己。您不能将Tree移出IO,但您可以将'抬起'功能ins转换为IO monad。

Control.Monad exports liftM :: Monad m => (a -> r) -> (m a -> m r)。它接受一个常规功能,并将它变成一个像IO这样的单子。它实际上是fmap(在标准Prelude中)的同义词,因为所有单子都是仿函数。因此,这个代码大致相当于@ us202,代码结果为fileRead,插入5,并返回包含在IO中的结果。我想推荐fmap版本。此代码仅使用IO是仿函数的事实,因此使用liftM意味着读者可能需要它成为monad。

“升降”是在monad或函数中包装的值上使用纯函数的一般技术。如果你不熟悉提升(或者如果你对单子和函子感到困惑),我衷心推荐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纯数据。

+1

嗯。事情是,我真正希望能够做的是产生一个可以调用的函数并返回一个Tree a类型的元素。即它读取文本文件中的列表,通过lstToTree或类似方法生成树,然后返回该树,以便用户可以运行类似于ins的其他函数。这可以直接完成吗? – modafarhan 2013-03-27 12:13:44

+0

@ MohammedAl-Farhan - 这是一个非常好的问题,要做到这一点,正义将需要600多个字符。我已经更新了我的答案。 – 2013-03-28 02:58:56