2010-05-29 27 views
10

Estoy escribiendo un script de shell (mi primer no-ejemplo en haskell) que se supone que lista un directorio, obtiene cada tamaño de archivo, realiza alguna manipulación de cadenas (código puro) y luego renombrar algunos archivos. No estoy seguro de lo que estoy haciendo mal, entonces 2 preguntas:lidiando con IO vs código puro en haskell

  1. ¿Cómo debo organizar el código en dicho programa?
  2. Tengo un problema específico, aparece el siguiente error, ¿qué estoy haciendo mal?
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 } 

código:

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

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

Respuesta

13

Su segundo y específico problema es con los tipos de sus funciones. Sin embargo, su primer problema (que en realidad no es una cosa de tipo) es la declaración do en getFileNameAndSize. Mientras que do se usa con mónadas, no es una panacea monádica; en realidad está implementado como some simple translation rules. La versión Notas de Cliff (que no es exactamente derecha, gracias a algunos detalles que implican el manejo de errores, pero está lo suficientemente cerca) es:

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

En otras palabras, getFileNameAndSize es equivalente a la versión sin el do bloque, y para que pueda deshacerse de do. Esto le deja con

getFileNameAndSize fname = (fname, withFile fname ReadMode hFileSize) 

Podemos encontrar el tipo para esto: desde fname es el primer argumento de withFile, que tiene el tipo FilePath; y hFileSize devuelve IO Integer, por lo que ese es el tipo de withFile .... Por lo tanto, tenemos getFileNameAndSize :: FilePath -> (FilePath, IO Integer). Esto puede o no ser lo que quieres; en su lugar, puede querer FilePath -> IO (FilePath,Integer).Para cambiarlo, puede escribir cualquiera de

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 

A continuación, como KennyTM señaló, tiene fileNames <- getDirectoryContents; desde getDirectoryContents tiene tipo FilePath -> IO FilePath, necesita darle un argumento. (, por ejemplo,getFilesWithSizes dir = do fileNames <- getDirectoryContents dir ...). Esto es probablemente solo un simple descuido.

Mext, llegamos al corazón de su error: files <- (mapM getFileNameAndSize fileNames). No estoy seguro de por qué te da el error preciso que tiene, pero puedo decirte lo que está mal. Recuerde lo que sabemos sobre getFileNameAndSize. En su código, devuelve un (FilePath, IO Integer). Sin embargo, mapM es de tipo Monad m => (a -> m b) -> [a] -> m [b], por lo que mapM getFileNameAndSize está mal escrito. Desea getFileNameAndSize :: FilePath -> IO (FilePath,Integer), como implementé anteriormente.

Finalmente, tenemos que arreglar su última línea. En primer lugar, aunque no nos lo proporcione, cmpFilesBySize es presumiblemente una función del tipo (FilePath, Integer) -> (FilePath, Integer) -> Ordering, que se compara con el segundo elemento. Sin embargo, esto es realmente simple: usando Data.Ord.comparing :: Ord a => (b -> a) -> b -> b -> Ordering, puede escribir este comparing snd, que tiene el tipo Ord b => (a, b) -> (a, b) -> Ordering. En segundo lugar, debe devolver el resultado envuelto en la mónada IO en lugar de solo como una lista simple; la función return :: Monad m => a -> m a hará el truco.

Por lo tanto, poner todo esto junto, obtendrá

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 

Esto es todo bien y bueno, y va a funcionar bien. Sin embargo, I podría escribirlo de manera ligeramente diferente. Mi versión, probablemente se vería así:

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

(<=< es el equivalente monádica de ., el operador de composición de funciones.) En primer lugar: sí, mi versión es más largo. Sin embargo, probablemente ya tenga preservingF definido en alguna parte, haciendo que los dos sean equivalentes en longitud. * (Podría incluso incluir fileSize si no se usaran en otro lugar). Segundo, me gusta más esta versión porque implica encadenar funciones simples más simples ya hemos escrito. Si bien su versión es similar, la mía (creo) es más racional y hace que este aspecto de las cosas sea más claro.

Esta es una pequeña respuesta a su primera pregunta sobre cómo estructurar estas cosas. Personalmente tiendo a bloquear mi IO en el menor número de funciones posibles, solo las funciones que necesitan tocar el mundo exterior directamente (, por ejemplo,main y todo lo que interactúa con un archivo) obtienen un IO. Todo lo demás es una función pura ordinaria (y solo es monádica si es monádica por razones generales, a lo largo de las líneas preservingF). Luego organizo las cosas para que main, etc., sean solo composiciones y cadenas de funciones puras: main obtiene algunos valores de IO -land; luego llama a funciones puras para doblar, girar y mutilar la fecha; luego obtiene más valores de IO; entonces opera más; etc. La idea es separar los dos dominios tanto como sea posible, de modo que el código que no sea IO más compositivo sea siempre gratuito, y el cuadro negro IO solo se haga precisamente donde sea necesario.

operadores como <=< realmente ayudar con la escritura de código en este estilo, ya que le permiten operar en funciones que interactúan con los valores monádicos (como el IO -World) del mismo modo que operar en las funciones normales.También debe mirar la notación Control.Applicative'sfunction <$> liftedArg1 <*> liftedArg2 <*> ..., que le permite aplicar funciones ordinarias a cualquier número de argumentos monádicos (realmente Applicative). Esto es realmente bueno para deshacerse de las espurias <- sy simplemente encadenar funciones puras sobre el código monádico.

*: Me siento como preservingF, o al menos su hermano preserving :: (a -> b) -> a -> (a,b), debería estar en un paquete en algún lugar, pero no he podido encontrar ninguno.

+0

Gracias, gran respuesta, todavía entiendo muy poco, supongo porque entiendo el 40%, pero resolvió el problema;) – Drakosha

+0

Me alegro de poder ayudar. ¿Hay algo en particular que no entiendes? Un montón de cosas hacia el final es más algo que creo que es posible que desee ver/aprender, en lugar de algo que creo que ya debería saber. –

10

getDirectoryContents is a function. Debería proporcionarle un argumento, p.

fileNames <- getDirectoryContents "/usr/bin" 

Además, el tipo de getFileNameAndSize es FilePath -> (FilePath, IO Integer), como se puede comprobar a partir de ghci:

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) 

Pero mapM requires the input function to return an IO stuff:

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

Debe cambiar su tipo a FilePath -> IO (FilePath, Integer) para que coincida con el tipo.

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

Gracias, gran respuesta – Drakosha