Существует общепринятое мнение, что во многих языках программирования можно увидеть «тег инфекционной функции» - какое-то специальное поведение для функции, которая должна распространяться и на ее вызывающих.
- Функции Rust могутбыть
unsafe
, означая, что они выполняют операции, которые могут потенциально нарушить безопасность памяти.unsafe
функции могут вызывать нормальные функции, но любая функция, которая вызывает функцию unsafe
, должна быть также unsafe
. - Функции Python могут быть
async
, то есть они возвращают обещание, а не фактическоезначение.async
функции могут вызывать нормальные функции, но вызов функции async
(через await
) может выполняться только другой функцией async
. - Функции Haskell могут быть нечистыми означает, что они возвращают
IO a
, а не a
.Нечистые функции могут вызывать чистые функции, но нечистые функции могут вызываться только другими нечистыми функциями. - Математические функции могут быть частичными , то есть они не отображают каждое значение в своей области навыход.Определения частичных функций могут ссылаться на итоговые функции, но если тотальная функция отображает часть своей области в частичную функцию, она также становится частичной.
Хотя могут быть способы вызова теговой функциииз функции без тегов не существует общего способа, и это часто может быть опасным и угрожать нарушить абстракцию, которую пытается обеспечить язык.
Преимущество наличия теговзаключается в том, что вы можете предоставить набор специальных примитивов, которым присвоен этот тег и которые имеют любую функцию, которая использует эти примитивы, чтобы это было ясно в его сигнатуре.
Скажем, вы разработчик языка и узнаете этот шаблон, иВы решаете, что хотите разрешить пользовательские теги.Допустим, пользователь определил тег Err
, представляющий вычисления, которые могут вызвать ошибку.Функция, использующая Err
, может выглядеть следующим образом:
function div <Err> (n: Int, d: Int): Int
if d == 0
throwError("division by 0")
else
return (n / d)
Если бы мы хотели упростить вещи, мы могли бы заметить, что нет ничего ошибочного в получении аргументов - это вычисление возвращаемого значения, где могут возникнуть проблемы.Таким образом, мы можем ограничить теги функциями, которые не принимают аргументов и имеют div
, возвращающие закрытие, а не фактическое значение:
function div(n: Int, d: Int): <Err> () -> Int
() =>
if d == 0
throwError("division by 0")
else
return (n / d)
В ленивом языке, таком как Haskell, нам не нужно закрытиеи может просто возвращать ленивое значение напрямую:
div :: Int -> Int -> Err Int
div _ 0 = throwError "division by 0"
div n d = return $ n / d
Теперь очевидно, что в Haskell теги не нуждаются в специальной языковой поддержке - они являются обычными конструкторами типов.Давайте создадим для них класс типов!
class Tag m where
Мы хотим иметь возможность вызывать функцию без тегов из функции с тегами, что эквивалентно превращению значения без тега (a
) в значение с тегом (m a
).
addTag :: a -> m a
Мы также хотим иметь возможность принять значение с тегом (m a
) и применить функцию с тегом (a -> m b
), чтобы получить результат с тегом (m b
):
embed :: m a -> (a -> m b) -> m b
Это, конечно, именно определение монады!addTag
соответствует return
, а embed
соответствует (>>=)
.
Теперь ясно, что «теговые функции» являются просто типом монады.Таким образом, всякий раз, когда вы обнаруживаете место, где может применяться «функциональный тег», скорее всего, у вас есть место, подходящее для монады.
PS Что касается тегов, которые я упоминал в этом ответе: модели Haskellпримесь с монадой IO
и частичное с монадой Maybe
.Большинство языков реализуют асинхронные / обещания довольно прозрачно, и, похоже, существует пакет на Haskell под названием обещание , который имитирует эту функциональность.Монада Err
эквивалентна монаде Either String
.Я не знаю ни одного языка, который моделирует небезопасную память монадически, это могло бы быть сделано.