2013-01-10 13 views
10

में निर्देशिका की स्ट्रीमिंग रिकर्सिव वंश स्ट्रीमिंग मैं हास्केल का उपयोग करके निर्देशिका संरचना का एक पुनरावर्ती मूल करने की कोशिश कर रहा हूं। मैं केवल आवश्यकतानुसार बाल निर्देशिका और फ़ाइलों को पुनर्प्राप्त करना चाहता हूं (आलसी)।हास्केल

मैं निम्नलिखित कोड लिखा था, लेकिन जब मैं इसे चलाने, ट्रेस पता चलता है कि सभी निर्देशिकाओं पहली फ़ाइल से पहले दौरा कर रहे हैं:

module Main where 

import Control.Monad (forM, forM_, liftM) 
import Debug.Trace (trace) 
import System.Directory (doesDirectoryExist, getDirectoryContents) 
import System.Environment (getArgs) 
import System.FilePath ((</>)) 

-- From Real World Haskell, p. 214 
getRecursiveContents :: FilePath -> IO [FilePath] 
getRecursiveContents topPath = do 
    names <- getDirectoryContents topPath 
    let 
    properNames = 
     filter (`notElem` [".", ".."]) $ 
     trace ("Processing " ++ topPath) names 
    paths <- forM properNames $ \name -> do 
    let path = topPath </> name 
    isDirectory <- doesDirectoryExist path 
    if isDirectory 
     then getRecursiveContents path 
     else return [path] 
    return (concat paths) 

main :: IO() 
main = do 
    [path] <- getArgs 
    files <- getRecursiveContents path 
    forM_ files $ \file -> putStrLn $ "Found file " ++ file 

मैं कैसे वंश के साथ फ़ाइल प्रोसेसिंग बिछा कर सकते हैं? क्या समस्या है files <- getRecursiveContents path कार्रवाई main में निम्नलिखित से पहले की जाती है?

module Main where 

import Control.Monad (forM, forM_, liftM) 
import Debug.Trace (trace) 
import System.Directory (doesDirectoryExist, getDirectoryContents) 
import System.Environment (getArgs) 
import System.FilePath ((</>)) 
import System.IO.Unsafe (unsafeInterleaveIO) 

-- From Real World Haskell, p. 214 
getRecursiveContents :: FilePath -> IO [FilePath] 
getRecursiveContents topPath = do 
    names <- unsafeInterleaveIO $ getDirectoryContents topPath 
    let 
    properNames = 
     filter (`notElem` [".", ".."]) $ 
     trace ("Processing " ++ topPath) names 
    paths <- forM properNames $ \name -> do 
    let path = topPath </> name 
    isDirectory <- doesDirectoryExist path 
    if isDirectory 
     then unsafeInterleaveIO $ getRecursiveContents path 
     else return [path] 
    return (concat paths) 

main :: IO() 
main = do 
    [path] <- getArgs 
    files <- unsafeInterleaveIO $ getRecursiveContents path 
    forM_ files $ \file -> putStrLn $ "Found file " ++ file 

वहाँ एक बेहतर तरीका है:

+2

