Haskell 自然地图推导算法

Haskell 自然地图推导算法,haskell,covariance,functor,contravariance,denotational-semantics,Haskell,Covariance,Functor,Contravariance,Denotational Semantics,为fmap(我在另一篇文章中读到)提供了自然地图的构造性定义,该定义来自自由定理: 对于给定的f、g、h和k,例如f。g=h。k:$map f。fmap g=fmap h$map k,其中$map是给定构造函数的自然映射 我不完全理解算法。让我们一步一步来解决这个问题: 我们可以通过归纳您给出的任何具体的F选择来定义“自然地图”。 最终,任何此类ADT都由总和、乘积、(>)、1s、0s、a、调用其他 函子等 考虑: data Smth a = A a a a | B a (Maybe a) |

fmap
(我在另一篇文章中读到)提供了自然地图的构造性定义,该定义来自自由定理

对于给定的
f
g
h
k
,例如
f。g=h。k
$map f。fmap g=fmap h$map k
,其中
$map
是给定构造函数的自然映射

我不完全理解算法。让我们一步一步来解决这个问题:

我们可以通过归纳您给出的任何具体的
F
选择来定义“自然地图”。 最终,任何此类ADT都由总和、乘积、
(>)
1
s、
0
s、
a
、调用其他 函子等

考虑:

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
应用于每个
x
,将
fmap xy
应用于每个
tx
,其中
T
是一个
函子
,我们保持每个单元不变,并将每个
Void
传递到
上。这也适用于递归定义

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)
(详细解释见链接帖子下方。)

这一部分很清楚

当我们允许
(>)
时,我们现在有了第一个混合方差的东西。
(>)
的左侧类型参数处于逆变位置,右侧类型参数处于协变位置。因此,您需要在整个ADT中跟踪最终类型变量,并查看它是否出现在正位置和/或负位置

现在我们包括
(>)
s。让我们试着让这一归纳继续下去:

我们以某种方式导出了
ta
sa
自然地图。因此,我们要考虑以下几个方面:

data T2S a = T2S (T a -> S a)

instance ?Class? T2S where
    ?map? ?? (T2S tx2sx) = T2S $ \ ty -> ???
我相信这是我们开始选择的地方。我们有以下选择:

  • a
    既不出现在
    T
    中,也不出现在
    S
    中<
    T2S
    中的code>a
是幻影,因此,我们可以实现
fmap
contracmap
作为幻影
  • (协变)
    a
    出现在
    sa
    中,而不出现在
    ta
    中。因此,这是
    ReaderT
    行中使用
    sa
    (实际上并不依赖
    a
    )作为环境的一部分,它用
    函子
    映射
    使用
    fmap
    ,用
    xy
    替换
    let tx = phantom ty 
        sx = tx2sx tx
        sy = fmap xy sx
    in sy
    
  • a
    出现在
    ta
    中,不出现在
    sa
    中。我看不到一种方法可以使这个东西成为协变函子,所以我们在这里实现了一个
    逆变
    实例,它用
    逆变
    替换
    yx
    替换
    let tx = fmap yx ty 
        sx = tx2sx tx
        sy = phantom sx 
    in sy
    
  • a
    同时出现在
    ta
    sa
    中。我们不能再使用幻影了,它很方便。有一个由Edward Kmett编写的模块。它为以下类提供了一个映射:
    class Invariant f where
      invmap :: (a -> b) -> (b -> a) -> f a -> f b
      -- and some generic stuff...
    
    然而,我看不出有什么方法可以把它变成fmap的自由定理——这个类型需要一个额外的函数参数,我们不能把它当作
    id
    或其他什么。无论如何,我们将
    invmap
    替换为
    ?map?
    xy yx
    替换为
    ,以下替换为
    let tx = fmap yx ty 
        sx = tx2sx tx
        sy = fmap xy sx
    in sy
    

  • 那么,我对这种算法的理解正确吗?如果是这样,我们如何正确处理不变量情况?

    我认为您的算法太复杂了,因为您正在尝试编写一个算法。相反,编写两个算法会使事情变得更简单。一种算法将构建自然fmap,另一种算法将构建自然对照图。但这两种算法在以下意义上都需要是不确定的:存在无法成功的类型,因此不返回实现;有些类型的人有多种成功的方法,但它们都是等价的

    首先,让我们仔细定义参数化类型的含义。以下是我们可以拥有的不同类型的参数化类型:

    F ::= F + F'
        | F * F'
        | F -> F'
        | F . F'
        | Id
        | Const X
    
    Const X
    中,
    X
    覆盖所有具体的非参数化类型,如
    Int
    Bool
    等。这是它们的解释,也就是说,一旦给定一个参数,它们就同构于具体类型:

    [[F + F']] a = Either ([[F]] a) ([[F']] a)
    [[F * F']] a = ([[F]] a, [[F']] a)
    [[F -> F']] a = [[F]] a -> [[F']] a
    [[F . F']] a = [[F]] ([[F']] a)
    [[Id]] a = a
    [[Const X]] a = X
    
    现在我们可以给出两种算法。您自己已经编写的第一部分:

    fmap @(F + F') f (Left x) = Left (fmap @F f x)
    fmap @(F + F') f (Right x) = Right (fmap @F' f x)
    fmap @(F * F') f (x, y) = (fmap @F f x, fmap @F f y)
    fmap @(Id) f x = f x
    fmap @(Const X) f x = x
    
    这些条款与你第一次提出的条款一致。然后,在
    [Graph a]
    示例中,您给出了一个与此对应的子句:

    fmap @(F . F') f x = fmap @F (fmap @F' f) x
    
    这很好,但这也是我们第一次得到一些不确定性。使其成为函子的一种方法是嵌套
    fmap
    s;但另一种方法是嵌套
    contracmap
    s

    fmap @(F . F') f x = contramap @F (contramap @F' f) x
    
    如果这两个子句都可以,那么
    F
    F'
    中都没有
    Id
    s,因此这两个实例都将返回
    x
    不变

    现在唯一剩下的就是你要问的那个箭盒。但事实证明,这种形式主义非常简单,只有一种选择:

    fmap @(F -> F') f x = fmap @F' f . x . contramap @F f
    
    这就是定义自然
    fmap
    的整个算法的全部细节。。。除了一个细节,这是自然
    对映
    的算法。但是如果你遵循以上所有的方法,你可以自己复制这个算法。我鼓励你试一试,然后对照下面我的答案核对一下

    contramap @(F + F') f (Left x) = Left (contramap @F f x)
    contramap @(F + F') f (Right x) = Right (contramap @F' f x)
    contramap @(F * F') f (x, y) = (contramap @F f x, contramap @F' f y)
    contramap @(F -> F') f x = contramap @F' f . x . fmap @F f
    
    contramap @(F . F') f x = contramap @F (fmap @F' f) x
    -- OR
    contramap @(F . F') f x = fmap @F (contramap @F' f) x
    
    -- contramap @(Id) fails
    contramap @(Const X) f x = x
    
    我个人感兴趣的一件事是:
    contra
    
    contramap @(F + F') f (Left x) = Left (contramap @F f x)
    contramap @(F + F') f (Right x) = Right (contramap @F' f x)
    contramap @(F * F') f (x, y) = (contramap @F f x, contramap @F' f y)
    contramap @(F -> F') f x = contramap @F' f . x . fmap @F f
    
    contramap @(F . F') f x = contramap @F (fmap @F' f) x
    -- OR
    contramap @(F . F') f x = fmap @F (contramap @F' f) x
    
    -- contramap @(Id) fails
    contramap @(Const X) f x = x