2011-01-02 17 views
1

私は単純なXMLパーサーをHaskellに書いた。 関数convertXMLは、XMLファイルの内容を受け取り、さらに処理される抽出値のリストを返します。IOコードで純関数を拡張することは可能ですか?

XMLタグの1つの属性にも商品画像の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 

にIOコードを挿入する方法任意のアイデアはgetImgName機能または私は完全に不純なバージョンにconvertXML機能を書き換えなければならないのですか?

UPDATE II convertXML関数の最終バージョンです。ハイブリッド純粋な/不純なしかしクリーンな方法は、カールによって示唆された。返されるペアの2番目のパラメータは、ダウンロードしてディスクに保存するイメージを実行し、イメージが格納されているローカルパスのリストをラップする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

ハスケルのタイプシステムの全体的なポイントは、IOアクション - IOaタイプの値を除いてはIOを実行できないことです。これに違反する方法はありますが、最適化や遅延評価とのやりとりのため、予想通りの動作をするリスクがあります。ですからIOがなぜ機能するのかを理解するまで、別の方法で動作させるようにしないでください。

しかし、この設計の非常に重要な結果は、IOアクションが第一級であることです。賢さのビットを使用すると、このようあなたの関数を書くことができ:

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

ペアの2番目の項目が実行され、IOアクションとなり、現在の画像のリストを与えるだろう。そうすれば、convertXMLの外にイメージローディングコードを持つ必要がなくなり、実際にイメージが必要な場合にのみIOを実行できます。

+0

IOアクションタイプの "値"を第2引数として返すのはいい考えです。どのように私の - >([String]、[String])バージョンを - >([String]、IO [String])に更新するかのヒント? –

+1

さて、私は、 'String'はイメージの正しいタイプではないと思います。 URLによって返されるバイトが必要であると仮定すると、文字列ではなく、表現に 'ByteString'を使いたいとします。文字列は文字データ用で、Unicodeコードポイントが含まれます。 ByteStringは、バイナリ形式のイメージである可能性があるため、一連のバイトを効率的に処理するためのものです。 URLを取る 'fetch :: String - > IO ByteString'のような関数を与えられた場合、URLのリストをIOアクションに変換して' mapM fetch urls'でそれらをフェッチすることができます。 – Carl

+0

Carlさんに感謝します。私はIO [String]へのアクセスを意図していましたが、イメージのURLのリストを返し、それらをディスクにダウンロードすることは "副作用"として呼び出されます。 –

2

私は基本的なアプローチを参照してください。

  1. は、機能があまりにも見つかった画像のリストを与え、その後、不純な機能とそれらを処理してみましょう。怠惰は残りをするでしょう。
  2. 私は一般的に多くの最初のアプローチを好む全体獣不純

してください。

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

をし、別の関数でそれらをダウンロードします。d

+0

アドバイスをいただきありがとうございます。私はタプルの余分なリストとしてurlのリストを返す関数を書き直すつもりです。それを "ワンパス"で行うには。 URLの追加リストを作成して処理することなく、私は不純なバージョンが解決策であると思いますか? –

+0

最適化は、リストを作成する典型的なビルドを削除するのに十分なほど巧妙です。効率性について心配しないでください。 – fuz

+1

これは心配ですが、最初は計算上の複雑さ(O(n^2)ではなくO(n))に注意を払うだけです。クリーンでシンプルなバージョンを稼動させてからプロファイラを使って過度の時間を費やしていることを示すだけで、プロデューサ/コンシューマ間のオーバーヘッドなどの一定の要因について心配しています。 –

4

より良いアプローチは、関数が結果の一部としてダウンロードするファイルのリストを返す持っているだろう。

+1

モジュール化は優れており、「IO値をここに固着できない」と言っていることは間違いありません。 –

関連する問題