Ответ 1
Вторая, конкретная проблема связана с типами ваших функций. Тем не менее, ваша первая проблема (не совсем вещь типа) - это оператор do
в getFileNameAndSize
. Хотя do
используется с монадами, это не монадическая панацея; он фактически реализован как несколько простых правил трансляции. Версия Cliff Notes (что не совсем верно, благодаря некоторым деталям, связанным с обработкой ошибок, но достаточно близко):
-
do a
≡a
-
do a ; b ; c ...
≡a >> do b ; c ...
-
do x <- a ; b ; c ...
≡a >>= \x -> do b ; c ...
Другими словами, getFileNameAndSize
эквивалентен версии без блока do
, и поэтому вы можете избавиться от do
. Это оставляет вас с
getFileNameAndSize fname = (fname, withFile fname ReadMode hFileSize)
Мы можем найти тип для этого: поскольку fname
является первым аргументом withFile
, он имеет тип FilePath
; и hFileSize
возвращает a IO 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
. Во-вторых, вам нужно вернуть результат, завершенный в монаде IO, а не просто как простой список; функция 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
(<=<
является монадическим эквивалентом .
, оператора композиции функций.) Прежде всего: да, моя версия дольше. Тем не менее, у меня, вероятно, уже есть preservingF
где-то определен, что делает два эквивалентных по длине. * (Я мог бы даже встроить fileSize
, если бы он не использовался в другом месте.) Во-вторых, мне нравится эта версия лучше, потому что она включает в себя цепочку вместе простые простые функции, которые мы уже писали. Хотя ваша версия похожа, моя (я чувствую) более оптимизирована и делает этот аспект вещей более ясным.
Итак, это немного ответ на ваш первый вопрос о том, как структурировать эти вещи. Я лично склонен блокировать мое IO до нескольких функций, как только возможные функции, которые нужно напрямую касаться внешнего мира (например, main
и все, что взаимодействует с файлом) получают IO
. Все остальное - обычная чистая функция (и только монадическая, если она монадична по общим причинам, по строкам preservingF
). Затем я упорядочиваю вещи так, что main
и т.д. - это просто композиции и цепочки чистых функций: main
получает некоторые значения из IO
-land; то он называет чистые функции, чтобы сбрасывать, шпинделя и калечить дату; то он получает больше значений IO
; то он работает больше; и т.д. Идея состоит в том, чтобы как можно больше отделить эти домены, так что более композитный код <<246 > всегда свободен, а черный ящик IO
выполняется только там, где это необходимо.
Операторы, такие как <=<
, действительно помогают писать код в этом стиле, поскольку они позволяют вам работать с функциями, которые взаимодействуют с монадическими значениями (такими как IO
-world) так же, как вы будете работать с обычными функциями. Вы также должны посмотреть на примечание Control.Applicative function <$> liftedArg1 <*> liftedArg2 <*> ...
, которое позволяет применять обычные функции к любому числу монадических (действительно Applicative
)) аргументы. Это действительно хорошо для избавления от ложных <-
и просто цепочки чистых функций над монадическим кодом.
*: Мне кажется, что preservingF
или, по крайней мере, его брат preserving :: (a -> b) -> a -> (a,b)
, должен быть где-то в пакете, но я тоже не смог найти.