Ваша вторая, конкретная проблема связана с типами ваших функций. Тем не менее, ваша первая проблема (не совсем типовая вещь) - это оператор do
в getFileNameAndSize
. Хотя do
используется с монадами, это не монадная панацея; на самом деле он реализован как несколько простых правил перевода . Версия 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
возвращает 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
. Во-вторых, вам нужно возвращать свой результат, заключенный в монаду ввода-вывода, а не просто как простой список; функция 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
, что сделало бы два эквивалентных по длине *. вместе более простые чистые функции мы уже написали. Хотя ваша версия похожа, моя (я чувствую) более упорядочена и проясняет этот аспект.
Так что это немного ответ на ваш первый вопрос о том, как структурировать эти вещи. Лично я склонен блокировать свой ввод-вывод на как можно меньшее количество функций - только функции, которые должны напрямую касаться внешнего мира ( например, main
и все, что взаимодействует с файлом), получают IO
. Все остальное - обычная чистая функция (и монадическая, только если по общим причинам она монадическая, по принципу preservingF
). Затем я расположил все так, чтобы main
и т. Д. Были просто композициями и цепочками чистых функций: main
получает некоторые значения из IO
-land; затем он вызывает чистые функции для свертывания, вращения и искажения даты; тогда он получает больше IO
значений; тогда это работает больше; и т. д. Идея состоит в том, чтобы как можно больше разделить два домена так, чтобы более композиционный не IO
код всегда был свободен, а черный ящик IO
выполнялся только там, где это необходимо.
OpТакие эраторы, как <=<
, действительно помогают в написании кода в этом стиле, поскольку они позволяют вам работать с функциями , которые взаимодействуют с монадическими значениями (такими как IO
-world) так же, как вы работаете с обычными функциями , Вам также следует обратить внимание на нотацию function <$> liftedArg1 <*> liftedArg2 <*> ...
Control.Applicative, которая позволяет применять обычные функции к любому количеству монадических (на самом деле Applicative
) аргументов. Это очень удобно для избавления от ложных <-
s и просто связывания чистых функций над монадическим кодом.
*: я чувствую, что preservingF
или, по крайней мере, его брат preserving :: (a -> b) -> a -> (a,b)
, должен быть где-то в пакете, но я не смог найти ни одного.