Я часто читал, что
Кажется, что личность монады бесполезна. Это не... но это другое тема.
Так может кто-нибудь сказать мне, как это полезно?
Я часто читал, что
Кажется, что личность монады бесполезна. Это не... но это другое тема.
Так может кто-нибудь сказать мне, как это полезно?
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
.)
Иногда я работаю с записями, поля которых являются необязательными в некоторых контекстах (например, при анализе записи из 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
.)
Одно из его использования - как базовая монада для моноблочных трансформаторных стеков: вместо того, чтобы предоставлять два типа 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
.)
Один реальный случай использования должен быть (чистой) базой стека блоков монад, например.
type Reader r = ReaderT r Identity