1

我在 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 函数完全重写为不纯的版本?

更新二 convertXML 函数的最终版本。卡尔建议的混合纯/不纯但干净的方式。返回对的第二个参数是一个 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
4

3 回答 3

4

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

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

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

于 2011-01-02T13:28:33.370 回答
3

Haskell 中类型系统的全部要点是,除了 IO 操作(类型为 IO a 的值)之外,您不能执行 IO。有一些方法可以违反这一点,但由于与优化和惰性评估的交互,它们的行为可能与您所期望的完全不同。因此,在您了解 IO 为何以它的方式工作之前,不要试图让它以不同的方式工作。

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

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

该对中的第二项将是一个 IO 操作,当执行该操作时,将给出一个存在图像的列表。这将避免在 convertXML 之外有图像加载代码的需要,并且仅当您确实需要图像时才允许您执行 IO。

于 2011-01-02T15:19:43.730 回答
2

我基本上看到了方法:

  1. 让函数也给出找到的图像列表,然后用不纯的函数处理它们。懒惰会做剩下的事情。
  2. 让整个野兽变得不洁

我通常更喜欢第一种方法。d

于 2011-01-02T13:27:44.250 回答