2010-05-29 20 views
10

'da saf kodla ilgili bir kabuk dosyası yazdım (bir dizini listelemem gerekiyordu), bir dizin listeleniyor, her dosya boyutuna ulaşılıyor, bir miktar string manipülasyonu yapılıyor (saf kod) ve sonra bazı dosyaları yeniden adlandırın. Ne yapıyorum emin değilim, yani 2 soru:IO ile karşılaştırmak haskell

  1. Bu programda kodu nasıl ayarlamalıyım?
  2. Belirli bir sorun var, aşağıdaki hatayı alıyorum, ne yapıyorum yanlış?
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 } 

kodu:

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

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

cevap

13

Sizin ikinci, belirli, sorun fonksiyonların türleriyle olduğunu. Ancak, ilk sayınız (gerçekten bir şey değil), getFileNameAndSize numaralı telefondan do ifadesidir. Monolar ile do kullanılırken, monadik bir panacea değildir; aslında some simple translation rules olarak uygulanmaktadır. (tam sağ, hata işleme ilgili bazı detaylar sayesinde değil ama yeterince yakın) Cliff'in Notlar sürümü:

  1. do aa
  2. do a ; b ; c ...a >> do b ; c ...
  3. do x <- a ; b ; c ...a >>= \x -> do b ; c ... başka bir deyişle

, getFileNameAndSizeolmadan sürümü eşdeğerdirbloğu ve böylece do'dan kurtulabilirsiniz. Bu Bunun için türünü bulabilirsiniz

getFileNameAndSize fname = (fname, withFile fname ReadMode hFileSize) 

ile yaprakları: fnamewithFile ilk argüman olduğundan, bu FilePath tip vardır; ve hFileSize, bir IO Integer döndürür, bu nedenle withFile ... türüdür. Böylece, getFileNameAndSize :: FilePath -> (FilePath, IO Integer) var. İstediğiniz bu olabilir veya olmayabilir; Bunun yerine FilePath -> IO (FilePath,Integer)'u isteyebilirsiniz.Bunu değiştirmek için,

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 

Sonraki herhangi yazabilir, KennyTM belirttiği gibi, sen fileNames <- getDirectoryContents var; getDirectoryContents, FilePath -> IO FilePath türüne sahip olduğundan, bir argüman vermeniz gerekir. (, ör.getFilesWithSizes dir = do fileNames <- getDirectoryContents dir ...). Bu muhtemelen sadece basit bir gözetimdir.

Mext, hatanın kalbine geldik: files <- (mapM getFileNameAndSize fileNames). Size neden kesin bir hata verdiğini bilmiyorum, ama neyin yanlış olduğunu söyleyebilirim. getFileNameAndSize ile ilgili bildiklerimizi hatırlayın. Kodunuzda (FilePath, IO Integer) döndürür. Ancak, mapM, Monad m => (a -> m b) -> [a] -> m [b] tipindedir ve mapM getFileNameAndSize yanlış yazılmıştır. Yukarıda uygulamış olduğum gibi getFileNameAndSize :: FilePath -> IO (FilePath,Integer). Son satırınızı düzeltmemiz gerekiyor. Son olarak, son satırınızı düzeltmemiz gerekiyor. Her şeyden önce, bize vermese de, cmpFilesBySize, muhtemelen ikinci elemanla kıyaslandığında, (FilePath, Integer) -> (FilePath, Integer) -> Ordering tipinin bir fonksiyonudur. Bununla birlikte, bu gerçekten basit: Data.Ord.comparing :: Ord a => (b -> a) -> b -> b -> Ordering kullanarak, Ord b => (a, b) -> (a, b) -> Ordering türünde bu comparing snd yazabilirsiniz. İkincisi, sonuçlarınızı IO monadına sarılmış bir sonuç olarak değil, basit bir liste olarak geri göndermeniz gerekir; return :: Monad m => a -> m a işlevi hile yapacaktır.

