理解组合仿函数类型的操作

时间:2015-03-16 09:20:30

标签: haskell functional-programming functor

根据几个消息来源,构成仿函数的Haskell实现或多或少如下:

import Data.Functor.Compose

newtype Compose f g a = Compose { getCompose :: f (g a) }

instance (Functor f, Functor g) => Functor (Compose f g) where
    fmap f (Compose x) = Compose (fmap (fmap f) x)

我的问题是:最后一个定义中x的类型是什么?

我说它是f g a,但即使在那里我也很难看到'计算fmap (fmap f) x

有没有人能够提供这一点的清晰完整的实例?如何Tree Maybe Empty关注Node和{{1}}&#39>?

提前谢谢。

2 个答案:

答案 0 :(得分:18)

  

最后一个定义中x的类型是什么?

在谈论此事之前:你可以问GHC! GHC 7.8及更高版本支持TypedHoles,这意味着如果在表达式(非模式)中放置下划线,并点击加载或编译,则会得到一条消息,其中包含下划线的预期类型和变量的类型。本地范围。

newtype Compose f g a = Compose { getCompose :: f (g a) }

instance (Functor f, Functor g) => Functor (Compose f g) where
    fmap f (Compose x) = _

GHC现在说,省略了一些部分:

Notes.hs:6:26: Found hole ‘_’ with type: Compose f g b …
    -- omitted type variable bindings --
    Relevant bindings include
      x :: f (g a)
        (bound at /home/kutta/home/Dropbox/src/haskell/Notes.hs:6:21)
      f :: a -> b
        (bound at /home/kutta/home/Dropbox/src/haskell/Notes.hs:6:10)
      fmap :: (a -> b) -> Compose f g a -> Compose f g b
        (bound at /home/kutta/home/Dropbox/src/haskell/Notes.hs:6:5)
    In the expression: _
    In an equation for ‘fmap’: fmap f (Compose x) = _
    In the instance declaration for ‘Functor (Compose f g)’

你去,x :: f (g a)。经过一些练习,TypedHoles可以帮助您找出复杂的多态代码。让我们试着通过从头开始写出右边来找出我们当前的代码。

我们已经看到这个洞的类型为Compose f g b。因此,我们必须在右侧有一个Compose _

instance (Functor f, Functor g) => Functor (Compose f g) where
    fmap f (Compose x) = Compose _

新洞的类型为f (g b)。现在我们应该看看上下文:

Relevant bindings include
  x :: f (g a)
    (bound at /home/kutta/home/Dropbox/src/haskell/Notes.hs:6:21)
  f :: a -> b
    (bound at /home/kutta/home/Dropbox/src/haskell/Notes.hs:6:10)
  fmap :: (a -> b) -> Compose f g a -> Compose f g b
    (bound at /home/kutta/home/Dropbox/src/haskell/Notes.hs:6:5)

目标是从上下文中的成分中获取f (g b)。不幸的是,上面列表中的fmap指的是正在定义的函数,这有时很有用,但不是这里。我们最好知道fg都是仿函数,因此它们可以fmap。由于我们有x :: f (g a),我们认为我们应该fmap超过x来获得f (g b)

fmap f (Compose x) = Compose (fmap _ x)

现在洞变为g a -> g b。但g a -> g b现在非常简单,因为我们f :: a -> bgFunctor,所以我们也有fmap :: (a -> b) -> g a -> g b,因此fmap f :: g a -> g b

fmap f (Compose x) = Compose (fmap (fmap f) x)

我们已经完成了。

总结技术:

  1. 首先在您不知道如何继续的地方放一个洞。在这里,我们首先将孔放在整个右侧,但通常您会对实现的大多数部分有一个很好的了解,并且您需要在特定的有问题的子表达式中使用该漏洞。

  2. 通过查看类型,尝试缩小哪些实现可能导致目标,哪些不可能。填写新表达式并重新定位孔。在证明助理行话中,这被称为"精炼"。

  3. 重复步骤2,直到你有目标,在这种情况下你已经完成,或者目前的目标似乎是不可能的,在这种情况下回溯直到你做出的最后一个非显而易见的选择,并尝试替代炼油。

  4. 上述技术有时被称为"类型俄罗斯方块"。一个可能的缺点是你可以通过播放" tetris"来实现复杂的代码,而无需真正理解你正在做的事情。有时候在走得太远之后,你会严重陷入游戏中,然后你必须开始考虑问题。但最终它可以让你理解那些本来很难掌握的代码。

    我个人一直使用TypedHoles ,基本上都是反射。我已经非常依赖它了,所以在我不得不回到GHC 7.6的时候,我觉得很不舒服(幸运的是你can emulate holes甚至在那里)。

答案 1 :(得分:4)

x的类型为f (g a)。例如,x可能是整数树的列表:[Tree Int](也可以写为[] (Tree Int),以便更紧密地匹配f (g x)

作为一个例子,考虑函数succ :: Int -> Int,它将一个加一个整数。然后,函数fmap succ :: Tree Int -> Tree Int将递增树中的每个整数。此外,fmap (fmap succ) :: [Tree Int] -> [Tree Int]会将先前的fmap succ应用于列表中的所有树,因此它将递增树列表中的每个整数。

如果您拥有Tree (Maybe Int),那么fmap (fmap succ)将递增此树中的每个整数。 Nothing形式的树中的值不会受到影响,而值Just x会增加x

示例:(GHCi会话)

> :set -XDeriveFunctor
> data Tree a = Node a (Tree a) (Tree a) | Empty deriving (Show, Functor)
> let x1 = [Node 1 Empty Empty]
> fmap (fmap succ) x1
[Node 2 Empty Empty]
> let x2 = [Node 1 Empty Empty, Node 2 (Node 3 Empty Empty) Empty]
> fmap (fmap succ) x2
[Node 2 Empty Empty,Node 3 (Node 4 Empty Empty) Empty]
> let x3 = Just (Node 1 Empty Empty)
> fmap (fmap succ) x3
Just (Node 2 Empty Empty)