灵感来自a question on polymorphic function between ADTs我试图在多个(不仅仅是2个)类型之间创建同构,因此每次我需要一个同构但不相同的类型时,我可以用一些{{1 }}
假设我有3个ADT:
convert
使用lens
我可以在AB和CD,CD和EF之间实现2个同构:
data AB = A | B deriving (Show)
data CD = C | D deriving (Show)
data EF = E | F deriving (Show)
将{-# 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
转换为A
非常简单:E
。将A^.convert :: EF
转换为D
也很简单:B
。但是,如果我想通过D^.from convert :: AB
从C
转换为E
,我必须为每个中间转换注释类型:
A
我理解为什么编译器无法推断出中间类型。可能有几个同构,人们可以通过这些同构从(C^.from convert :: AB)^.convert :: EF
到C
。但是我可以简化我的代码,所以我不会在任何地方手动注释类型吗?
我可以写另一个实例直接在E
和CD
之间进行转换,但如果我有超过3种类型呢?如果我有5个同构类型,我将必须指定10个实例,因为同构对象之间的isos数是完整图形中的一些边,即triangular number。我宁愿指定EF
个实例,我需要权衡更多n-1
或convert
。
是否存在使用from convert
中的Iso
在多个类型之间建立同构的惯用方法,以便最少量的样板文件并且我不需要对所有内容进行类型注释?如果我必须使用TemplateHaskell,我该怎么做?
动机是,在我的工作中,我有许多荒谬复杂但又愚蠢的类型,其中lens
和() -> (() -> ()) -> X
与((), X)
同构。我必须手动包装和解包所有内容,我希望采用多态方式将复杂类型简化为更简单的同构类型。
答案 0 :(得分:11)
你可以将你的同构构建为一个星图:拥有一个规范的" hub"所有其他人连接的类型。缺点是您必须在每个实例中明确指定集线器,并且您只能在共享集线器的类型之间进行转换。但是,您的两个要求(良好的类型推断和线性数量的实例)将得到满足。以下是您将如何做到这一点:
{-# 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
现在你将拥有,例如</ p>
viaHub :: (() -> (() -> ()) -> X) -> ((), X)