2010-05-29 12 views
10

में आईओ बनाम शुद्ध कोड से निपटने के लिए मैं एक शेल स्क्रिप्ट (हैकेल में मेरा पहला गैर-उदाहरण) लिख रहा हूं, जिसे एक निर्देशिका सूचीबद्ध करना है, प्रत्येक फ़ाइल आकार प्राप्त करना है, कुछ स्ट्रिंग मैनिपुलेशन (शुद्ध कोड) और फिर कुछ फाइलों का नाम बदलें। मुझे यकीन नहीं है कि मैं क्या गलत कर रहा हूं, इसलिए 2 प्रश्न:हैकेल

  1. मुझे इस कार्यक्रम में कोड कैसे व्यवस्थित करना चाहिए?
  2. मेरे पास एक विशिष्ट समस्या है, मुझे निम्न त्रुटि मिलती है, मैं गलत क्या कर रहा हूं?
error: 
    Couldn't match expected type `[FilePath]' 
      against inferred type `IO [FilePath]' 
    In the second argument of `mapM', namely `fileNames' 
    In a stmt of a 'do' expression: 
     files <- (mapM getFileNameAndSize fileNames) 
    In the expression: 
     do { fileNames <- getDirectoryContents; 
      files <- (mapM getFileNameAndSize fileNames); 
      sortBy cmpFilesBySize files } 

कोड:

getFileNameAndSize fname = do (fname, (withFile fname ReadMode hFileSize)) 

getFilesWithSizes = do 
    fileNames <- getDirectoryContents 
    files <- (mapM getFileNameAndSize fileNames) 
    sortBy cmpFilesBySize files 

उत्तर

13

आपकी दूसरी, विशिष्ट, समस्या आपके कार्यों के प्रकारों के साथ है। हालांकि, आपका पहला मुद्दा (वास्तव में एक प्रकार की चीज़ नहीं) getFileNameAndSize में कथन है। जबकि do मोनैड के साथ प्रयोग किया जाता है, यह एक monadic panacea नहीं है; यह वास्तव में some simple translation rules के रूप में लागू किया गया है। क्लिफ नोट्स संस्करण (जो बिल्कुल सही, त्रुटि हैंडलिंग से जुड़े कुछ विवरण के लिए धन्यवाद, नहीं है, लेकिन पर्याप्त करीब है) है:

  1. do aa
  2. do a ; b ; c ...a >> do b ; c ...
  3. do x <- a ; b ; c ...a >>= \x -> do b ; c ...

दूसरे शब्दों में, getFileNameAndSizeके बिना संस्करण के बराबर हैब्लॉक, और इसलिए आप do से छुटकारा पा सकते हैं। यह हम इस के लिए प्रकार पा सकते हैं

getFileNameAndSize fname = (fname, withFile fname ReadMode hFileSize) 

के साथ छोड़ देता है: के बाद से fnamewithFile को पहला तर्क है, यह टाइप FilePath है; और hFileSizeIO Integer देता है, तो यह withFile ... का प्रकार है। इस प्रकार, हमारे पास getFileNameAndSize :: FilePath -> (FilePath, IO Integer) है। यह हो सकता है कि आप जो चाहते हैं हो या न हो; आप इसके बजाय FilePath -> IO (FilePath,Integer) चाहते हैं।इसे बदलने के लिए, आपको

getFileNameAndSize_do fname = do size <- withFile fname ReadMode hFileSize 
            return (fname, size) 
getFileNameAndSize_fmap fname = fmap ((,) fname) $ 
             withFile fname ReadMode hFileSize 
-- With `import Control.Applicative ((<$>))`, which is a synonym for fmap. 
getFileNameAndSize_fmap2 fname =  ((,) fname) 
           <$> withFile fname ReadMode hFileSize 
