尝试在Phantom Type上实现Functor

时间:2015-02-08 14:51:56

标签: haskell

给出以下代数数据类型:

data Foo = Bar Int | Baz Bool deriving Show

我尝试实现一个Functor实例。

instance Functor Foo where
    fmap f (Bar x) = Bar (f x)
    fmap f (Baz x) = Baz (f x)

但我得到了这个编译时错误:

ghci> :l explore/FunctorExplore.hs 
[1 of 1] Compiling Main             ( explore/FunctorExplore.hs, interpreted )

explore/FunctorExplore.hs:3:18:
    The first argument of ‘Functor’ should have kind ‘* -> *’,
      but ‘Foo’ has kind ‘*’
    In the instance declaration for ‘Functor Foo’
Failed, modules loaded: none.

所以,然后我添加了a以尝试满足编译器:

data Foo a = Bar Int | Baz Bool deriving Show

但后来我得到了两个错误,据我所知,这基本上意味着(a -> b)函数无法应用于IntBoola是通用类型,而其他两个是特定类型。

ghci> :l explore/FunctorExplore.hs 
[1 of 1] Compiling Main             ( explore/FunctorExplore.hs, interpreted )

explore/FunctorExplore.hs:4:31:
    Couldn't match expected type ‘Int’ with actual type ‘b’
      ‘b’ is a rigid type variable bound by
          the type signature for fmap :: (a -> b) -> Foo a -> Foo b
          at explore/FunctorExplore.hs:4:9
    Relevant bindings include
      f :: a -> b (bound at explore/FunctorExplore.hs:4:14)
      fmap :: (a -> b) -> Foo a -> Foo b
        (bound at explore/FunctorExplore.hs:4:9)
    In the first argument of ‘Bar’, namely ‘(f x)’
    In the expression: Bar (f x)

explore/FunctorExplore.hs:4:33:
    Couldn't match expected type ‘a’ with actual type ‘Int’
      ‘a’ is a rigid type variable bound by
          the type signature for fmap :: (a -> b) -> Foo a -> Foo b
          at explore/FunctorExplore.hs:4:9
    Relevant bindings include
      f :: a -> b (bound at explore/FunctorExplore.hs:4:14)
      fmap :: (a -> b) -> Foo a -> Foo b
        (bound at explore/FunctorExplore.hs:4:9)
    In the first argument of ‘f’, namely ‘x’
    In the first argument of ‘Bar’, namely ‘(f x)’
Failed, modules loaded: none.

在这种情况下,我觉得无法创建Functor。还有更多我的问题,即我错过了什么?

2 个答案:

答案 0 :(得分:7)

函数式编程的一项关键技能是在特定情况下掌握无所事事的正确方法。例如,当[]意味着需要“一个简单的解决方案”时,出现经典错误,例如,给出[[]]意味着“无解”作为递归搜索的基本情况。因此,幻像类型的Functor个实例,即常量函子,也可用作较大模式的明显基本情况。

我可以提供用于构建类似容器的结构的通用工具包,如下所示:

newtype K a x = K a deriving Functor             -- K for konstant
{- fmap _ (K a) = K a -}

newtype I x = I x deriving Functor               -- I for identity
{- fmap k (I x) = I (k x) -}

newtype P f g x = P (f x, g x) deriving Functor  -- P for product
{- will give (Functor f, Functor g) => Functor (P f g), such that
   fmap k (P (fx, gx)) = P (fmap k fx, fmap k gx) -}

newtype S f g x = S (Either (f x) (g x))         -- S for sum
instance (Functor f, Functor g) => Functor (S f g) where
  fmap k (S (Left  fx))  = S (Left  (fmap k fx))
  fmap k (S (Right gx))  = S (Right (fmap k gx))

现在,任何递归数据结构都可以表示为顶级节点,充当子结构的容器。

data Data f = Node (f (Data f))

例如,如果我想在树叶上创建带数字的二叉树,我可以 写

type Tree = S (K Int) (P I I)

表示树的节点结构是带有Int无子树的叶子或带有一对子树的叉子。我需要K来指出递归子结构的缺席。那么树的类型是Data Tree

这些事情的通常递归方案是

fold :: Functor f => (f t -> t) -> Data f -> t
fold k (Node fd) = k (fmap (fold k) fd)

我们不需要做任何工作来实例化树,因为Tree已经是Functor,因为它是从functorial组件构建的。对于fmap来说,K Int的琐事PatternSynonyms相当于说当你到达一片叶子时递归就会停止。

当然,这些“编码”数据类型使您在通过模式匹配编写普通程序时更难看到您在做什么。这就是pattern Leaf i = Node (S (Left (K i))) pattern Fork l r = Node (S (Right (P (I l, I r)))) 扩展来拯救你的地方。你可以说

Node

恢复通常的界面。我建议不要使用外fold,以适合Nodepattern Leaf i = S (Left (K i)) pattern Fork l r = S (Right (P (I l, I r))) add :: Data Tree -> Int add = fold $ \ t -> case t of Leaf i -> i Fork x y -> x + y 的方式。

K

我只是为IPSK开发了一些通用功能的表面,而这些功能可以推广到很多数据类型。 VoidData.Void案件总是微不足道的,但他们必须在那里。

类似的注意事项适用于{{1}}数据类型(在{{1}}中)。为什么我们懒得引入一个没有值得说的元素的数据类型呢?模拟较大方案的不可能案例。

答案 1 :(得分:4)

由于a是幻像类型,因此您根本无法应用f,但您可以这样做:

instance Functor Foo where
    fmap _ (Bar x) = Bar x
    fmap _ (Baz x) = Baz x