Автоматное программирование
- 1 year ago
- 0
- 0
Мона́да — особый тип данных в функциональных языках программирования , для которого возможно задать императивную последовательность выполнения некоторых операций над хранимыми значениями . Монады позволяют задавать последовательность выполнения операций, производить операции с побочными эффектами и другие действия, которые сложно или вовсе невозможно реализовать в функциональной парадигме программирования другими способами.
Концепция монады и термин изначально происходят из теории категорий , где она определяется как функтор с дополнительной структурой. Исследования, начатые в конце 1980-х — начале 1990-х годов, установили, что монады могут привнести, казалось бы, разрозненные проблемы компьютерной науки в единую функциональную модель. Теория категорий также выдвигает несколько формальных требований [ каких? ] , так называемых монадических законов , которые должны соблюдаться любой монадой и могут быть использованы для верификации монадического кода.
Монады чаще всего используются в
функциональных языках программирования
. При
ленивой модели вычисления
порядок редукции неизвестен. Например, вычисление
1 + 3 + 6
может быть редуцировано в
1 + 9
или
4 + 6
. Монады позволяют упорядочить редукцию. Поэтому существует ироничное утверждение, что монады — это способ перегрузить оператор «точка с запятой».
Монада является контейнером, который хранит в себе значение произвольного типа. Она должна обладать функцией связывания (bind), которая принимает два аргумента: текущее значение монады и функцию, принимающую значение типа, который содержит текущая монада и возвращающая новую монаду. Результатом вызова функции связывания будет новая монада, полученная путём применения первого аргумента ко второму. Так могла бы выглядеть монада в императивном языке Java и одна из её реализаций, контейнер Maybe:
import java.util.function.Function;
interface Monad<T> {
<U> Monad<U> bind(Function<T, Monad<U>> f);
}
class Maybe<T> implements Monad<T> {
private final T val;
public Maybe(T val) {
this.val = val;
}
public T getVal() {
return val;
}
@Override
public <U> Monad<U> bind(Function<T, Monad<U>> f) {
if (val == null)
return new Maybe<U>(null);
return f.apply(val);
}
}
public class MonadApp {
public static void main(String[] args) {
Maybe<Integer> x = new Maybe<>(5);
Monad<Integer> y = x
.bind(v -> new Maybe<>(v + 1))
.bind(v -> new Maybe<>(v * 2));
System.out.println( ((Maybe<Integer>)y).getVal() );
}
}
Появившиеся в Java 8 функциональные интерфейсы позволяют реализовать интерфейс, похожий на монаду.
Класс 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
не имеет отношения к теоретической сущности монад, однако используется в случае ошибки сопоставления с образцом внутри монадического вычисления.
).
Оператор
>>=
является функцией связывания. Оператор
>>
— частный случай оператора
>>=
, используется когда нам не важен результат связывания.
Некоторые типы, реализующие класс 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"