-- With {-# LANGUAGE TupleSections #-} at the top of the file 
getFileNameAndSize_ts fname = (fname,) <$> withFile fname ReadMode hFileSize 

अगला के किसी भी लिख सकते हैं, के रूप में KennyTM ने कहा, आप fileNames <- getDirectoryContents है; चूंकि getDirectoryContents में FilePath -> IO FilePath टाइप है, तो आपको इसे एक तर्क देना होगा। (उदा।getFilesWithSizes dir = do fileNames <- getDirectoryContents dir ...)। यह शायद सिर्फ एक साधारण निरीक्षण है।

mext, हम अपने त्रुटि के दिल के लिए आते हैं: files <- (mapM getFileNameAndSize fileNames)। मुझे यकीन नहीं है कि यह आपको सटीक त्रुटि क्यों देता है, लेकिन मैं आपको बता सकता हूं कि क्या गलत है। याद रखें कि हम getFileNameAndSize के बारे में क्या जानते हैं। आपके कोड में, यह (FilePath, IO Integer) देता है। हालांकि, mapM टाइप Monad m => (a -> m b) -> [a] -> m [b] है, और इसलिए mapM getFileNameAndSize खराब टाइप है। आप getFileNameAndSize :: FilePath -> IO (FilePath,Integer) चाहते हैं, जैसा कि मैंने उपरोक्त कार्यान्वित किया है।

अंत में, हम अपने अंतिम पंक्ति को ठीक करने की जरूरत है। सबसे पहले, हालांकि आप इसे हमें नहीं देते हैं, cmpFilesBySize संभावित रूप से दूसरे तत्व की तुलना में (FilePath, Integer) -> (FilePath, Integer) -> Ordering प्रकार का एक कार्य है। यह वास्तव में सरल है, हालांकि: Data.Ord.comparing :: Ord a => (b -> a) -> b -> b -> Ordering का उपयोग करके, आप यह comparing snd लिख सकते हैं, जिसमें Ord b => (a, b) -> (a, b) -> Ordering है। दूसरा, आपको एक सादा सूची के बजाय आईओ मोनैड में अपना परिणाम लपेटना होगा; फ़ंक्शन return :: Monad m => a -> m a चाल करेगा।

इस प्रकार, यह सब एक साथ डाल, तो आप

import System.IO   (FilePath, withFile, IOMode(ReadMode), hFileSize) 
import System.Directory (getDirectoryContents) 
import Control.Applicative ((<$>)) 
import Data.List   (sortBy) 
import Data.Ord   (comparing) 

getFileNameAndSize :: FilePath -> IO (FilePath, Integer) 
getFileNameAndSize fname = ((,) fname) <$> withFile fname ReadMode hFileSize 

getFilesWithSizes :: FilePath -> IO [(FilePath,Integer)] 
getFilesWithSizes dir = do fileNames <- getDirectoryContents dir 
          files  <- mapM getFileNameAndSize fileNames 
          return $ sortBy (comparing snd) files 

मिलेगा यह सब अच्छी तरह से और अच्छा है, और ठीक से काम करेगा। हालांकि, मैं इसे थोड़ा अलग लिख सकता हूं। बंद

{-# LANGUAGE TupleSections #-} 
import System.IO   (FilePath, withFile, IOMode(ReadMode), hFileSize) 
import System.Directory (getDirectoryContents) 
import Control.Applicative ((<$>)) 
import Control.Monad  ((<=<)) 
import Data.List   (sortBy) 
import Data.Ord   (comparing) 

preservingF :: Functor f => (a -> f b) -> a -> f (a,b) 
preservingF f x = (x,) <$> f x 
-- Or liftM2 (<$>) (,), but I am not entirely sure why. 

fileSize :: FilePath -> IO Integer 
fileSize fname = withFile fname ReadMode hFileSize 

getFilesWithSizes :: FilePath -> IO [(FilePath,Integer)] 
getFilesWithSizes = return . sortBy (comparing snd) 
          <=< mapM (preservingF fileSize) 
          <=< getDirectoryContents 

(<=<. की monadic बराबर, समारोह रचना ऑपरेटर है।) प्रथम:: मेरी संस्करण शायद इस प्रकार दिखाई देगा हाँ, मेरी संस्करण अब है। हालांकि, मैं शायद पहले से ही है preservingF कहीं परिभाषित होता है, जिससे दो लंबाई में बराबर। * (मैं हो सकता है यहां तक ​​कि इनलाइन fileSize अगर यह कहीं और इस्तेमाल नहीं किया गया।) दूसरा, मैं इस संस्करण बेहतर पसंद है, क्योंकि यह एक साथ सरल शुद्ध कार्यों चेनिंग शामिल हम पहले से ही लिखे हैं। जबकि आपका संस्करण समान है, मेरा (मुझे लगता है) अधिक सुव्यवस्थित है और चीजों के इस पहलू को स्पष्ट बनाता है।

तो यह कैसे इन बातों की संरचना करने के अपने पहले प्रश्न का उत्तर का एक सा है। मैं व्यक्तिगत रूप से मेरे आईओ जो बाहर की दुनिया सीधे (जैसेmain और जो कुछ भी एक फ़ाइल के साथ सूचना का आदान प्रदान) एक IO पाने को स्पर्श करना संभव-केवल कार्यों के रूप में के रूप में कुछ कार्यों में नीचे लॉक करने के लिए करते हैं। बाकी सब कुछ एक सामान्य शुद्ध कार्य है (और यह केवल मोनैडिक है यदि यह preservingF की तर्ज पर सामान्य कारणों से मोनैडिक है)। मैं तो बातें की व्यवस्था इतनी है कि main, आदि, बस रचनाओं और शुद्ध कार्यों की श्रृंखला हैं: mainIO -land से कुछ मान हो जाता है; तो यह तारीख को गुना करने, धुंधला करने और विचलित करने के लिए शुद्ध कार्यों को बुलाता है; तो यह IO मान प्राप्त करता है; तो यह अधिक संचालित करता है; इत्यादि। विचार दो डोमेन को यथासंभव अलग करना है, ताकि अधिक रचनात्मक गैर -IO कोड हमेशा नि: शुल्क होता है, और ब्लैक-बॉक्स IO केवल वही किया जाता है जहां आवश्यक हो।

ऑपरेटर्स <=< की तरह वास्तव में, इस शैली में लेखन कोड के साथ मदद के रूप में वे आप कार्यों जो (जैसे IO -world के रूप में) monadic मूल्यों के साथ बातचीत आप सामान्य कार्यों पर कार्य करते हैं बस के रूप में पर काम करते हैं।आपको Control.Applicative'sfunction <$> liftedArg1 <*> liftedArg2 <*> ... नोटेशन भी देखना चाहिए, जो आपको किसी भी प्रकार के मोनैडिक (वास्तव में Applicative) तर्कों के लिए सामान्य कार्यों को लागू करने देता है। यह नकली <- एस से छुटकारा पाने के लिए वास्तव में अच्छा है और केवल मोनैडिक कोड पर शुद्ध कार्यों को चेन करना।

*: मुझे लगता है कि preservingF, या कम से कम इसके भाई preserving :: (a -> b) -> a -> (a,b), कहीं पैकेज में होना चाहिए, लेकिन मैं या तो खोजने में असमर्थ हूं।

+0

धन्यवाद, महान जवाब है, मैं अभी भी समझ में बहुत कम मैं लगता है क्योंकि मैं इसके बारे में 40% को समझते हैं, लेकिन यह समस्या हल हो;) – Drakosha

+0

खुशी है कि मैं मदद कर सकता है। क्या विशेष रूप से कुछ भी आपको नहीं मिलता है? अंत में बहुत सारी चीजें एक और चीज है जो मुझे लगता है कि आप शायद कुछ सीखने/सीखना चाहें, जैसा कि मुझे लगता है कि आपको पहले ही पता होना चाहिए। –

10

getDirectoryContents is a function। आपको इसके लिए एक तर्क देना चाहिए, उदा।

Prelude> :m + System.IO 
Prelude System.IO> let getFileNameAndSize fname = do (fname, (withFile fname ReadMode hFileSize)) 
Prelude System.IO> :t getFileNameAndSize 
getFileNameAndSize :: FilePath -> (FilePath, IO Integer) 

लेकिन mapM requires the input function to return an IO stuff:

Prelude System.IO> :t mapM 
mapM :: (Monad m) => (a -> m b) -> [a] -> m [b] 
-- #     ^^^^^^^^ 

आप FilePath -> IO (FilePath, Integer) को अपनी तरह बदलना चाहिए

fileNames <- getDirectoryContents "/usr/bin" 

इसके अलावा, getFileNameAndSize के प्रकार FilePath -> (FilePath, IO Integer), जैसा कि आप GHCi से जांच कर सकते हैं है प्रकार से मिलान करने के लिए।

getFileNameAndSize fname = do 
    fsize <- withFile fname ReadMode hFileSize 
    return (fname, fsize) 
+1

धन्यवाद, महान जवाब – Drakosha