बाद में खंड [ "फाइल सिस्टम सर्च कर रहे हैं में" ट्रेवर्सल को देखने का एक और तरीका है "कहा जाता है "] (http://book.realworldhaskell.org/read/io-case-study-a-library-for-searching-the-filesystem.html) रीयल वर्ल्ड हास्केल का अध्याय फ़ाइल को नेविगेट करने का एक और अधिक सुविधाजनक तरीका भी प्रदान करता है प्रणाली जो एक गुना और एक पुनरावर्तक का उपयोग करता है। –

+1

मैंने (स्पष्ट रूप से) आरडब्ल्यूएच से 'getRecursiveContents' समारोह लिया। मैंने बाद के खंड को नहीं देखा। मैं एक नजर मार लूगां। धन्यवाद। – Ralph

+0

आप http://hackage.haskell.org/package/FilePather – singpolyma

उत्तर

8

यह वास्तव में ऐसी समस्या है जो iteratees/coroutines को हल करने के लिए डिज़ाइन की गई थी।

आप आसानी से pipes के साथ ऐसा कर सकते हैं। आपके getRecursiveContents में किए गए एकमात्र परिवर्तन को इसे FilePath एस और respond पर फ़ाइल नाम के साथ इसे वापस करने के बजाय बनाना था। इससे getRecursiveContents पूर्ण होने की प्रतीक्षा करने के बजाय डाउनस्ट्रीम फ़ाइल नाम को तुरंत संभाल सकता है।

module Main where 

import Control.Monad (forM_, liftM) 
import Control.Proxy 
import System.Directory (doesDirectoryExist, getDirectoryContents) 
import System.Environment (getArgs) 
import System.FilePath ((</>)) 

getRecursiveContents :: (Proxy p) => FilePath ->() -> Producer p FilePath IO() 
getRecursiveContents topPath() = runIdentityP $ do 
    names <- lift $ getDirectoryContents topPath 
    let properNames = filter (`notElem` [".", ".."]) names 
    forM_ properNames $ \name -> do 
    let path = topPath </> name 
    isDirectory <- lift $ doesDirectoryExist path 
    if isDirectory 
     then getRecursiveContents path() 
     else respond path 

main :: IO() 
main = do 
    [path] <- getArgs 
    runProxy $ 
      getRecursiveContents path 
     >-> useD (\file -> putStrLn $ "Found file " ++ file) 

यह तुरंत प्रत्येक फ़ाइल बाहर प्रिंट के रूप में यह पेड़ को पार करता है, और यह आलसी IO आवश्यकता नहीं है। फ़ाइल नामों के साथ आप जो भी करते हैं उसे बदलना भी बहुत आसान है, क्योंकि आपको केवल अपने वास्तविक फ़ाइल हैंडलिंग तर्क के साथ useD चरण स्विच करना है।

pipes के बारे में अधिक जानने के लिए, मैं अत्यधिक अनुशंसा करता हूं कि आप Control.Proxy.Tutorial पढ़ लें।

+2

मैंने पाइप्स 3 के बजाय पाइप्स 4 के वर्तमान एपीआई के लिए कोड अपडेट किया है लेकिन यहां पेस्ट करना बहुत लंबा है, इसलिए मैंने इसे ग्रिस्ट किया: https://gist.github.com/FranklinChen/133cb61af931a08bbe20 – FranklinChen

2

निकलस बी द्वारा टिप्पणी के लिए धन्यवाद, यहाँ समाधान है कि मैं है?

7

आलसी आईओ/unsafe... का उपयोग जाने का एक अच्छा तरीका नहीं है। आलसी आईओ many problems का कारण बनता है, जिसमें अनजान संसाधन शामिल हैं और शुद्ध कोड के भीतर अशुद्ध कार्य निष्पादित करते हैं। (Haskell विकी पर भी The problem with lazy I/O देखें।)

कुछ सुरक्षित/गणनाकर्ता पुस्तकालय का उपयोग करने का एक सुरक्षित तरीका है। (समस्याग्रस्त आलसी आईओ को बदलना इन अवधारणाओं को विकसित करने के लिए प्रेरणा थी।) आपका getRecursiveContents डेटा का स्रोत बन जाएगा (AKA गणनाकर्ता)। और डेटा कुछ पुनरावर्तक द्वारा उपभोग किया जाएगा। (यह भी देखें हास्केल विकी पर Enumerator and iteratee।)

वहाँ a tutorial on the enumerator library कि सिर्फ traversing और छानने निर्देशिका वृक्ष का एक उदाहरण देता है, को लागू करने के लिए एक सरल लगता है उपयोगिता है। यह विधि

enumDir :: FilePath -> Enumerator FilePath IO b 

जो मूल रूप से आपको चाहिए, लागू करता है। मेरा मानना ​​है कि आपको यह दिलचस्प लगेगा। Iteratee: जॉन डब्ल्यू Lato, iteratee पुस्तकालय के लेखक द्वारा शिक्षण एक पुराने तह नई युक्तियां

इसके अलावा वहाँ एक अच्छा लेख The Monad Reader, Issue 16 में iteratees समझा है।

आज कई लोग pipes जैसे नए पुस्तकालय पसंद करते हैं। आपको तुलना में रुचि हो सकती है: What are the pros and cons of Enumerators vs. Conduits vs. Pipes?

+0

को देखना चाहते हैं मैंने आपके Instapaper खाते में दिए गए सभी संदर्भ जोड़े हैं और काम के बाद उन्हें पढ़ेंगे। धन्यवाद। – Ralph

0

मैं हाल ही में एक बहुत ही समान समस्या को देख रहा था, जहां मैं IO मोनैड का उपयोग करके कुछ जटिल खोज करने की कोशिश कर रहा हूं, मुझे उस फ़ाइल को ढूंढने के बाद रोकना है, जिसमें मुझे दिलचस्पी है। पुस्तकालयों जैसे एन्युमेरेटर, कंडिट, इत्यादि सबसे अच्छा प्रतीत होता है जब आप उन उत्तरों को पोस्ट करते थे, मैंने अभी सीखा IO एक साल पहले जीएचसी की बेस लाइब्रेरी में Alternative का उदाहरण बन गया, जो कुछ नई संभावनाएं खुलता है।

import Control.Applicative (empty) 
import Data.Foldable (asum) 
import Data.List (isSuffixOf) 
import System.Directory (doesDirectoryExist, listDirectory) 
import System.FilePath ((</>)) 

searchFiles :: (FilePath -> IO a) -> FilePath -> IO a 
searchFiles f fp = do 
    isDir <- doesDirectoryExist fp 
    if isDir 
     then do 
      entries <- listDirectory fp 
      asum $ map (searchFiles f . (fp </>)) entries 
     else f fp 

matchFile :: String -> FilePath -> IO() 
matchFile name fp 
    | name `isSuffixOf` fp = putStrLn $ "Found " ++ fp 
    | otherwise = empty 

searchFiles समारोह एक निर्देशिका वृक्ष की गहराई-पहले खोज, रोक जब यह पाता है कि तुम क्या, के लिए देख रहे के रूप में समारोह के रूप में पारित कर दिया द्वारा निर्धारित: यहाँ कोड मैं इसे आज़माने के लिए लिखा है पहला तर्क matchFile फ़ंक्शन यह दिखाने के लिए है कि searchFiles के लिए पहली तर्क के रूप में उपयोग करने के लिए उपयुक्त फ़ंक्शन कैसे बनाया जाए; वास्तविक जीवन में आप शायद कुछ और जटिल करेंगे।

यहां दिलचस्प बात यह है कि अब आप empty उपयोग कर सकते हैं बनाने के लिए एक IO गणना एक परिणाम के लौटने के बिना "छोड़ देना" है, और आप श्रृंखला संगणना asum के साथ एक साथ (जो सिर्फ foldr (<|>) empty है) में से एक है जब तक संगणना की कोशिश कर रख सकते हैं वे सफल होते हैं।

मुझे यह थोड़ा अचूक लगता है कि IO क्रिया का प्रकार हस्ताक्षर अब इस तथ्य को प्रतिबिंबित नहीं करता है कि यह जानबूझकर परिणाम नहीं दे सकता है, लेकिन यह सुनिश्चित करता है कि कोड को सरल बना दिया जाए। मैं पहले IO (Maybe a) जैसे प्रकारों का उपयोग करने की कोशिश कर रहा था, लेकिन ऐसा करने से कार्यों को लिखना बहुत मुश्किल हो गया।

आईएमएचओ IO (Maybe a) जैसे किसी प्रकार का उपयोग करने के लिए अब और अधिक कारण नहीं है, लेकिन यदि आपको उस प्रकार का उपयोग करने वाले कोड के साथ इंटरफेस करने की आवश्यकता है, तो यह दो प्रकार के बीच परिवर्तित करना आसान है। IO (Maybe a) करने के लिए IO a बदलने के लिए, तुम सिर्फ Control.Applicative.optional उपयोग कर सकते हैं, और अन्य रास्ते पर जा रहा है, तो आप कुछ इस तरह का उपयोग कर सकते हैं:

maybeEmpty :: IO (Maybe a) -> IO a 
maybeEmpty m = m >>= maybe empty pure