为什么 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)
(包含 a
和 b
的二元组)和 Identity (a, b)
(包含单个 (a, b)
值的单元组)。
(注意Data.Tuple.Select
定义了自己的类型OneTuple
而不是重用Identity
,但它同构于Identity
——实际上只是重命名离开——我认为它的存在只是为了避免非 base
依赖。)
有时我处理的记录在某些情况下是可选的(比如从 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)
解析时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。)
我经常读
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)
(包含 a
和 b
的二元组)和 Identity (a, b)
(包含单个 (a, b)
值的单元组)。
(注意Data.Tuple.Select
定义了自己的类型OneTuple
而不是重用Identity
,但它同构于Identity
——实际上只是重命名离开——我认为它的存在只是为了避免非 base
依赖。)
有时我处理的记录在某些情况下是可选的(比如从 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)
解析时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 定义其他概念通常很方便,这通常会增加很多灵活性。例如 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。)