Böylece hep birlikte bu koyarak, sen Bunların hepsi iyi ve güzel

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 

alırsınız ve iyi çalışır. Ancak, biraz farklı yazabilirim. Benim versiyonu muhtemelen bu şekilde görünecektir:

{-# 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 

(<=<. ait monadic eşdeğer, fonksiyon kompozisyon operatörüdür.) İlk kapalı: evet, benim sürümü daha uzundur. Ancak, ben zaten preservingF yapım yerde tanımlanmış olurdu uzunluğunda eşdeğer iki. * (Olabilir hatta inline fileSize başka bir yerde kullanılan olmasaydı.) Bir arada daha basit saf fonksiyonları zincirleme içerir çünkü İkincisi, Bu versiyonundan daha ister zaten yazdık. Senin versiyonun benzer olsa da, benim (kendimi) hissediyorum daha akıcı ve bu şeylerin daha net olmasını sağlar.

Bu, bu şeylerin nasıl yapılandırılacağı konusundaki ilk sorunuzun bir cevabıdır. Ben şahsen doğrudan dış dünya (örneğinmain ve bir dosya ile etkileşim şey) bir IO olsun dokunmak gerek mümkün salt fonksiyonları olduğunca az fonksiyonlarına aşağı benim IO kilitlemek eğilimindedir. Diğer her şey sıradan bir saf işlevdir (ve preservingF satırları boyunca genel nedenlerden dolayı monadik ise sadece monadiktir). Sonra şeyler düzenlemek böylece main vb sadece kompozisyonlar ve saf fonksiyonların zincirleri şunlardır: mainIO .arazi gelen bazı değerleri alır; Daha sonra, tarihi katlamak, iş mili haline getirmek ve mutlaklaştırmak için saf işlevleri çağırır; o zaman daha fazla IO değerini alır; o zaman daha fazla çalışır; Buradaki fikir, iki alanı mümkün olduğunca ayırmaktır, böylece IO kodunun daha fazla kompozisyonu her zaman ücretsizdir ve siyah kutu IO sadece gerekli olduğu yerde tam olarak yapılır. onlar normal fonksiyonları üzerinde faaliyet tıpkı (örneğin IO -Dünya gibi) monadic değerlerle etkileşim fonksiyonları üzerinde işlem izin olarak <=< gibi

Operatörler gerçekten bu tarzda yazı kodu ile yardımcı olur.Ayrıca, herhangi bir sayıda monoya (gerçekten Applicative) bağımsız değişkene normal işlevler uygulamanıza olanak veren Control.Applicative'sfunction <$> liftedArg1 <*> liftedArg2 <*> ... notasyonuna bakmalısınız. Bu sahte <- s kurtulmak için gerçekten güzel ve monadik kod üzerinde sadece saf işlevleri zincirleme.

*: preservingF veya en azından kardeşinin preserving :: (a -> b) -> a -> (a,b) gibi bir paket içinde olması gerektiğini hissediyorum, ancak bulamadım da.

+0

Teşekkürler, büyük bir cevap, hala anlıyorum, çok azını anlıyorum çünkü% 40'ını anlıyorum, ama sorunu çözdü;) – Drakosha

+0

Yardım edebilirim. Özellikle anlamadığınız bir şey var mı? Sonuna doğru bir çok şey, bilmeniz gereken bir şey olarak değil, bakmak/öğrenmek istediğinizi düşündüğünüz bir şey. –

10

getDirectoryContents is a function. Buna bir argüman sağlamalısınız, ör.

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) 

Ama mapM requires the input function to return an IO stuff: Sen FilePath -> IO (FilePath, Integer) onun türünü değiştirmek gerekir

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

Eğer GHCi dan kontrol edebilirsiniz olarak

fileNames <- getDirectoryContents "/usr/bin" 

Ayrıca getFileNameAndSize türü, FilePath -> (FilePath, IO Integer) olduğunu Türü eşleştirmek için

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

Teşekkürler, müthiş cevap – Drakosha