2011-01-02 5 views
1

나는 하스켈에 간단한 XML 파서를 작성했다. convertXML 함수는 XML 파일의 내용을 받아서 처리 된 추출 값의 목록을 반환합니다.IO 코드로 순수 함수를 확장 할 수 있습니까?

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 기능을 다시 작성해야 할 방법을 어떤 생각?

업데이트 2 최종 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

하스켈의 유형 시스템의 전체 점은 IO 작업 - IO 유형의 값 a를 제외하고는 IO를 수행 할 수 없다는 것입니다. 이를 위반하는 방법이 있지만 최적화 및 지연 평가와의 상호 작용으로 인해 기대했던 것과는 완전히 다른 행동을 할 위험이 있습니다. IO가 왜 작동하는지 이해하기 전까지는 다른 방식으로 작동하도록하지 마십시오.

그러나이 디자인의 매우 중요한 결과는 IO 작업이 최우선 순위라는 것입니다. 영리의 비트와 함께,이 같은 함수를 작성할 수

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

, 실행될 때, 현재 이미지의 목록을 제공 것 인 IO 작업이 될 것이다 쌍의 두 번째 항목입니다. 이렇게하면 convertXML 외부에 이미지로드 코드가있을 필요가 없으므로 실제로 이미지가 필요한 경우에만 IO를 수행 할 수 있습니다.

+0

두 번째 인수로 IO 작업 유형의 "값"을 반환하는 것이 좋습니다. 어떤 방법으로 내 -> ([String], [String]) 버전을 -> ([String], IO [String])로 업데이트 할 수 있습니까? –

+1

글쎄, 'String'이 이미지의 올바른 유형이라고 생각하지 않습니다. URL에 의해 리턴 된 바이트를 원할 경우 String이 아닌 표현을 위해 'ByteString'을 사용하고자 할 것입니다. 문자열은 문자 데이터 용이며 유니 코드 코드 포인트를 포함합니다. ByteString은 바이너리 형식의 이미지 일 수 있으므로 바이트 시퀀스를 효율적으로 처리하기위한 것입니다. URL을 취하는'fetch :: String -> IO ByteString'과 같은 함수가 주어지면 URL 목록을 IO action으로 변환하여'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 값을 여기에 붙일 수 없다"는 것이 확실히 알려줍니다. –

관련 문제