为什么 Identity monad 有用?

Why is Identity monad useful?

我经常读

It seem that identity monad is useless. It's not... but that's another topic.

谁能告诉我它有什么用?

一个真正的用例是作为 monad 转换器堆栈的(纯)基础,例如

type Reader r = ReaderT r Identity

它的一个用途是作为 monad 转换器堆栈的基本 monad:不必提供两种类型 Some :: * ->*SomeT :: (* -> *) -> * -> *,通过设置 [=] 只提供后者就足够了12=]。

另一个有点类似的用例(但完全脱离了整个 monad 业务)是当你需要引用元组时:我们可以说 () 是一个空元组,(a, b) 是一个元组二元元组,(a, b, c) 是一个三元元组,依此类推,但是对于一元情况还有什么意义呢?说 a 是一元元组,因为 a 的任何选择通常都不能令人满意,例如,当我们构建一些类型类实例时,如 Data.Tuple.Select,需要一些类型构造函数作为明确的钥匙。所以通过添加例如Sel1 实例到 Identity a,它迫使我们区分 (a, b)(包含 ab 的二元组)和 Identity (a, b)(包含单个 (a, b) 值的单元组)。

(注意Data.Tuple.Select定义了自己的类型OneTuple而不是重用Identity,但它同构于Identity——实际上只是重命名离开——我认为它的存在只是为了避免非 base 依赖。)

有时我处理的记录在某些情况下是可选的(比如从 JSON 解析记录时),但在其他情况下是强制性的。

我通过用函子参数化记录来解决这个问题,并在每种情况下使用 MaybeIdentity

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

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

解析时server字段可选JSON:

instance FromJSON (Query Maybe)

但是我有一个像

这样的函数
withDefaultServer :: Server -> Query Maybe -> Query Identity 
withDefaultServer = undefined

returns 一条记录,其中 _target 字段是必填字段。

(虽然这个答案没有使用关于 Identity 的任何单子。)

Identity 之于 monad、函子和应用函子,就像 0 之于数字一样。就其本身而言,它似乎毫无用处,但在人们期望 monad 或实际上什么都不做的(应用)仿函数的地方经常需要它。

如前所述,Identity 允许我们只定义 monad 转换器,然后像 SomeT Identity.

一样定义它们对应的 monad。

但这还不是全部。根据 monad 定义其他概念通常很方便,这通常会增加很多灵活性。例如 Conduit i m o (also see this tutorial) 在管道中定义了一个元素,它可以请求 i 类型的数据,可以产生 o 类型的数据,并使用 monad m 进行内部处理。那么这样的管道可以是 运行 in the given monad using

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

(其中 Source 是没有输入的 Conduit 的别名,Sink 是没有输出的 Conduit 的别名)。当管道中不需要有效的计算,只需要纯代码时,我们只需将 m 专门化为 Identity 和 运行 这样的管道

runIdentity (source $$ sink)

Identity 也是 "empty" 仿函数和应用仿函数: Identity 与另一个仿函数或应用仿函数组成,与原始仿函数同构。例如,Lens' 被定义为 Functor:

中的多态函数
Functor f => (a -> f a) -> s -> f s

粗略地说,这样的镜头允许读取或操作 s 中的 a 类型的东西,例如记录中的字段(有关镜头的介绍,请参阅 this post ).如果我们将 f 专门化为 Identity,我们得到

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

同构于

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

因此给定 a 上的更新函数,return s 上的更新函数。 (为了完整性:如果我们将 f 专门化为 Const a,我们得到 (a -> Const b a) -> s -> Const b s,它与 (a -> b) -> (s -> b) 同构,也就是说,在 a,return s 上的 reader。)