Edward Kmett 的这篇 Reddit 帖子提供了自然地图的建设性定义,它来自自由定理(fmap
我在另一篇Edward Kmett 的帖子中读到):
对于给定
f
的 ,和g
,这样: , 其中是给定构造函数的自然映射。h
k
f . g = h . k
$map f . fmap g = fmap h . $map k
$map
我不完全理解算法。让我们一步一步地接近它:
我们可以通过对您给出的任何特定具体选择的归纳来定义“自然地图” 。
F
最终,任何这样的 ADT 都是由 sum、products、(->)
's、1
s、0
s、a
's、其他仿函数的调用等组成的。
考虑:
data Smth a = A a a a | B a (Maybe a) | U | Z Void deriving ...
没有箭头。让我们看看fmap
(我认为这是任何没有 s 的 ADT 的自然选择(->)
)在这里如何操作:
instance Functor Smth where
fmap xy (A x x1 x2) = A (xy x) (xy x1) (xy x2)
fmap xy (B x xPlus1) = B (xy x) (fmap xy xPlus1)
-- One can pattern-match 'xPlus1' as 'Just x1' and 'Nothing'.
-- From my point of view, 'fmap' suits better here. Reasons below.
fmap _xy U = U
fmap _xy (Z z) = absurd z
这看起来很自然。更正式地说,我们适用xy
于 every x
,适用fmap xy
于 every T x
,其中T
a Functor
,我们保持每个单元不变,然后将 every 传递Void
给absurd
。这也适用于递归定义!
data Lst a = Unit | Prepend a (Lst a) deriving ...
instance Functor Lst where
fmap xy Unit = Unit
fmap xy (Prepend x lstX) = Prepend (xy x) (fmap xy lstX)
Graph a = Node a [Graph a]
instance Functor Graph where
fmap xy (Node x children) = Node (xy x) (fmap (fmap xy) children)
这部分很清楚。
当我们允许时,
(->)
我们现在有了第一个混合方差的东西。的左侧类型参数(->)
处于逆变位置,右侧处于协变位置。因此,您需要通过整个 ADT 跟踪最终类型变量,并查看它是否出现在正和/或负位置。
现在我们包括(->)
s。让我们尝试继续进行这种归纳:
我们以某种方式推导出和的自然映射。因此,我们要考虑以下几点:T a
S a
data T2S a = T2S (T a -> S a)
instance ?Class? T2S where
?map? ?? (T2S tx2sx) = T2S $ \ ty -> ???
我相信这是我们开始选择的起点。我们有以下选择:
- (幻影)
a
既不在T
也不在S
。a
inT2S
是幻像,因此,我们可以同时实现fmap
和contramap
asconst phantom
。 - (协变)
a
出现在S a
和不出现在 中T a
。因此,这在ReaderT
withS a
(实际上并不依赖于a
)作为环境的行中,它?Class?
用Functor
、?map?
withfmap
、???
、??
with代替xy
:let tx = phantom ty sx = tx2sx tx sy = fmap xy sx in sy
- (逆变)
a
出现在T a
,不出S a
。我看不到让这个东西成为协变函子的方法,所以我们Contravariant
在这里实现一个实例,它?map?
用contramap
、??
withyx
、???
with 代替:let tx = fmap yx ty sx = tx2sx tx sy = phantom sx in sy
- (不变量)
a
同时出现在T a
和S a
。我们不能再使用phantom
,它派上用场了。Edward Kmett有一个模块Data.Functor.Invariant
。它为以下类提供了一个地图:
然而,我看不出有什么方法可以把它变成我们可以加入 fmap 的自由定理class Invariant f where invmap :: (a -> b) -> (b -> a) -> f a -> f b -- and some generic stuff...
id
的东西——这种类型需要一个额外的函数参数,我们不能把它当作什么东西刷掉。无论如何,我们将invmap
代替?map?
,xy yx
代替??
, 并将以下内容代替???
:let tx = fmap yx ty sx = tx2sx tx sy = fmap xy sx in sy
那么,我对这种算法的理解正确吗?如果是这样,我们如何正确处理Invariant情况?