Почему нужна уникальная монада?

Я часто читал, что

Кажется, что личность монады бесполезна. Это не... но это другое тема.

Так может кто-нибудь сказать мне, как это полезно?

Ответ 1

Identity - это монады, функторы и аппликативные функторы, так как 0 - числа. Само по себе это кажется бесполезным, но часто требуется в местах, где можно ожидать, что монада или (аппликативный) функтор фактически ничего не делают.

Как уже упоминалось, Identity позволяет нам определять только монадные трансформаторы, а затем определять их соответствующие монады так же, как SomeT Identity.

Но это не все. Часто удобно также определять другие понятия в терминах монадов, что обычно добавляет большую гибкость. Например Conduit i m o (см. Также этот учебник) определяет элемент в конвейере, который может запрашивать данные тип i, может генерировать данные типа o и использует monad m для внутренней обработки. Затем такой конвейер можно запустить в данной монаде, используя

($$) :: Monad m => Source m a -> Sink a m b -> m b

(где Source является псевдонимом для Conduit без ввода и Sink для Conduit без вывода). И когда в конвейере не нужны эффективные вычисления, просто чистый код, мы просто специализируем m до Identity и запускаем такой конвейер, как

runIdentity (source $$ sink)

Identity также является "пустым" функтором и аппликативным функтором: Identity, составленным с помощью другого функтора или аппликативного функтора, изоморфен оригиналу. Например, Lens' определяется как функция, полиморфная в Functor:

Functor f => (a -> f a) -> s -> f s

грубо говоря, такая линза позволяет читать или манипулировать чем-то типа a внутри s, например, поле внутри записи (для введения в линзы см. this размещать). Если мы специализируем f на Identity, получаем

(a -> Identity a) -> s -> Identity s

которая изоморфна

(a -> a) -> s -> s

поэтому для функции обновления на a необходимо вернуть функцию обновления на s. (Для полноты: если мы специализируем f на Const a, получаем (a -> Const b a) -> s -> Const b s, который изоморфен (a -> b) -> (s -> b), т.е. Читается на a, возвращает читателя на s.)

Ответ 2

Иногда я работаю с записями, поля которых являются необязательными в некоторых контекстах (например, при анализе записи из JSON), но обязательно в других.

Я решаю это, параметризуя запись с помощью функтора и используя Maybe или Identity в каждом случае.

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE StandaloneDeriving #-}

data Query f = Query
    {
        _viewName :: String
    ,   _target :: f Server -- Server is some type, it doesn't matter which
    }
    deriving (Generic)

Поле сервера является необязательным при разборе JSON:

instance FromJSON (Query Maybe)

Но тогда у меня есть функция вроде

withDefaultServer :: Server -> Query Maybe -> Query Identity 
withDefaultServer = undefined

который возвращает запись, в которой поле _target является обязательным.

(Этот ответ не использует ничего монадического в отношении Identity.)

Ответ 3

Одно из его использования - как базовая монада для моноблочных трансформаторных стеков: вместо того, чтобы предоставлять два типа Some :: * ->* и SomeT :: (* -> *) -> * -> *, достаточно просто указать последнее, установив type Some = SomeT Identity.

Другой, несколько похожий вариант использования (но полностью отделенный от всего бизнеса монады) - это когда вам нужно ссылаться на кортежи: мы можем сказать, что () является нулевым кортежем, (a, b) является двоичным кортежем, (a, b, c) это тернарный кортеж и т.д., но что это значит для унарного дела? Выражение a является унарным кортежем для любого выбора a, часто не является удовлетворительным, например, когда мы создаем некоторые экземпляры типа typeclass, такие как Data.Tuple.Select, некоторый тип конструктор необходим, чтобы действовать как однозначный ключ. Таким образом, путем добавления, например, Sel1 instance to Identity a, это заставляет нас различать (a, b) (двухстрочный, содержащий a и a b), и Identity (a, b) (один кортеж, содержащий один (a, b) значение).

(Обратите внимание: Data.Tuple.Select определяет свой собственный тип OneTuple вместо повторного использования Identity, но он изоморфен Identity - фактически, это просто переименование - и я думаю, что он существует только для того, чтобы избежать зависимости от base.)

Ответ 4

Один реальный случай использования должен быть (чистой) базой стека блоков монад, например.

type Reader r = ReaderT r Identity