Я пишу сценарий оболочки (мой 1-й непример в haskell), который, как предполагается, перечисляет каталог, получает каждый размер файла, сделайте некоторую обработку строк (чистый код) и затем переименуйте некоторые файлы. Я не уверен, что я делаю неправильно, таким образом, 2 вопроса:
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 }
код:
getFileNameAndSize fname = do (fname, (withFile fname ReadMode hFileSize))
getFilesWithSizes = do
fileNames <- getDirectoryContents
files <- (mapM getFileNameAndSize fileNames)
sortBy cmpFilesBySize files
Вторая, конкретная проблема, связана с типами ваших функций.Однако ваша первая проблема (не совсем типичная) - это оператор do
в getFileNameAndSize
. Хотя do
используется с монадами, это не монадическая панацея; на самом деле это реализовано как несколько простых правил преобразования . Версия Cliff's Notes (которая не совсем правильная, благодаря некоторым деталям, связанным с обработкой ошибок, но достаточно близка):
do a
≡ a
do а; б; c ...
≡ a >> делать б; c ...
do x <- a; б; 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
Далее, как указал Кенни, у вас есть 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 -> mb) -> [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
, вы можете написать это , сравнивая 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
, что сделало бы два эквивалентными по длине. * (Я мог бы даже встроить fileSize
, если бы он не использовался где-то еще.) Во-вторых,Мне больше нравится эта версия, потому что она включает в себя объединение более простых чистых функций, которые мы уже написали. Хотя ваша версия похожа, моя (я чувствую) более обтекаемая и проясняет этот аспект.
Итак, это немного ответ на ваш первый вопрос о том, как эти вещи структурировать. Я лично стараюсь ограничить свой ввод-вывод как можно меньшим количеством функций - только функции, которые должны напрямую касаться внешнего мира ( например main
и все, что взаимодействует с файлом), получают IO
. Все остальное - обычная чистая функция (и монадическая только в том случае, если она монадическая по общим причинам, в соответствии с preservingF
). Затем я упорядочиваю вещи так, чтобы main
и т. Д. Были просто композициями и цепочками чистых функций: main
получает некоторые значения из IO
-land; затем он вызывает чистые функции для свертывания, веретения и искажения даты; затем он получает больше значений ввода-вывода
; потом действует больше; и т. д. Идея состоит в том, чтобы разделить два домена как можно больше, чтобы более композиционный код, отличный от IO
, всегда был свободен, а черный ящик IO
выполнялся только точно. где необходимо.
Такие операторы, как <= <
, действительно помогают при написании кода в этом стиле, поскольку они позволяют работать с функциями , которые взаимодействуют с монадическими значениями (такими как IO
-world), как если бы вы работали с обычными функциями. Вам также следует взглянуть на функцию Control.Applicative <$> liftedArg1 <*> liftedArg2 <*> ...Нотация
, которая позволяет применять обычные функции к любому количеству монадических (на самом деле Аппликативных
) аргументов. Это действительно хорошо для избавления от ложных <-
s и просто связывания чистых функций над монадическим кодом.
*: мне кажется, что preservingF
или, по крайней мере, его брат preserving :: (a -> b) -> a -> (a, b)
, должен быть в где-то посылка, но я тоже не нашел.
getDirectoryContents
- это функция . Вы должны указать ему аргумент, например
fileNames <- getDirectoryContents "/usr/bin"
Кроме того, тип getFileNameAndSize
- FilePath -> (FilePath, IO Integer)
, как вы можете проверить из 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)
Но mapM
требует, чтобы функция ввода возвращала материал ввода-вывода
:
Prelude System.IO> :t mapM
mapM :: (Monad m) => (a -> m b) -> [a] -> m [b]
-- # ^^^^^^^^
Вы должны изменить его тип на FilePath -> IO (FilePath, Integer)
для соответствия типу.
getFileNameAndSize fname = do
fsize <- withFile fname ReadMode hFileSize
return (fname, fsize)