10

受到关于 ADT 之间多态函数的问题的启发,我试图在多个(不仅仅是 2 个)类型之间创建同构,这样每次我需要一个同构但不相同的类型时,我都可以在我的代码中撒上一些convert.

假设我有 3 个 ADT:

data AB = A | B deriving (Show)
data CD = C | D deriving (Show)
data EF = E | F deriving (Show)

使用lensI 可以实现 AB 和 CD 以及 CD 和 EF 之间的 2 个同构:

{-# LANGUAGE MultiParamTypeClasses #-}
class Isomorphic a b where
  convert :: Iso' a b

instance Isomorphic AB CD where
  convert = iso ab2cd cd2ab
    where ab2cd A = C
          ab2cd B = D
          cd2ab C = A
          cd2ab D = B

instance Isomorphic AB EF where
  convert = iso ab2ef ef2ab
    where ab2ef A = E
          ab2ef B = F
          ef2ab E = A
          ef2ab F = B

转换AE很容易:A^.convert :: EF. 转换DB也很容易:D^.from convert :: AB. 但是如果我想将 from 转换CEvia A,我必须为每个中间转换注释类型:

(C^.from convert :: AB)^.convert :: EF

我理解为什么编译器不能推断中间类型。可能有几个同构可以通过它们从CE。但是我可以简化我的代码,这样我就不用在任何地方手动注释类型了吗?

我可以编写另一个实例来直接在CDand之间进行转换EF,但是如果我有 3 种以上的类型怎么办?如果我有 5 个同构类型,我将不得不指定 10 个实例,因为同构对象之间的 iso 数量是完整图中的边数,它是一个三角形数。我宁愿指定n-1实例,并权衡我写更多convertfrom convert.

Iso有没有一种惯用的方法来使用from建立多种类型之间的同构,lens这样样板文件的数量最少,而且我不必对所有内容进行类型注释?如果我必须为此使用 TemplateHaskell,我该怎么做?

动机是,在我的工作中,我有许多非常复杂但愚蠢的类型,其中() -> (() -> ()) -> X((), X)同构于X. 我必须手动包装和展开所有内容,并且我想要一些多态方法来将复杂类型减少为更简单的同构类型。

4

1 回答 1

11

您可以将您的同构构造为星图:具有所有其他连接到的规范“集线器”类型。缺点是您必须在每个实例中明确指定集线器,并且您只能在共享集线器的类型之间进行转换。但是,您的两个要求(良好的类型推断和线性数量的实例)将得到满足。以下是你将如何做到这一点:

{-# LANGUAGE TypeFamilies #-}
import Control.Lens
import Unsafe.Coerce

data AB = A | B deriving (Show)
data CD = C | D deriving (Show)
data EF = E | F deriving (Show)

class Isomorphic a where
    type Hub a
    convert :: Iso' a (Hub a)

viaHub :: (Isomorphic a, Isomorphic b, Hub a ~ Hub b) => a -> b
viaHub x = x ^. convert . from convert

instance Isomorphic AB where
    type Hub AB = AB
    convert = id

instance Isomorphic CD where
    type Hub CD = AB
    convert = unsafeCoerce -- because I'm too lazy to do it right

instance Isomorphic EF where
    type Hub EF = AB
    convert = unsafeCoerce

在 ghci 中:

> viaHub A :: EF
E
> viaHub A :: CD
C
> viaHub E :: AB
A
> viaHub E :: CD
C

以下是您可以如何将其用于您的示例:

class Unit a where unit :: a
instance Unit () where unit = ()
instance Unit b => Unit (a -> b) where unit _ = unit

instance Isomorphic X where
    type Hub X = X
    convert = id

instance (Unit a, Isomorphic b) => Isomorphic (a -> b) where
    type Hub (a -> b) = Hub b
    convert = iso ($unit) const . convert

instance Isomorphic a => Isomorphic ((), a) where
    type Hub ((), a) = Hub a
    convert = iso snd ((,)()) . convert

instance Isomorphic a => Isomorphic (a, ()) where
    type Hub (a, ()) = Hub a
    convert = iso fst (flip(,)()) . convert

现在你将拥有,例如

viaHub :: (() -> (() -> ()) -> X) -> ((), X)
于 2016-09-09T20:25:06.007 回答