Haskell-具有函数构造函数的数据类型上的自定义仿函数实例

时间:2019-03-15 22:12:15

标签: haskell functor

我无法为自定义数据类型(不能更改)编写自己的functor实例。数据类型定义为:

data Foo a = Baz String (Qux -> Foo a) | Bar a
data Qux = None | Quux String

我的问题是为Foo类型写一个仿函数。具体来说,我不确定如何将函子函数f正确地应用于Foo中的函数。我正在考虑以某种方式在构造函数中调用该函数,但是由于我没有任何可用的Qux,因此我陷入了困境。

instance Functor Foo where
    fmap f (Bar a) = Bar (f a)
    fmap f (Baz s ???) = Baz s (???) -- What goes here?

    -- Clearly, something like this doesn't work
    -- fmap f (Baz s g) = Baz s (f g) 

    -- I've also tried something like this, but I'm not sure where to go from there
    -- fmap f (Baz s (None   -> Bar b)) = Baz s (f b) ???
    -- fmap f (Baz s (Quux x -> Bar b)) = Baz s ???

2 个答案:

答案 0 :(得分:6)

让我们从完成此等式的左侧开始。我们可以编写g将您的函数绑定到变量。

fmap f (Baz s g) = Baz s (???)

然后,我们需要用另一个函数填充问号,该函数需要一个Qux并返回一个Foo b

fmap f (Baz s g) = Baz s (\q -> ???)

我们只能对q做一件事,对它应用g

fmap f (Baz s g) = Baz s (\q -> g q)

但是,这给了我们Foo a,但是我们需要Foo b!我们没有执行此操作的功能。但是,我们确实有f,它需要一个a并返回一个b。如果只有一种方法可以将a -> b转换成Foo a -> Foo b ...哦,等等,这叫fmap

fmap f (Baz s g) = Baz s (\q -> fmap f (g q))

如果要以无点表示法(https://wiki.haskell.org/Pointfree)编写函数,则可以执行此操作。

fmap f (Baz s g) = Baz s (fmap f . g)

答案 1 :(得分:4)

遵循类型:

fmap f (Baz s g) = GOAL
   -- f    :: a -> b
   -- s    :: String
   -- g    :: Qux -> Foo a
   -- GOAL :: Foo b

因此,我们正在寻找Foo b(目标行)。我们可以用BarBaz做一个。 fmap应该保留该结构,因此应该为BazString的{​​{1}}参数可能不应更改,这只是引起问题的第二个参数。

Baz

现在我们必须创建一个fmap f (Baz s g) = Baz s GOAL -- f :: a -> b -- s :: String -- g :: Qux -> Foo a -- GOAL :: Qux -> Foo b 。如果需要执行功能,lambda是必不可少的工具(有其他工具,但是lambda是祖父)。所以做一个lambda:

Qux -> Foo b

请注意,我们现在可以使用fmap f (Baz s g) = Baz s (\x -> GOAL) -- f :: a -> b -- s :: String -- g :: Qux -> Foo a -- x :: Qux <-- NEW -- GOAL :: Foo b 。使用x :: Quxg我们可以制作一个x,然后使用Foo a 递归 1 必填fmap f

请注意,我一次只是一步一步地填写表达式,用目标替换未知参数,然后退后一步来考虑一下我在范围内有哪些变量以及它们的类型是什么。继续执行此操作,直到明确目标的路径为止。


1 递归类型通常将具有相应的递归Foo b定义。递归发生在fmap中的位置与类型递归的方式完全对应。