可以使用IO代码扩展纯函数吗?

时间:2011-01-02 13:22:29

标签: haskell types io monads referential-transparency

我在Haskell中编写了一个简单的XML解析器。 函数 convertXML 接收XML文件的内容,并返回进一步处理的提取值列表。

XML标记的一个属性还包含产品图像的URL,如果找到标记,我想扩展该功能以便下载它。

convertXML ::  (Text.XML.Light.Lexer.XmlSource s) => s -> [String]
convertXML xml = productToCSV products
    where
        productToCSV [] = []
        productToCSV (x:xs) = (getFields x) ++ (productToCSV
                                (elChildren x)) ++ (productToCSV xs)
        getFields elm = case (qName . elName) elm of
                            "product" -> [attrField "uid", attrField "code"]
                            "name" -> [trim $ strContent elm]
                            "annotation" -> [trim $ strContent elm]
                            "text" -> [trim $ strContent elm]
                            "category" -> [attrField "uid", attrField "name"]
                            "manufacturer" -> [attrField "uid",
                                                attrField "name"]
                            "file" -> [getImgName]
                            _ -> []
            where
                attrField fldName = trim . fromJust $
                                        findAttr (unqual fldName) elm
                getImgName = if (map toUpper $ attrField "type") == "FULL"
                                then
                                    -- here I need some IO code
                                    -- to download an image
                                    -- fetchFile :: String -> IO String
                                    attrField "file"
                                else []
        products = findElements (unqual "product") productsTree
        productsTree = fromJust $ findElement (unqual "products") xmlTree
        xmlTree = fromJust $ parseXMLDoc xml

知道如何在 getImgName 函数中插入IO代码,还是必须将convertXML函数完全重写为不纯版本?

更新II convertXML函数的最终版本。 Carl建议的混合纯/不纯但干净的方式。返回对的第二个参数是一个IO动作,它运行图像下载并保存到磁盘并包装存储图像的本地路径列表。

convertXML ::  (Text.XML.Light.Lexer.XmlSource s) => s -> ([String], IO [String])
convertXML xml = productToCSV products (return [])
    where
        productToCSV :: [Element] -> IO String -> ([String], IO [String])
        productToCSV [] _ = ([], return [])
        productToCSV (x:xs) (ys) = storeFields (getFields x)
                            ( storeFields (productToCSV (elChildren x) (return []))
                                (productToCSV xs ys) )
        getFields elm = case (qName . elName) elm of
                            "product" -> ([attrField "uid", attrField "code"], return [])
                            "name" -> ([trim $ strContent elm], return [])
                            "annotation" -> ([trim $ strContent elm], return [])
                            "text" -> ([trim $ strContent elm], return [])
                            "category" -> ([attrField "uid", attrField "name"], return [])
                            "manufacturer" -> ([attrField "uid",
                                                attrField "name"], return [])
                            "file" -> getImg
                            _ -> ([], return [])
            where
                attrField fldName = trim . fromJust $
                                        findAttr (unqual fldName) elm
                getImg = if (map toUpper $ attrField "type") == "FULL"
                            then
                                ( [attrField "file"], fetchFile url >>=
                                    saveFile localPath >>
                                    return [localPath] )
                                else ([], return [])
                    where
                        fName = attrField "file"
                        localPath = imagesDir ++ "/" ++ fName
                        url = attrField "folderUrl" ++ "/" ++ fName

        storeFields (x1s, y1s) (x2s, y2s) = (x1s ++ x2s, liftM2 (++) y1s y2s)
        products = findElements (unqual "product") productsTree
        productsTree = fromJust $ findElement (unqual "products") xmlTree
        xmlTree = fromJust $ parseXMLDoc xml

3 个答案:

答案 0 :(得分:4)

更好的方法是让函数返回要作为结果的一部分下载的文件列表:

convertXML ::  (Text.XML.Light.Lexer.XmlSource s) => s -> ([String], [URL])

并在单独的功能中下载它们。

答案 1 :(得分:3)

Haskell中类型系统的整个要点是除了IO操作之外你不能做IO - 类型IO a的值。有一些方法可以违反这一点,但由于与优化和懒惰评估的相互作用,它们存在完全不同于您所期望的行为风险。因此,除非您理解IO为何如此工作,否则不要试图使其工作方式不同。

但这种设计的一个非常重要的结果是IO动作是一流的。有点聪明,你可以编写你的函数:

convertXML ::  (Text.XML.Light.Lexer.XmlSource s) => s -> ([String], IO [Image])

该对中的第二项将是一个IO动作,在执行时,将给出一个存在的图像列表。这样可以避免在convertXML之外使用图像加载代码,并且只有在您真正需要图像时才允许您执行IO。

答案 2 :(得分:2)

我基本上看到了方法:

  1. 让函数给出一个已找到图像的列表,然后用不纯的函数处理它们。懒惰会做其余的事。
  2. 让整个野兽不纯洁
  3. 我通常更喜欢第一种方法。 d