Монада (программирование)
Мона́да в программировании — это абстракция линейной цепочки связанных вычислений. Её основное назначение — инкапсуляция функций с побочным эффектом от чистых функций, а точнее их выполнений от вычислений [1] . Монады применяются в языке Haskell, так как он повсеместно использует ленивые вычисления, которые вместе с побочным эффектом, как правило, образуют плохо прогнозируемый результат. Она описывается [2] полиморфным контейнерным типом для выполнений с одним параметром, стратегией «поднятия» значения в монаду и стратегией связывания двух вычислений, второе из которых зависит от параметра, вычисляемого первым:
m :: * -> * class Monad m where (>>=) :: m a -> (a -> m b) -> m b (>>) :: m a -> m b -> m b return :: a -> m a fail :: String -> m a class Functor f where fmap :: (a -> b) -> f a -> f b
Функция return описывает «возвращение» (втягивание) типа a в монаду m , то есть обрамление его контейнером. Функция fail не имеет отношения к теоретической сущности монад, однако используется в случае ошибки сопоставления с образцом внутри монадического кода — останавливает процесс последовательных действий и выводит сообщение о причине ошибки (в будущих версиях библиотеки может быть переведён в отдельный класс [3] ). Оператор >>= описывает, что в монаде действия происходят последовательно, то есть после применения функции её результат передаётся далее (.. -> a -> b -> ..), примером которой может быть передача текста в буфер: типы данные облачаются в монаду (конструктором), а затем с ними функция производит действия, в данном случае добавление. Оператор >> — частный случай оператора >>= , когда предыдущие данные просто заменяются следующими, которые не формируются на основании предыдущих.
В частности, к монадам относятся:
- IO (монада строго последовательных вычислений): стратегия связывания — «сначала первое вычисление, затем второе»;
- Maybe (монада вычислений с отсутствующими значениями): стратегия связывания — «если первое вычисление дало результат, то второе; иначе — отсутствие результата»;
- List (монада вычислений с несколькими результатами): стратегия связывания — «все возможные результаты второго вычисления, примененного к каждому из вычисленных первым значений параметра»;
- State (монада вычислений с переменной состояния): стратегия связывания — «начать второе вычисление с состоянием, измененным в результате первого»;
- и некоторые другие типы.
Монады также применяются для синтаксического анализа, продолжений (continuations), вероятностных вычислений и в других случаях.
Концепция монад в программировании была унаследована из теории категорий: Монада (математика)
Примечания
- ↑ Контейнер не имеет задачи инкапсулирования данных.
- ↑ Описание класса Monad находится в модуле Monad пакета Control и в стандартном модуле Prelude , класса Functor и MonadPlus — только в модуле Monad пакета Control .
- ↑Евгений Кирпичев.Монады в Haskell (рус.) . Монады — «обобщение некоторых привычных идиом, а также как еще один метод для их абстракции».
Ссылки
Учебные пособия
- Monad Tutorials Timeline (англ.) Большая коллекция пособий по монадам, представлены в порядке появления.
- What the hell are Monads?
- You Could Have Invented Monads! (And Maybe You Already Have.), простое введение
- All About Monads
- Monads as Computation
- Monads as Containers
- Monads for the Working Haskell Programmer
- The Haskell Programmer’s Guide to the IO Monad — Don’t Panic
- Introduction to Haskell, Part 3: Monads
- On Monads
- Crash Monad Tutorial (англ.) — статья о монадах, объясняющая их с точки зрения теории категорий
- Learn You a Haskell for Great Good! (англ.) — книга содержит доступное описание языка Haskell, в котором много внимания уделено понятию монады и аналогичным конструкциям
Другие статьи
- A tour of the Haskell Monad functions (англ.)
- Notions of Computation and Monads от Eugenio Moggi, первая статья, предлагающая использование монад в программировании
- «Monads for Functional Programming» от Philip Wadler, описание монад в языке Хаскелл (написано еще до того, как они в нем появились)
- 4. Монады — простое изложение основ языка
Литература
- Душкин Р.В. Охрана // Приёмы программирования // Функции // Синтаксис и идиомы языка // Справочник по языку Haskell / Гл. ред. Д. А. Мовчан. — М .: ДМК Пресс, 2008. — С. 37-38. — 554 с. — 1500 экз. — ISBN 5-94074-410-9, ББК 32.973.26-018.2, УДК 004.4
- П. Д. Симон. 8. Лекция: Стандартное начало (Prelude) // Язык и библиотеки Haskell 98.
- Erkok Levent. Value Recursion in Monadic Computations. Oregon Graduate Institute. — 2002. — 162 p.
- Теория категорий
- Haskell
- Концепции языков программирования
Wikimedia Foundation . 2010 .
Записки программиста
Когда я впервые увидел код в стиле f1 >>= \x -> f2 >>= \y -> Right ( x , y ) моя реакция была «Ааа! Что тут происходит? Как вообще кто-то может писать на таком языке?». Но, как выяснилось, если сесть и спокойно во всем разобраться, в монадах нет абсолютно ничего сложного. Кроме того, оказывается, монады имеют множество важных практических применений и способны существенно облегчить выполнение нашей с вами повседневной работы.
Что такое монада?
В Haskell монада — это совершенно обычный класс типов:
class Monad m where
( >>= ) :: m a -> ( a -> m b ) -> m b
( >> ) :: m a -> m b -> m b
return :: a -> m a
fail :: String -> m a
С тем же успехом мы можем объявить интерфейс в Java или абстрактный класс в C++. В большинстве случаев для превращения некого типа в монаду достаточно определить только функции ( >>= ) (произносится «bind») и return , потому что остальные функции имеют разумную реализацию по умолчанию.
Функции (>>=), (>>), return и fail
Давайте временно забудем о монадах вообще и подумаем об одной конкретной, всем хорошо знакомой, монаде IO. Если заменить в перечисленных ранее функциях «m» на «IO», получим:
( >>= ) :: IO a -> ( a -> IO b ) -> IO b
( >> ) :: IO a -> IO b -> IO b
return :: a -> IO a
fail :: String -> IO a
Вспомним тип функции putStrLn:
ghci> :t putStrLn
putStrLn :: String -> IO ()
ghci> :t putStrLn «hello»
putStrLn «hello» :: IO ()
Смотрите-ка! Функция putStrLn , примененная к строке, имеет тип IO ( ) , прямо как аргументы функции ( >> ) , если заменить a или b на конкретный тип ( ) . Давайте попробуем вызвать ( >> ) и посмотрим, что будет:
ghci> putStrLn «hello» >> putStrLn «world»
hello
world
ghci> :t putStrLn «hello» >> putStrLn «world»
putStrLn «hello» >> putStrLn «world» :: IO ()
Как интересно! Сначала была выведена первая строка, а потом вторая. При этом тип всего выражения оказался IO ( ) , что позволяет снова передать его в качестве аргумента функции ( >> ) :
ghci> putStrLn «hello» >> putStrLn «world» >> putStrLn «of monads»
hello
world
of monads
Получается, ( >> ) позволяет создавать что-то вроде цепочек последовательно выполняемых команд. Есть, правда, одна проблема. Что, если я хочу передать второй функции в цепочке значение, возвращенное первой функцией? Например, сначала получить от пользователя строку с помощью getLine , а затем вывести эту строку с помощью putStrLn . Как раз для этого и нужна функция ( >>= ) . Давайте посмотрим на типы:
getLine :: IO String
putStrLn :: String -> IO ( )
( >>= ) :: IO a -> ( a -> IO b ) -> IO b
Получается, функцию getLine можно связать с putStrLn при помощи функции ( >>= ) , при этом a будет типом String , а b будет ( ) :
ghci> :t getLine >>= putStrLn
getLine >>= putStrLn :: IO ()
ghci> getLine >>= putStrLn
Hello
Hello
ghci> getLine >>= (\s -> putStrLn $ «Hello, » ++ s)
Alex
Hello, Alex
Очень, очень хорошо! А что, если у нас есть чистая функция, возвращающая строку, и мы хотим вывести ее при помощи putStrLn? Мы не можем просто поместить функцию в цепочку, потому что после применения к своим аргументам она будет иметь тип String, а нам нужен IO String. Вот для этого и нужна функция return, которая «оборачивает» произвольный тип в монаду:
ghci> let f x y = x ++ «, » ++ y ++ «!»
ghci> f «Hello» «World»
«Hello, World!»
ghci> return (f «Hello» «World») >>= putStrLn
Hello, World!
Наконец, функция fail нужна для обработки исключительных ситуаций. В контексте монады IO она бросает исключение IOError. Хотя, в зависимости от конкретной монады, она может делать и что-то более осмысленное.
do-нотация
Все это замечательно, но при работе с монадой IO мы же просто пишем:
main :: IO ( )
main = do
putStrLn «What’s your name?»
name putStrLn $ «Hello, » ++ name ++ «!»
Никаких стрелочек! Как же так? В действительности, приведенный выше код полностью аналогичен следующему:
main :: IO ( )
main =
putStrLn «What’s your name?» >>
getLine >>=
\name -> putStrLn $ «Hello, » ++ name ++ «!»
Другими словами, do-нотация — это просто синтаксический сахар над стрелочками. Можно спокойно писать на Haskell без нее, просто код будет чуть менее понятен и придется вводить чуть больше символов.
Кстати, при работе в ghci мы на самом деле находимся внутри монады и используем do-нотацию.
Монадные законы
Когда вы работаете с монадами, предполагается, что выполняются следующие правила:
return a >>= k = k a
m >>= return = m
m >>= ( \x -> k x >>= h ) = ( m >>= k ) >>= h
Компилятор не может проверить выполнение этих законов на этапе компиляции, поэтому ответственность за их соблюдение при объявлении новых монад ложиться на программиста. Если внимательно присмотреться, первые два правила напоминают свойство существования нейтрального элемента, а последнее — это что-то вроде ассоциативности. Давайте проверим на примерах, что эти правила выполняются для монады IO.
ghci> let k = (\x -> return $ x * x + 1) :: Int -> IO Int
ghci> let a = 2 :: Int
ghci> return a >>= k
5
ghci> k a
5
ghci> let m = return 2 :: IO Int
ghci> m >>= return
2
ghci> m
2
ghci> let h = k
ghci> m >>= (\x -> k x >>= h)
26
ghci> (m >>= k) >>= h
26
Разумеется, строго доказать, что все три закона всегда выполняются для некоторой монады, намного сложнее. Впрочем, как я понимаю, обычно в этом вопросе программисты полагаются либо на интуицию, либо на тесты.
Монада Maybe
В заметке Пишем простой RESTful сервис с использованием Scotty мы видели такую вот странную функцию:
extractNamePhone :: M . Map String String -> Maybe ( String , String )
extractNamePhone m =
M . lookup «name» m >>=
\name -> M . lookup «phone» m >>=
\phone -> Just ( name , phone )
Давайте разберемся, как это работает. Экземпляр класса Monad для типа Maybe определен следующим образом:
instance Monad Maybe where
( Just x ) >>= k = k x
Nothing >>= _ = Nothing
( Just _ ) >> k = k
Nothing >> _ = Nothing
return = Just
fail _ = Nothing
Обратите внимание на функцию ( >>= ) . Благодаря ей мы можем определить extractNamePhone всего лишь в три строчки кода вместо того, чтобы писать:
extractNamePhone m =
case M . lookup «name» m of
Nothing -> Nothing
Just name ->
case M . lookup «phone» m of
Nothing -> Nothing
Just phone -> Just ( name , phone )
Если бы Maybe не был монадой, а нам нужно было бы извлечь из Map’а десяток значений, пришлось бы написать десяток вложенных case of. Кроме того, что монады в данном случае позволяют писать меньше кипятильно-тарелочного кода, также мы фактически получаем что-то вроде механизма исключений для чистых функций.
Благодаря do-нотации мы можем записать это выражение еще проще. А при помощи аппликативных функторов можно записать его вообще в одну строку. Однако об аппликативных функторах речь пойдет как-нибудь в другой раз.
Монада Either
В модуле Control.Monad.Error объявлен экземпляр класса Monad для типа Either:
ghci> :m + Control.Monad.Error
ghci> let f x = Right x
ghci> Right 1 >>= f :: Either String Int
Right 1
ghci> Left «Error 123» >> Right 1 >>= f
Left «Error 123»
ghci> Left 1.0 >> Right 1 >>= f
Left 1.0
Здесь все работает аналогично Maybe, только вместо того, чтобы в случае неудачи просто возвращать Nothing, мы можем как-то уточнить причину ошибки.
Монада State
Монада State бывает полезна, когда имеется некоторое состояние, которое мы постоянно изменяем. Например, если у нас есть Map, в который мы хотим поместить десять значений, возвращаемых разными функциями, нам придется ввести множество переменных m0, m1, m2, … m9. Благодаря монаде State мы можем создать изменяемое состояние, не потеряв при этом чистоты функций, и избежать тем самым описанной проблемы.
Работает это как-то так:
ghci> :m + Control.Monad.State
ghci> let f = (\x y -> modify (+1) >>= \_ -> get >>= \t -> return $ x ++ y ++ show t) :: String -> String -> State Int String
ghci> evalState (f «aaa» «bbb») 0
«aaabbb1»
ghci> execState (f «aaa» «bbb») 0
1
ghci> runState (f «aaa» «bbb») 0
(«aaabbb1»,1)
Здесь Int — это наше состояние, а String — как бы значение, возвращаемое функцией. Получить текущее состояние можно с помощью функции get, сохранить — при помощи put, а изменить — при помощи modify. Для выполнения функции и получения возвращаемого ею значения используйте evalState. Если же вы хотите получить конечное состояние, воспользуйтесь execState. Если вам нужно как возвращаемое значение, так и конечное состояние, скажите runState.
В do-нотации монада State, конечно, выглядит намного красивее.
Монада Reader
Это своего рода State, доступный только на чтение. Монада Reader удобна в случае, когда у нас есть множество функций, работающих в некотором неизменяемом окружении. Вместо того, чтобы передавать между этими функциями одни и те же параметры, мы можем завернуть их в монаду Reader.
ghci> :m + Control.Monad.Reader
ghci> let f = (\x y -> local (+1) (asks (+1) >>= \t1 -> ask >>= \t2 -> return $ x ++ y ++ show t1 ++ show t2) ) :: String -> String -> Reader Int String
ghci> runReader (f «aaa» «bbb») 0
«aaabbb21»
С помощью функции local мы можем создать скоп с измененным окружением. Это намного удобнее, чем читать окружение и создавать на его основе новое. Функция ask возвращает текущее окружение. Функция asks применяет заданную функцию к текущему окружению и возвращает результат. Ее удобно использовать в сочетании с селекторами.
По понятным причинам функций execReader и evalReader не предусмотрено.
Монада Writer
Если Reader — это State, из которого можно только читать, то Writer — это State, в который можно только писать. Монада Writer часто применяется для логирования и трассировки без потери чистоты функций.
ghci> let f = (\x y -> tell [«111»] >>= \_ -> tell [«222»] >>= \_ -> return $ x ++ y ) :: String -> String -> Writer [String] String
ghci> runWriter (f «aaa» «bbb»)
(«aaabbb»,[«111″,»222»])
ghci> execWriter (f «aaa» «bbb»)
[«111″,»222»]
ghci> runWriter (censor (map (++ «!»)) $ f «aaa» «bbb»)
(«aaabbb»,[«111!»,»222!»])
ghci> runWriter (listen $ f «aaa» «bbb»)
((«aaabbb»,[«111″,»222»]),[«111″,»222»])
ghci> runWriter (listens (filter (/= «111»)) $ f «aaa» «bbb»)
((«aaabbb»,[«222»]),[«111″,»222»])
Функция tell производит запись в лог. Интересно, что лог при этом должен быть моноидом. В рамках данного поста будет достаточно сказать, что списки являются моноидами. Функция censor применяется для модификации записей в логе. Функция listen превращает Writer, который возвращает a и пишет w, во Writer, который возвращает (a, w) и пишет w. Функция listens позволяет делать с логом что угодно. Мы можем отфильтровать записи в нем, добавить новые, привести их к другому типу или сделать все это одновременно.
Монада [] (список)
Список тоже являются монадой. Вот классический пример:
— почему при игре в кости выгодно ставить на семь
combinations :: Int -> [ ( Int , Int ) ]
combinations n = do
a b if a + b == n then return ( a , b ) else [ ]
solve :: [ ( Int , Int ) ]
solve = do
n return ( n , length $ combinations n )
main = do
putStrLn $ show solve
Вызываем функцию main:
ghci> main
[(2,1),(3,2),(4,3),(5,4),(6,5),(7,6),(8,5),(9,4),(10,3),(11,2),(12,1)]
Можно сказать, что это другой способ записи генераторов списков.
Некоторые функции для работы с монадами
В модуле Control.Monad вы найдете множество обобщенных функций для работы с монадами. О назначении большинства из них несложно догадаться по названию и типу, поэтому я просто приведу список:
(= <<) :: Monad m =>(a -> m b) -> m a -> m b
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c
( <=<) :: Monad m =>(b -> m c) -> (a -> m b) -> a -> m c
mapM :: Monad m => (a -> m b) -> [a] -> m [b]
mapM_ :: Monad m => (a -> m b) -> [a] -> m ()
forM :: Monad m => [a] -> (a -> m b) -> m [b]
forM_ :: Monad m => [a] -> (a -> m b) -> m ()
foldM :: Monad m => (a -> b -> m a) -> a -> [b] -> m a
foldM_ :: Monad m => (a -> b -> m a) -> a -> [b] -> m ()
filterM :: Monad m => (a -> m Bool) -> [a] -> m [a]
zipWithM :: Monad m => (a -> b -> m c) -> [a] -> [b] -> m [c]
zipWithM_ :: Monad m => (a -> b -> m c) -> [a] -> [b] -> m ()
liftM :: Monad m => (a1 -> r) -> m a1 -> m r
liftM2 :: Monad m => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r
replicateM :: Monad m => Int -> m a -> m [a]
replicateM_ :: Monad m => Int -> m a -> m ()
sequence :: Monad m => [m a] -> m [a]
sequence_ :: Monad m => [m a] -> m ()
when :: Monad m => Bool -> m () -> m ()
unless :: Monad m => Bool -> m () -> m ()
forever :: Monad m => m a -> m b
join :: Monad m => m (m a) -> m a
ap :: Monad m => m (a -> b) -> m a -> m b
Поэкспериментируйте с ними на досуге в ghci с конкретными монадами, например, IO, Maybe и [], чтобы разобраться, как эти функции работают. Если хотите, можете считать это своим домашним заданием.
Заключение
Помимо перечисленных в данной заметке монад есть и другие. Например, уже знакомая нам монада STM, а также Indentity, монада цитирования Q, Error, ST, Cont, Eval, Par, ActionM и многие другие. В языке Scala много, так сказать, завуалированных монад, среди которых особый интерес представляет Future. Еще в Haskell есть моноиды, MonadPlus, функторы, аппликативные функторы, monad trasformer’ы и другие интересные вещи. Но эти вопросы выходят за рамки поста.
В качестве источников дополнительной информации я бы советовал:
- Две главы в «Learn You a Haskell for Great Good» — раз и два;
- Еще две главы из «Real World Haskell» — эту и эту;
- «Еще одно руководство по монадам», оригинал на английском и перевод на русский первых четырех частей из цикла;
- Хороший туториал по монадам в Haskell Wiki;
Как видите, все прекрасно работает безо всяких там погружений в теории категорий и прочие матаны. Поначалу монады выглядят немного пугающе, потому что в мейнстримовых языках ничего подобного нет. Но если вы обладаете достаточным терпением, а также достаточной незамутненностью сознания, то скоро у вас начнет вызывать удивление, как вообще можно писать что-то серьезное без использования монад.
Дополнение: Вот отличный пример для медитации:
ghci> liftM3 (,,) (+1) (> 0) show 23
(24,True,»23″)
Попробуйте посмотреть на типы в REPL и понять, как это работает.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.
Монада (программирование)
Мона́да — особый тип данных в функциональных языках программирования, для которого возможно задать императивную последовательность выполнения некоторых операций над хранимыми значениями [1] . Монады позволяют задавать последовательность выполнения операций, производить операции с побочными эффектами и другие действия, которые сложно или вовсе невозможно реализовать в функциональной парадигме программирования другими способами.
Концепция монады и термин изначально происходят из теории категорий, где она определяется как функтор с дополнительной структурой. Исследования, начатые в конце 1980-х — начале 1990-х годов, установили, что монады могут привнести, казалось бы, разрозненные проблемы компьютерной науки в единую функциональную модель. Теория категорий также выдвигает несколько формальных требований [каких?] , так называемых монадических законов, которые должны соблюдаться любой монадой и могут быть использованы для верификации монадического кода.
Описание [ править | править код ]
Монады чаще всего используются в функциональных языках программирования. При ленивой модели вычисления порядок редукции неизвестен. Например, вычисление 1 + 3 + 6 может быть редуцировано в 1 + 9 или 4 + 6 . Монады позволяют упорядочить редукцию. Поэтому существует ироничное утверждение, что монады — это способ перегрузить оператор «точка с запятой».
Монада является контейнером, который хранит в себе значение произвольного типа. Она должна обладать функцией связывания (bind), которая принимает два аргумента: текущее значение монады и функцию, принимающую значение типа, который содержит текущая монада и возвращающая новую монаду. Результатом вызова функции связывания будет новая монада, полученная путём применения первого аргумента ко второму. Так могла бы выглядеть монада в императивном языке Java и одна из её реализаций, контейнер Maybe:
import java.util.function.Function; interface MonadT> U> MonadU> bind(FunctionT, MonadU>> f); > class MaybeT> implements MonadT> private final T val; public Maybe(T val) this.val = val; > public T getVal() return val; > @Override public U> MonadU> bind(FunctionT, MonadU>> f) if (val == null) return new MaybeU>(null); return f.apply(val); > > public class MonadApp public static void main(String[] args) MaybeInteger> x = new Maybe<>(5); MonadInteger> y = x .bind(v -> new Maybe<>(v + 1)) .bind(v -> new Maybe<>(v * 2)); System.out.println( ((MaybeInteger>)y).getVal() ); > >
Появившиеся в Java 8 функциональные интерфейсы позволяют реализовать интерфейс, похожий на монаду.
В Haskell [ править | править код ]
Класс Monad присутствует в стандартном модуле Prelude . Для реализации данного класса требуется любой однопараметрический тип (тип рода * -> * ). Монада обладает четырьмя методами
class Functor f where fmap :: (a -> b) -> f a -> f b class Functor f => Applicative f where pure :: a -> f a ( ) :: f (a -> b) -> f a -> f b (*>) :: f a -> f b -> f b () :: f a -> f b -> f a -- m :: * -> * class Applicative m => Monad m where (>>=) :: m a -> (a -> m b) -> m b (>>) :: m a -> m b -> m b -- реализован по-умолчанию: a >> b = a >>= \_ -> b return :: a -> m a -- = pure fail :: String -> m a -- по-умолчанию вызывает errorWithoutStackTrace
Метод return может ввести в заблуждение программистов, знакомых с императивными языками: он не прерывает вычисления, а лишь упаковывает произвольное значение типа a в монаду m . Метод fail не имеет отношения к теоретической сущности монад, однако используется в случае ошибки сопоставления с образцом внутри монадического вычисления. [2] ). Оператор >>= является функцией связывания. Оператор >> — частный случай оператора >>= , используется когда нам не важен результат связывания.
Некоторые типы, реализующие класс Monad:
- IO , используется для функций с побочным эффектом. Конструкторы IO скрыты от программиста, также отсутствуют функции распаковки монады. Это не позволяет вызывать грязные функции из чистых.
- Maybe . Вычисление прерывается, если получено значение Nothing.
- [] (список) . Вычисление прерывается при пустом списке. При непустом списке оператор >>= вызывает функцию для каждого элемента списка.
- Reader .
- Writer .
- State . Помимо возможностей Reader позволяет изменять состояние.
В языке также присутствует do -нотация, которая является более удобной формой записи монадических функций. В данном примере f1 использует do -нотацию, а f2 записана с помощью операторов связывания:
f1 = do s getLine putStrLn $ "Hello " ++ s putStrLn "Goodbye" f2 = getLine >>= (\s -> putStrLn $ "Hello " ++ s) >> putStrLn "Goodbye"
Примечания [ править | править код ]
- ↑Душкин-ФП, 2008, с. 215.
- ↑Евгений Кирпичев.Монады в Haskell(рус.) . Архивировано 16 января 2017 года. Монады — «обобщение некоторых привычных идиом, а также как ещё один метод для их абстракции».
Ссылки [ править | править код ]
Учебные пособия [ править | править код ]
- Monad Tutorials Timeline(англ.) Большая коллекция пособий по монадам, представлены в порядке появления.
- What the hell are Monads?
- You Could Have Invented Monads! (And Maybe You Already Have.), простое введение
- Monads as Computation
- Monads as Containers
- Monads for the Working Haskell Programmer
- The Haskell Programmer’s Guide to the IO Monad — Don’t Panic
- Introduction to Haskell, Part 3: Monads
- On Monads
- Crash Monad Tutorial(англ.) — статья о монадах, объясняющая их с точки зрения теории категорий
- Learn You a Haskell for Great Good!(англ.) — книга содержит доступное описание языка Haskell, в котором много внимания уделено понятию монады и аналогичным конструкциям
Другие статьи [ править | править код ]
- A tour of the Haskell Monad functions(англ.)
- Notions of Computation and Monads от Eugenio Moggi, первая статья, предлагающая использование монад в программировании
- «Monads for Functional Programming» от Philip Wadler, описание монад в языке Хаскелл (написано ещё до того, как они в нём появились)
- 4. Монады — простое изложение основ языка
Литература [ править | править код ]
- Душкин Р.В. Охрана // Приёмы программирования // Функции // Синтаксис и идиомы языка // Справочник по языку Haskell / Гл. ред. Д. А. Мовчан. — М. : ДМК Пресс, 2008. — С. 37—38. — 554 с. — 1500 экз. — ISBN 5-94074-410-9, ББК 32.973.26-018.2, УДК 004.4.
- Душкин Р. В. Функциональное программирование на языке Haskell. — М. : ДМК Пресс, 2008. — 609 с. — ISBN 5-94074-335-8.
- Пейтон-Джонс, Саймон. 8. Лекция: Стандартное начало (Prelude) // Язык и библиотеки Haskell 98.
- Erkok Levent. Value Recursion in Monadic Computations. Oregon Graduate Institute. — 2002. — 162 p.
- Теория категорий
- Haskell
- Концепции языков программирования
- Страницы, использующие устаревший тег source
- Википедия:Статьи, требующие конкретизации
- Википедия:Статьи с шаблонами недостатков по алфавиту
- Страницы, использующие волшебные ссылки ISBN
Javascript и монада Maybe
В какой-то момент мне надоело писать что-то типа object && object.prop1 && object.prop1.prop2 . Это ужасно и неправильно. Кроме очевидного варианта с перехватом исключения try/catch, нашлись опциональные цепочки, и, конечно же, монады.
В новых версиях JS появились опциональные цепочки ?. , безопасный способ доступа к свойствам вложенных объектов, даже если какое-либо из промежуточных свойств не существует, то есть вместо user && user.address && user.address.street можно написать user?.address?.street . Но в моем случае этот вариант не подошел, использовалась старая версия Node.js.
Во-первых, а что такое монада? Монада — это особый тип данных в функциональных языках программирования. Про функциональное программирование я только слышал и считал, что это не про Javascript, а оказалось, что даже на вики написано:
Поддерживает объектно-ориентированный, императивный и функциональный стили.
Вот про функциональный стиль Javascript мы сегодня и поговорим. Я не буду углубляться в определение что такое монада и цитировать умные книжки, а расскажу как я понял её сам. Монада — это что-то вроде контейнера, который поддерживает определенные стандартами операции. Самый первый стандарт, который находит Google — это Fantasy Land.
В общем и целом, монада должна поддерживать как минимум:
Это позволяет соединять различные (или даже одинаковые) монады в цепочки, так как все они следуют единому стандарту.
Есть куча готовых библиотек с различными готовыми монадами, но это не наш метод, чтобы разобраться как это работает — нужно написать свою версию монады Maybe. Ниже одна из реализаций класса Maybe, уверен, что не полная, а может быть и где-то неправильная, но моя.
class Maybe < constructor(val) < this.__value = val; >static of(val) < return new Maybe(val); >static Nothing() < return Maybe.of(null); >flatMap(fn) < if(this.isNothing()) return Maybe.Nothing(); const m = fn(this.__value); return m.isNothing() ? Maybe.Nothing() : Maybe.of(m.__value); >getOrElse(elseVal) < return this.isNothing() ? elseVal : this.__value; >getOrEmptyArray() < return this.getOrElse([]); >getOrNull() < return this.getOrElse(null); >isNothing() < return this.__value === null || this.__value === undefined; >map(fn) < return this.isNothing() ? Maybe.of(null) : Maybe.of(fn(this.__value)); >join() < return this.__value; >chain(fn) < return this.map(fn).join(); >ap(someOtherMaybe) < return someOtherMaybe.map(this.__value); >>
Ну и, по сути, вся проверка на существование свойства объекта происходит банальным this.__value === null || this.__value === undefined .
А теперь попробуем монаду Maybe в деле:
Подключим необходимые модули, для работы с path используется библиотека rambda:
import < path >from 'rambda'
Определяем объект testObj :
const testObj = < "a": < "b": < "c": "test" >> >
А затем создаем экземпляр нашего класса Maybe .
const maybeObj = Maybe.of(testObj)
Для того, чтобы получить строку, используется функция join() .
А здесь мы пытаемся получить значение свойства c :
const val = maybeObj .map(path(["a", "b", "c"])) .join()
Далее попытка обратиться к несуществующему свойству не приведет к возникновению ошибки, а просто вернет undefined .
Полный исходный код ниже:
import < path >from 'rambda' const testObj = < "a": < "b": < "c": "test" >> > const maybeObj = Maybe.of(testObj); console.log(maybeObj); console.log(maybeObj.join()); const val = maybeObj .map(path(["a", "b", "c"])) .join(); console.log(val); const valNothing = maybeObj .map(path(["test", "b", "c"])) .join(); console.log(valNothing);
- https://jrsinclair.com/articles/2016/marvellously-mysterious-javascript-maybe-monad/
- https://medium.com/@yyankowski/maybe-monad-in-javascript-to-save-us-from-the-hell-of-the-null-guard-clauses-bc9f9a1f291b
- https://github.com/stoeffel/awesome-fp-js
- https://tproger.ru/translations/better-javascript-code-with-fp-features/
Previous article
Муки выбора платформы для блога
Иногда душа просит измарать бумагу, но, раз мы айтишники, то писать ну бумаге — это не наш метод, писать нужно в электронном виде. Ну а
Следующая статья
У меня поломалась Raspberry Pi 3B
Как у любого уважающего себя айтишника у меня есть компьютер у кровати. Ноутбук не очень удобно, зато китайский сенсорный монитор, беспроводные клава и мышь, тонкий