为具有更高种类参数的数据自动生成映射函数
Automatically generate mapping function for data with higher-kinded parameter
考虑数据类型
data Foo f = Foo {fooInt :: f Int, fooBool :: f Bool}
我想要一个函数 mapFoo :: (forall a. f a -> g a) -> Foo f -> Foo g
。我的选择:
- 我可以手动写。这有点烦人,但最主要的反对意见是我希望
Foo
随着时间的推移获得领域,我希望它尽可能无摩擦,所以必须向这个函数添加一个案例很烦人。
- 我可以编写模板 Haskell。我很确定这并不太难,但我倾向于将 TH 视为最后的手段,所以我希望有更好的东西。
- 我可以使用泛型吗?我导出了
Generic
,但是当我尝试实现 K1
的情况时(专门处理 Rec0
),我不知道该怎么做;我需要它来更改类型。
- 是否还有第四个选项我错过了?
如果有一种通用的方式来编写 mapFoo
而无需使用模板 Haskell,我很想知道它!谢谢。
The rank2classes
package can derive this 给你。
{-# LANGUAGE TemplateHaskell #-}
import Rank2.TH (deriveFunctor)
data Foo f = Foo {fooInt :: f Int, fooBool :: f Bool}
$(deriveFunctor ''Foo)
现在你可以写 mapFoo = Rank2.(<$>)
.
编辑:哦,我应该明确这是一个 手动 方法 - 它是一个指向具有许多有用功能和类型 类 的包的指针但是afaik 没有 TH 来生成你想要的东西。欢迎提出请求,我敢肯定。
parameterized-utils package provides a rich set of higher rank classes. For your needs there's FunctorF:
-- | A parameterized type that is a function on all instances.
class FunctorF m where
fmapF :: (forall x . f x -> g x) -> m f -> m g
这些实例可能就是您所期望的:
{-# LANGUAGE RankNTypes #-}
import Data.Parameterized.TraversableF
data Foo f = Foo {fooInt :: f Int, fooBool :: f Bool}
instance FunctorF Foo where
fmapF op (Foo a b) = Foo (op a) (op b)
这里是基于 GHC.Generics
的实现,如果你仍然不想使用 TemplateHaskell
:
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
import GHC.Generics
data Foo f = Foo {
fooInt :: f Int,
fooBool :: f Bool,
fooString :: f String
} deriving (Generic)
class Functor2 p q f where
fmap2 :: (forall a. p a -> q a) -> f p -> f q
instance (Generic (f p), Generic (f q), GFunctor2 p q (Rep (f p)) (Rep (f q))) => Functor2 p q f where
fmap2 f = to . (gfmap2 f) . from
class GFunctor2 p q f g where
gfmap2 :: (forall a. p a -> q a) -> f x -> g x
instance (GFunctor2 p q a b) => GFunctor2 p q (D1 m1 (C1 m2 a)) (D1 m1 (C1 m2 b)) where
gfmap2 f (M1 (M1 a)) = M1 (M1 (gfmap2 f a))
instance (GFunctor2 p q a c, GFunctor2 p q b d) => GFunctor2 p q (a :*: b) (c :*: d) where
gfmap2 f (a :*: b) = gfmap2 f a :*: gfmap2 f b
instance GFunctor2 p q (S1 m1 (Rec0 (p a))) (S1 m1 (Rec0 (q a))) where
gfmap2 f (M1 (K1 g)) = M1 (K1 (f g))
-- Tests
foo = Foo (Just 1) (Just True) (Just "foo")
test1 = fmap2 (\(Just a) -> [a]) foo
test2 = fmap2 (\[a] -> Left "Oops") test1
我不确定是否可以避免 MultiParamTypeClasses
使 class Functor2
与定义的 rank2classes
相同。
考虑数据类型
data Foo f = Foo {fooInt :: f Int, fooBool :: f Bool}
我想要一个函数 mapFoo :: (forall a. f a -> g a) -> Foo f -> Foo g
。我的选择:
- 我可以手动写。这有点烦人,但最主要的反对意见是我希望
Foo
随着时间的推移获得领域,我希望它尽可能无摩擦,所以必须向这个函数添加一个案例很烦人。 - 我可以编写模板 Haskell。我很确定这并不太难,但我倾向于将 TH 视为最后的手段,所以我希望有更好的东西。
- 我可以使用泛型吗?我导出了
Generic
,但是当我尝试实现K1
的情况时(专门处理Rec0
),我不知道该怎么做;我需要它来更改类型。 - 是否还有第四个选项我错过了?
如果有一种通用的方式来编写 mapFoo
而无需使用模板 Haskell,我很想知道它!谢谢。
The rank2classes
package can derive this 给你。
{-# LANGUAGE TemplateHaskell #-}
import Rank2.TH (deriveFunctor)
data Foo f = Foo {fooInt :: f Int, fooBool :: f Bool}
$(deriveFunctor ''Foo)
现在你可以写 mapFoo = Rank2.(<$>)
.
编辑:哦,我应该明确这是一个 手动 方法 - 它是一个指向具有许多有用功能和类型 类 的包的指针但是afaik 没有 TH 来生成你想要的东西。欢迎提出请求,我敢肯定。
parameterized-utils package provides a rich set of higher rank classes. For your needs there's FunctorF:
-- | A parameterized type that is a function on all instances.
class FunctorF m where
fmapF :: (forall x . f x -> g x) -> m f -> m g
这些实例可能就是您所期望的:
{-# LANGUAGE RankNTypes #-}
import Data.Parameterized.TraversableF
data Foo f = Foo {fooInt :: f Int, fooBool :: f Bool}
instance FunctorF Foo where
fmapF op (Foo a b) = Foo (op a) (op b)
这里是基于 GHC.Generics
的实现,如果你仍然不想使用 TemplateHaskell
:
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
import GHC.Generics
data Foo f = Foo {
fooInt :: f Int,
fooBool :: f Bool,
fooString :: f String
} deriving (Generic)
class Functor2 p q f where
fmap2 :: (forall a. p a -> q a) -> f p -> f q
instance (Generic (f p), Generic (f q), GFunctor2 p q (Rep (f p)) (Rep (f q))) => Functor2 p q f where
fmap2 f = to . (gfmap2 f) . from
class GFunctor2 p q f g where
gfmap2 :: (forall a. p a -> q a) -> f x -> g x
instance (GFunctor2 p q a b) => GFunctor2 p q (D1 m1 (C1 m2 a)) (D1 m1 (C1 m2 b)) where
gfmap2 f (M1 (M1 a)) = M1 (M1 (gfmap2 f a))
instance (GFunctor2 p q a c, GFunctor2 p q b d) => GFunctor2 p q (a :*: b) (c :*: d) where
gfmap2 f (a :*: b) = gfmap2 f a :*: gfmap2 f b
instance GFunctor2 p q (S1 m1 (Rec0 (p a))) (S1 m1 (Rec0 (q a))) where
gfmap2 f (M1 (K1 g)) = M1 (K1 (f g))
-- Tests
foo = Foo (Just 1) (Just True) (Just "foo")
test1 = fmap2 (\(Just a) -> [a]) foo
test2 = fmap2 (\[a] -> Left "Oops") test1
我不确定是否可以避免 MultiParamTypeClasses
使 class Functor2
与定义的 rank2classes
相同。