Продолжательное
Dec. 7th, 2010 01:31 pm> module WithFile where > import Control.Monad.Cont > import System.IO
Помнится, кто-то меня когда-то спрашивал, для чего нужна монада
Cont. Я тогда сказал, что иных применений, кроме выхода из кучи вложенных вычислений, не знаю. Теперь знаю. Для соблюдения скобочной структуры.Есть такая функция, называется
withFile. Тип у неё будет, стало быть,*WithFile> :t withFile withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r
Эта функция открывает файл (в указанном режиме), подставляет его хэндл в заданную функцию, выполняет получившееся действие, закрывает файл, и возвращает результат действия.
Допустим, мы хотим написать такую же функцию, но работающую со списком файлов, а не с одним из них. Пишем, прямо и тупо:
withFiles :: [FilePath] -> IOMode -> ([Handle] -> IO r) -> IO r withFiles [] _ action = action [] withFiles (f:fs) mode action = withFile f mode (\h -> withFiles fs mode (\hs -> action (h:hs)))
Непорядок. Во-первых, параметр
mode всегда один и тот же, а значит, должен быть первым по списку. Во-вторых, единственное, что реально зависит от конкретных типов FilePath, IOMode, Handle и монады IO - это функция withFile.Имеет смысл её абстрагировать. Пишем:
withFiles fs mode action = withMany (\f -> withFile f mode) fs action withMany withOne [] action = action [] withMany withOne (f:fs) action = withOne f (\h -> withMany withOne fs (\hs -> action (h:hs)))
Можно написать несколько короче и проще:
withFiles fs mode = withMany (flip withFile mode) fs
Посмотрим теперь, какой тип имеет функция
withMany*WithFile> :t withMany withMany :: (t1 -> (a -> t) -> t) -> [t1] -> ([a] -> t) -> t
Хм. Никакого упоминания ни об одном из перечисленных типов, характерных для
IO. Полностью обобщённая функция. Или... не совсем?В самом деле, ведь то, что там стоит в двух местах - это, практически, монада
Cont! Только конструктора не хватает. Попробуем заменить всё выражение (a -> t) -> t на Cont, и получим(t1 -> Cont t a) -> [t1] -> Cont t [a]
Упс. А ведь одна такая функция уже есть. Это не что иное, как функция
mapM, специализированная для конкретной монады Cont t. Да-да, это она и есть, мы её только записали в несколько уродском виде.Таким образом, функция
withMany записывается в виде> withMany withOne fs = runContT $ mapM (ContT . withOne) fs
Здесь пришлось написать
ContT вместо Cont, поскольку в нынешней mtl нет конструктора Cont, вместо этого используется ContT, в который подставляется монада Identity.Однако, это даже лучше: ведь в нашем исходном варианте вместо
t был тип IO r, уже сидящий в монаде. Таким образом, определениеwithFiles fs mode = withMany (flip withFile mode) fs
работает отлично. То есть, вместо вложенных "скобок"
withFile, мы имеем последовательность продолжений, которая отлично порождается библиотечной функцией. Можно даже забить на withMany, так как она очень проста, и писать> withFiles fs mode = runContT (mapM (ContT . flip withFile mode) fs)
no subject
Date: 2010-12-07 06:29 pm (UTC)no subject
Date: 2010-12-07 06:56 pm (UTC)