Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
fst
. Чтобы получить i-тый элемент списка, есть (!!)
. А чтобы изменить элемент пары или значение в списке, ничего нету. Пока не придумали линзы, разбирать структуры данных было гораздо легче, чем собирать их назад. А теперь getter и setter объединены в одной функции, и эти функции-линзы даже образуют категорию (т.е. (.)
работает для них так же, как и для обычных функций).getLine <*> readFile <*> putStrLn
написать не получится, т.к. типы не сойдутся: <*>
имеет тип f (a -> b) -> f a -> f b
, т.е. применяет функцию внутри коробки — функтора. readFile
же берёт чистое значение и возвращает упакованное (String -> IO String)
, и в итоге получается, что строка с текстом файла будет вложена в коробку дважды (IO (IO String))
:Prelude Control.Applicative> :t (pure readFile <*> getLine)
(pure readFile <*> getLine) :: IO (IO String)
join :: m (m a) -> m a
монада и отличается от АФ. Грубо говоря, монада имеет право распаковывать значения из коробок, а АФ — только выполнять функции, не распаковывая коробок.Как всегда в таких случаях, мне очень хочется спросить: а зачем это всё нужно?
То есть, в контексте Haskell это всё понятно, ввели такую систему типов. Но зачем это всё в реальном мире?
Есть примеры?
Почему в Real World Haskell почти во всех примерах решение идёт через foreign, небезопасные массивы и прочие прелести?
Где же там мощь функторов и монад?
Имеют ли они вообще какой-нибудь не абстрактный смысл вне Haskell?
Но я за долгое время так и не смог понять, чем хороши монады. Я понимаю, зачем они нужны в чистом функциональном языке, но я не понимаю, почему это это всё полезно и удобно при условии существования кучи других языков программирования. Я не понимаю, в чём «изящество» и мощь этих механизмов.
И у меня есть стойкое ощущение, что единственное для чего они придуманы, это чтобы у адептов Haskell было постоянное развлечение в виде «напишу-ка я очередную статью о монадах, теперь с картинками!». Нет, ну реально. Ещё ни разу не видел статью о монадах вида «я придумал крутую монаду XYZ, теперь мой код стал понятнее на 50% и короче на 60». Зато, при этом есть куча статей с объяснениями того, что такое монада в Haskell. Разве это не является признаком того, что с практической точки зрения монада — это какой-то странноватый инструмент?
Как бы… Эмс… То, что существует множество разных языков программирования не наталкивает вас на мысль, что эти языки нужны для решения различных задач? IMHO, тотально глупо считать, что Haskell, Python, Bash и C позволяют одинаково хорошо решать разные задачи.
Большинство из этих пакетов — реализации примитивных структур данных, которые почему-то в других сообществах являются самоочевидными. Ну никто в мире Си не гордится тем, что реализовал набор queue-like data structures, даже студенты, которые хотят на халяву зачёт по курсовой получить.
Покажите мне реальные приложения. Я вот когда-то смотрел на darcs и frag, первый меня убил обилием кода на Си, второй ужасными конструкциями с несколькими видами стрелок, за каждой из которых стояла какая-то перестановочная семантика, отслеживание которой по коду в течении сотни строчек надолго привило мне стойкое неприятие Haskell. На Си у Кармака то же самое написано в 10 раз понятнее и лаконичнее. Так зачем тогда городить этот огород со стрелками? Не понятно.
newtype X a = X (ReaderT XConf (StateT XState IO) a)Зачем оно нужно? Потому что большинства функций тип
что-то -> что-то -> X (что-то)То есть де-факто большая часть функций довольно грязные. Надо ли это? Не знаю и не очень понимаю, чему здесь помогает этот стек трансформеров.
-- | The X monad, 'ReaderT' and 'StateT' transformers over 'IO'
-- encapsulating the window manager configuration and state,
-- respectively.
--
-- Dynamic components may be retrieved with 'get', static components
-- with 'ask'. With newtype deriving we get readers and state monads
-- instantiated on 'XConf' and 'XState' automatically.
--
newtype X a = X (ReaderT XConf (StateT XState IO) a)
deriving (Functor, Monad, MonadIO, MonadState XState, MonadReader XConf, Typeable)
instance Applicative X where
pure = return
(<*>) = ap
instance (Monoid a) => Monoid (X a) where
mempty = return mempty
mappend = liftM2 mappend
(<*>) :: (Applicative f) => f (a -> b) -> f a -> f b
flip (>>=) :: (Monad f) => (a -> f b) -> f a -> f b
<*>
можно выразить через >>=
, а вот наоборот — нет.join :: (Monad m) => m (m a) -> m a
Maybe
здесь наиболее иллюстративен. Так как Maybe
является монадой, то вы можете написать такое (здесь все операции возвращают Maybe
):do result1 <- someFailingOperation1
result2 <- someFailingOperation2
result3 <- someFailingOperation3 result2
someFailingOperation4 result1 result3
someFailingOperation1 >>= \result1 ->
someFailingOperation2 >>= \result2 ->
someFailingOperation3 result2 >>= \result3 ->
someFailingOperation4 result1 result3
Maybe
, описанных в статье, такая цепочка операций вернёт Nothing
, если хотя бы одна операция вернула Nothing
. Заметьте, что здесь мы применяем someFailingOperation{3,4}
к результатам предыдущих операций.Maybe
был бы только аппликативным функтором, но не монадой, то такого мы бы написать не смогли: в случае аппликативных функторов функции внутри контекста не могут возвращать сам аппликативный функтор. Вернее, они могут, но тогда мы получим два уровня вложенности контейнеров, а «схлопнуть» их не получится, что делает невозможным их сколько-нибудь осмысленное применение.fmap
и «немного магии» вдруг сложились — это жесть.Функтор — это тип классов.
Map
объявлен только для Maybe
, а хаскеллевский fmap
работает с любым функтором.Monad m => m a -> a
, то в общем случае нельзя. Как вы справедливо заметили, из Nothing
доставать нечего. А из IO
тем более просто так ничего достать не получится, сайд-эффекты во все поля.Maybe
может выглядеть вот так:someFunction m = case m of
Just v -> ... -- Maybe и в самом деле содержит значение; в этой ветке оно теперь доступно под именем v
Nothing -> ... -- Значения нет, делаем что-то другое, например, используем дефолтное
Maybe
существует комбинатор maybe :: b -> (a -> b) -> Maybe a -> b
, который определён примерно так:maybe def f m = case m of
Just v -> f v
Nothing -> def
Maybe
-значения.>>=
и <*>
, есть и частные, типа maybe
. Они позволяют очень точно, чётко и ясно выражать различные действия внутри монад и функторов.IO
, но это, понятно, отдельная история), вообще говоря, можно «разобрать» на части с помощью паттернматчинга. Конечно, если автор библиотеки не стал экспортировать конструкторы своей монады/функтора, то пользователи этой библиотеки не смогут её «разобрать» непосредственно, но в таком случае автор, как правило, предоставляет другие возможности получить значение из «контейнера», если это вообще можно сделать в принципе.
Функторы, аппликативные функторы и монады в картинках