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:
do a
≡ a
do a ; b ; c ...
≡ a >> do b ; c ...
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.
Gracias, gran respuesta, todavía entiendo muy poco, supongo porque entiendo el 40%, pero resolvió el problema;) – Drakosha
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. –