3型和更多类型之间的同构使用镜头 [英] Isomorphisms between 3 and more types using lens
问题描述
convert转换
。 假设我有3个ADT:
data AB = A | B导出(显示)
数据CD = C | D导出(显示)
数据EF = E | F导出(显示)
使用 $ b
$ b
{ - #LANGUAGE MultiParamTypeClasses# - }
class同构ab其中
转换:: Iso'ab
instance Isomorphic AB CD其中
convert = iso ab2cd cd2ab
其中ab2cd A = C
ab2cd B = D
cd2ab C = A
cd2ab D = B
实例同构AB EF其中
convert = iso ab2ef ef2ab
其中ab2ef A = E
ab2ef B = F
ef2ab E = A
ef2ab F = B
将 A
转换为 E
很简单: A ^ .convert :: EF
。将 D
转换为 B
也很容易: D ^ .from convert :: AB code>。但如果我想通过
A
从 C
转换为 E
>,我必须注释每种中间转换的类型:
(C ^ .from convert :: AB)^。convert: :EF
我理解编译器为什么不能推断中间类型。可能有几种同态现象可以从 C
到 E
中获得。但我可以简化我的代码,所以我不手动注释到处类型?
我可以写另一个实例直接转换 CD
和 EF
,但如果我有超过3种类型?如果我有5个同构类型,我将不得不指定10个实例,因为同构对象之间的isos数量是完整图形中的一些边,这是一个三角数字。我更愿意指定 n-1
实例,并进行折衷,我写更多 convert
或转换
。
是否有一种惯用的方法可以使用 Iso
from lens
,这样样板数量最少,我不必键入所有内容?如果我必须使用TemplateHaskell,那么我该怎么做?
动机是,在我的工作中,我有许多荒谬复杂但愚蠢的类型,其中() - > (() - >()) - > X
和((),X)
同构于 X
。我不得不手动包装和解包所有东西,我希望有一些多态的方法来将复杂类型简化为更简单的同构类型。
{ - #LANGUAGE TypeFamilies# - }
import Control.Lens
导入Unsafe.Coerce
数据AB = A | B导出(显示)
数据CD = C | D导出(显示)
数据EF = E | F导出(显示)
类同构a其中
类型Hub a
convert :: Iso'a(Hub a)
viaHub :: :(同构a,同构b,Hub a〜Hub b)=> a - > b
viaHub x = x ^。兑换 。从转换
实例同构AB其中
类型Hub AB = AB
convert = id
实例同构CD其中
类型Hub CD = AB
convert = unsafeCoerce - 因为我懒得做正确的
实例同形EF其中
类型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 =>单元(a - > b)单元_ =单元
实例同构X其中
类型Hub X = X
convert = id
实例(单元a,同构b)=>同构(a - &b; b)其中
类型Hub(a - > b)= Hub b
convert = iso($ unit)const。转换
实例同构a =>同构((),a)其中
类型Hub((),a)= Hub a
convert = iso snd((,)())。转换
实例同构a =>同构(a,())其中
类型Hub(a,())= Hub a
convert = iso fst(flip(,)())。转换
现在您将获得,例如
<$ (() - >()) - > X) - >(& ((),X)
Inspired by a question on polymorphic function between ADTs I'm trying to create isomorphisms between multiple (not just 2) types, so that every time I need an isomorphic but not the same type, I can sprinkle my code with some convert
.
Suppose I have 3 ADTs:
data AB = A | B deriving (Show)
data CD = C | D deriving (Show)
data EF = E | F deriving (Show)
Using lens
I can implement 2 isomorphisms between AB and CD, and CD and EF:
{-# 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
Converting A
to E
is easy: A^.convert :: EF
. Converting D
to B
is also easy: D^.from convert :: AB
. But if I want to convert from C
to E
via A
, I have to annotate types for every intermediary conversion:
(C^.from convert :: AB)^.convert :: EF
I understand why compiler can't infer intermediate types. It could be that there are several isomorphisms through which one could get from C
to E
. But can I simplify my code so I don't manually annotate types everywhere?
I could just write yet another instance to convert directly between CD
and EF
, but what if I have more than 3 types? If I had 5 isomorphic types, I would have to specify 10 instances, because the number of isos between isomorphic objects is a number of edges in a complete graph, which is a triangular number. I'd rather specify n-1
instances, with a trade-off that I write more convert
or from convert
.
Is there an idiomatic way to establish isomorphisms between multiple types using Iso
from lens
so that there is least amount of boilerplate and I don't have to type-annotate everything? If I have to use TemplateHaskell for that, how do I do that?
The motivation is that in my work I have many ridiculously complicated but stupid types, where () -> (() -> ()) -> X
and ((), X)
are isomorphic to X
. I have to manually wrap and unwrap everything and I would like some polymorphic way to reduce the complicated types to simpler isomorphic types.
You can structure your isomorphisms as a star graph: have a canonical "hub" type to which all others connect. The downside is that you will have to specify the hub explicitly in each instance, and you will only be able to convert between types which share a hub. However your two requirements (good type inference, and a linear number of instances) will be met. Here's how you would do that:
{-# 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
In ghci:
> viaHub A :: EF
E
> viaHub A :: CD
C
> viaHub E :: AB
A
> viaHub E :: CD
C
Here's how you might use this for your examples:
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
Now you will have, e.g.
viaHub :: (() -> (() -> ()) -> X) -> ((), X)
这篇关于3型和更多类型之间的同构使用镜头的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!