自动为参数较高的数据生成映射功能

时间:2018-08-11 14:53:15

标签: haskell metaprogramming generic-programming higher-kinded-types

考虑数据类型

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,我很想知道这一点!谢谢。

3 个答案:

答案 0 :(得分:4)

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.(<$>)

答案 1 :(得分:1)

编辑:哦,我应该明确地说,这是一个手动方法-它是一个指向具有很多有用函数和类型类但没有TH生成所需内容的包的指针。我确定拉请求很受欢迎。

parameterized-utils package提供了一组丰富的较高等级的类。根据您的需要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)

答案 2 :(得分:1)

如果您仍然不喜欢使用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相同。{p}