查看同伦类型理论/立方Agda,其中“平等是同构”。我对它不够熟悉,不知道操作上会发生什么,即使 Agda 知道同构类型是相等的,我仍然认为你的“真正的同构”(即具有证明和融合)太高了。
在 GHC 中,可以通过“同构”派生,但我们需要等待依赖类型才能正确验证类型系统中的同构。即便如此,即使您必须在操作上做一些工作,它们也可以用来生成真正的代码。
您已经提到了“代表性平等”(Coercible
),但值得讨论。它支持coerce
基于两个的推导策略:GeneralizedNewtypeDeriving
并且DerivingVia
概括了 GND。
GND 是将同构 ( Coercible USD Int
) 转换为代码的最简单方法:
type USD :: Type
newtype USD = MkUSD Int
deriving
newtype (Eq, Ord, Show, Num)
运营coerce
成本为零,因此它们在运行时不会产生任何成本。这是你在 Haskell 中得到你想要的东西的唯一方法。
同构也可以通过用户定义的类型类来完成。
Representable f
一个手段的实例f
(自然地)与其表示对象的函数同构(Rep f ->)
。newtype使用这种同构来派生可Co
表示函子的函数实例。Pair a
两个值的A由 表示Bool
,因此与 同构Bool -> a
。
这种同构可以Pair
派生Functor
,Applicative
并Monad
通过往返(Bool ->)
:
type Pair :: Type -> Type
data Pair a = a :# a
deriving (Functor, Applicative, Monad)
via Co Pair
instance Distributive Pair where
distribute :: Functor f => f (Pair a) -> Pair (f a)
distribute = distributeRep
instance Representable Pair where
type Rep Pair = Bool
index :: Pair a -> (Bool -> a)
index (false :# true) = \case
False -> false
True -> true
tabulate :: (Bool -> a) -> Pair a
tabulate make = make False :# make True
当您派生Generic
/时Generic1
,编译器会在泛型类型与其泛型表示Rep
/之间生成同构(不要与上面示例Rep1
中的表示对象混淆)。Rep
阶级法规定to
/from
和to1
/from1
见证了同构。类型系统不强制执行这些法律,但如果您派生它们,它们应该遵守。
它们是在 Haskell 中定义泛型实现的主要方式。我最近介绍了两个新类型Generically
和base,作为通用行为的标准名称(在下一个 GHC 版本之前使用generic-data )。您可以派生通用同构并以编程方式在下一行中使用它,而无需离开声明:Generically1
data
type Lists :: Type -> Type
data Lists a = Lists [a] [a] [a]
deriving
stock (Show, Generic, Generic1)
deriving (Semigroup, Monoid)
via Generically (Lists a)
deriving (Functor, Applicative, Alternative)
via Generically1 Lists
>> mempty @(Lists _)
Lists [] [] []
>> empty @Lists
Lists [] [] []
>> Lists "a" "b" "c" <> Lists "!" "." "?"
Lists "a!" "b." "c?"
>> pure @Lists 'a'
Lists "a" "a" "a"
但是,您将不得不支付转换成本,这并不像添加那么简单,{-# Rules "to/from" to . from = id #-}
因为实际实例将出现中间项,例如to (from a <> from b)
. 即使您的“真正的同构”GHC 也无法融合转换,因为它不是to . from
.
还有一个库iso-deriving
(博客)允许通过任意同构派生。