Возможно ли расширение чистой функции с помощью кода IO? - PullRequest
1 голос
/ 02 января 2011

Я написал простой парсер XML на Haskell. Функция 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 или мне нужно полностью переписать функцию convertXML в нечистую версию?

ОБНОВЛЕНИЕ II Окончательная версия функции convertXML. Гибрид чистый / нечистый, но чистый способ, предложенный Карлом. Второй параметр возвращаемой пары - это действие ввода-вывода, которое запускает загрузку и сохранение изображений на диск и переносит список локальных путей, где хранятся изображения.

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 ]

4 голосов
/ 02 января 2011

Лучше было бы, чтобы функция возвращала список файлов для загрузки как часть результата:

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

и загружала их в отдельную функцию.

3 голосов
/ 02 января 2011

Весь смысл системы типов в Haskell состоит в том, что вы не можете выполнять IO, кроме как с помощью действий IO - значений типа IO a. Есть способы нарушить это, но они рискуют вести себя совершенно не так, как вы ожидаете, из-за взаимодействия с оптимизацией и ленивой оценкой. Поэтому, пока вы не поймете, почему IO работает так, как он работает, не пытайтесь заставить его работать по-другому.

Но очень важным следствием этого дизайна является то, что действия ввода-вывода являются первоклассными. С некоторой сообразительностью вы можете написать свою функцию так:

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

Вторым элементом в паре будет действие ввода-вывода, которое при выполнении выдаст список имеющихся изображений. Это избавило бы от необходимости иметь код загрузки изображения вне convertXML, и это позволило бы вам выполнять ввод-вывод, только если вам действительно нужны изображения.

2 голосов
/ 02 января 2011

Я в основном вижу подходы:

  1. пусть функция выдает список найденных изображений, а затем обрабатывает их нечистой функцией.Лень сделает все остальное.
  2. Сделайте зверя целым нечистым

Мне вообще больше нравится первый подход.д

...