2011-01-02 4 views
1

Ich habe einen einfachen XML-Parser in Haskell geschrieben. Die Funktion convertXML empfängt den Inhalt einer XML-Datei und gibt eine Liste extrahierter Werte zurück, die weiterverarbeitet werden.Erweiterung der reinen Funktion um IO-Code möglich?

Ein Attribut des XML-Tags enthält auch eine URL eines Produktbilds und ich möchte die Funktion erweitern, um es auch herunterzuladen, wenn das Tag gefunden wird. Ich muss komplett neu schreiben convertXML Funktion zu einer unreinen Version

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 

Jede Idee, wie in der getImgName Funktion oder tun einen IO-Code einfügen?

UPDATE II Endgültige Version der convertXML-Funktion. Hybride rein/unreine aber saubere Art vorgeschlagen von Carl. Der zweite Parameter des zurückgegebenen Paars ist eine E/A-Aktion, die das Herunterladen und Speichern von Bildern auf Festplatte ausführt und die Liste lokaler Pfade, in denen Bilder gespeichert sind, umschließt.

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 

Antwort

3

Der gesamte Punkt des Typsystems in Haskell ist, dass Sie IO nur mit IO-Aktionen tun können - Werte vom Typ IO a. Es gibt Möglichkeiten, dies zu verletzen, aber sie laufen Gefahr, sich aufgrund von Interaktionen mit Optimierungen und einer faulen Evaluierung völlig anders zu verhalten als erwartet. Bevor Sie also verstehen, warum IO so funktioniert, wie Sie es tun, versuchen Sie nicht, es anders funktionieren zu lassen.

Aber eine sehr wichtige Konsequenz dieses Entwurfs ist, dass IO-Aktionen erstklassig sind. Mit ein wenig Klugheit, können Sie Ihre Funktion wie diese schreiben könnte:

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

Das zweite Element in dem Paar würde ein IO-Aktion sein, die, wenn sie ausgeführt werden, präsentieren eine Liste der Bilder geben würde. Dies würde die Notwendigkeit vermeiden, Bildladecode außerhalb von convertXML zu haben, und es würde Ihnen erlauben, IO nur dann auszuführen, wenn Sie die Bilder tatsächlich benötigen.

+0

Das klingt nach einer netten Idee, um "Wert" des IO-Aktionstyps als zweites Argument zurückzugeben. Irgendwelche Hinweise, wie man meine -> ([String], [String]) Version auf -> ([String], IO [String]) aktualisiert? –

+1

Nun, ich glaube nicht, dass "String" der richtige Typ für ein Bild ist. Angenommen, Sie möchten die Bytes, die von der URL zurückgegeben werden, möchten Sie 'ByteString' für die Darstellung verwenden, nicht String. String ist für Zeichendaten und enthält Unicode-Codepunkte. ByteString ist für die effiziente Behandlung von Bytefolgen, wie es ein Bild in binärer Form sein könnte. Bei einer Funktion wie 'fetch :: String -> IO ByteString', die eine URL akzeptiert, können Sie eine Liste von URLs in eine IO-Aktion konvertieren, um sie mit' mapM fetch urls' zu holen. – Carl

+0

Danke Carl. Ich beabsichtigte Zugriff auf IO [String] wird die Liste der URLs von Bildern zurückgeben und auf die Festplatte herunterladen wird als "Nebeneffekt" aufgerufen. –

2

Ich sehe grundsätzlich Ansätze: danach

  1. lassen Sie die Funktion auch eine Liste der gefundenen Bilder geben und sie mit einer unreinen Funktion verarbeiten. Faulheit wird den Rest erledigen.
  2. Machen Sie das ganze Tier unrein

ich der erste Ansatz allgemein gerne. d

+0

Danke für die Ratschläge. Ich werde die Funktion umschreiben, um eine Liste von URLs als eine zusätzliche Liste in einem Tupel zurückzugeben. Um es in einem "One-Pass" zu tun, dh. ohne eine zusätzliche Liste von URLs zu erstellen und zu verarbeiten, denke ich, dass nur eine unreine Version die Lösung ist? –

+0

Die Optimierungen sind oft clever genug, um das typische Build-It-Verhalten zu entfernen. Mach dir keine Sorgen über Effizienz. – fuz

+1

Nun ... mach dir darüber Sorgen, aber achte nur auf die Rechenkomplexität (O (n) statt O (n^2)).Sorgen Sie sich um konstante Faktoren wie den Overhead von Produzenten und Verbrauchern, nachdem Sie die reine und einfache Version zum Laufen gebracht haben, und verwenden Sie den Profiler, um zu demonstrieren, dass es zu viel Zeit in Anspruch nimmt. –

4

Der bessere Ansatz wäre die Funktion zu haben, um die Liste der Dateien zurück, wie ein Teil des Ergebnisses zum Download:

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

und sie in einer separaten Funktion herunterladen.

+1

Modularisierung ist gut, und genau das sagt Ihnen die "Unfähigkeit, hier einen IO-Wert festzuhalten". –

Verwandte Themen