为什么我必须按字段强制转换这种数据类型,而不是一次全部强制转换?
Why do I have to coerce this data type by fields, rather than all at once?
我有两个类型 (<->)
和 (<-->)
表示类型之间的同构:
data Iso (m :: k -> k -> *) a b = Iso { to :: m a b, from :: m b a }
type (<->) = Iso (->)
infix 0 <->
data (<-->) a b = Iso' { to' :: a -> b, from' :: b -> a }
infix 0 <-->
两者之间的唯一区别是 (<->)
是更通用类型的特化。
我可以 coerce
(<-->)
同构很容易:
coerceIso' :: (Coercible a a', Coercible b b') => (a <--> b) -> (a' <--> b')
coerceIso' = coerce
但是当我尝试使用 (<->)
同构时出现错误:
coerceIso :: (Coercible a a', Coercible b b') => (a <-> b) -> (a' <-> b')
coerceIso = coerce
{-
src/Data/Iso.hs:27:13: error:
• Couldn't match type ‘a’ with ‘a'’ arising from a use of ‘coerce’
‘a’ is a rigid type variable bound by
the type signature for:
coerceIso :: forall a a' b b'.
(Coercible a a', Coercible b b') =>
(a <-> b) -> a' <-> b'
at src/Data/Iso.hs:25:1-73
‘a'’ is a rigid type variable bound by
the type signature for:
coerceIso :: forall a a' b b'.
(Coercible a a', Coercible b b') =>
(a <-> b) -> a' <-> b'
at src/Data/Iso.hs:25:1-73
-}
我目前的解决方法是分别强制执行前进和后退功能:
coerceIso :: (Coercible a a', Coercible b b') => (a <-> b) -> (a' <-> b')
coerceIso (Iso f f') = Iso (coerce f) (coerce f')
但为什么需要这样的解决方法?为什么不能直接强制(<->)
?
问题在于参数 m
在一般 Iso
类型中的作用。
考虑:
data T a b where
K1 :: Int -> T () ()
K2 :: String -> T () (Identity ())
type (<->) = Iso T
即使 ()
和 Identity ()
是可转换的,您也不能真正期望能够将 T () ()
转换为 T () (Identity ())
。
你需要类似的东西(伪代码):
type role m representational representational =>
(Iso m) representational representational
但我相信目前 Haskell 无法做到这一点。
不是直接的答案,但我想分享这个相关技巧:只要 m
是 profunctor(我怀疑通常是),您可以使用 Yoneda-esque转换为具有代表性参数的等效类型。
newtype ProYo m a b = Yo2 (forall x y. (x -> a) -> (b -> y) -> m x y)
ProYo m
同构于 m
,除了它的参数角色是代表性的,通过以下同构:
toProYo :: (Profunctor m) => m a b -> ProYo m a b
toProYo m = ProYo (\f g -> dimap f g m)
fromProYo :: ProYo m a b -> m a b
fromProYo (ProYo p) = p id id
如果我们根据此
定义您的Iso
data Iso m a b = Iso { to :: ProYo m a b, from :: ProYo m b a }
coerceIso
无需修改即可通过。
我有两个类型 (<->)
和 (<-->)
表示类型之间的同构:
data Iso (m :: k -> k -> *) a b = Iso { to :: m a b, from :: m b a }
type (<->) = Iso (->)
infix 0 <->
data (<-->) a b = Iso' { to' :: a -> b, from' :: b -> a }
infix 0 <-->
两者之间的唯一区别是 (<->)
是更通用类型的特化。
我可以 coerce
(<-->)
同构很容易:
coerceIso' :: (Coercible a a', Coercible b b') => (a <--> b) -> (a' <--> b')
coerceIso' = coerce
但是当我尝试使用 (<->)
同构时出现错误:
coerceIso :: (Coercible a a', Coercible b b') => (a <-> b) -> (a' <-> b')
coerceIso = coerce
{-
src/Data/Iso.hs:27:13: error:
• Couldn't match type ‘a’ with ‘a'’ arising from a use of ‘coerce’
‘a’ is a rigid type variable bound by
the type signature for:
coerceIso :: forall a a' b b'.
(Coercible a a', Coercible b b') =>
(a <-> b) -> a' <-> b'
at src/Data/Iso.hs:25:1-73
‘a'’ is a rigid type variable bound by
the type signature for:
coerceIso :: forall a a' b b'.
(Coercible a a', Coercible b b') =>
(a <-> b) -> a' <-> b'
at src/Data/Iso.hs:25:1-73
-}
我目前的解决方法是分别强制执行前进和后退功能:
coerceIso :: (Coercible a a', Coercible b b') => (a <-> b) -> (a' <-> b')
coerceIso (Iso f f') = Iso (coerce f) (coerce f')
但为什么需要这样的解决方法?为什么不能直接强制(<->)
?
问题在于参数 m
在一般 Iso
类型中的作用。
考虑:
data T a b where
K1 :: Int -> T () ()
K2 :: String -> T () (Identity ())
type (<->) = Iso T
即使 ()
和 Identity ()
是可转换的,您也不能真正期望能够将 T () ()
转换为 T () (Identity ())
。
你需要类似的东西(伪代码):
type role m representational representational =>
(Iso m) representational representational
但我相信目前 Haskell 无法做到这一点。
不是直接的答案,但我想分享这个相关技巧:只要 m
是 profunctor(我怀疑通常是),您可以使用 Yoneda-esque转换为具有代表性参数的等效类型。
newtype ProYo m a b = Yo2 (forall x y. (x -> a) -> (b -> y) -> m x y)
ProYo m
同构于 m
,除了它的参数角色是代表性的,通过以下同构:
toProYo :: (Profunctor m) => m a b -> ProYo m a b
toProYo m = ProYo (\f g -> dimap f g m)
fromProYo :: ProYo m a b -> m a b
fromProYo (ProYo p) = p id id
如果我们根据此
定义您的Iso
data Iso m a b = Iso { to :: ProYo m a b, from :: ProYo m b a }
coerceIso
无需修改即可通过。