【问题标题】:Haskell - Custom functor instance on data type with function constructorHaskell - 具有函数构造函数的数据类型的自定义函子实例
【发布时间】:2019-03-15 22:12:14
【问题描述】:

我在为自定义数据类型(我无法更改)编写自己的仿函数实例时遇到问题。数据类型定义为:

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 ???

【问题讨论】:

  • 第一部分很简单:Baz 是两种类型的乘积,所以你只需要在Baz s g(其中g :: Qux -> Foo a)上进行模式匹配。用g 做什么 是棘手的部分 :)
  • 在 Haskell 中,思考这个问题的方法就是“遵循类型”。你有一个函数f :: a -> b,一个Qux -> Foo a 类型的函数(我称之为g),你想要一个Qux -> Foo b 类型的函数。我很确定只有一种方法可以用你在这里得到的东西来做到这一点 - 并且看到它,不要害怕使用递归;)

标签: haskell functor


【解决方案1】:

让我们从完成这个等式的左边开始。我们可以写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)

【讨论】:

  • 非常感谢!这就是我遇到的问题。我忘了我可以像那样使用 lambdas。
【解决方案2】:

按照类型:

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

所以我们正在寻找Foo b(目标行)。我们可以使用BarBaz 制作一个。 fmap 应该保留结构,所以它应该是 BazBazString 参数可能不应该改变,它只是造成问题的第二个参数。

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

现在我们必须创建一个Qux -> Foo b。如果您需要创建一个函数,lambda 是必不可少的工具(还有其他工具,但 lambda 是祖父)。所以制作一个 lambda:

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 :: Qux。使用gx 我们可以创建Foo a,然后使用fmap f 递归1 我们可以创建所需的Foo b

请注意,我只是一次一小步地填写表达式,用目标替换未知参数,然后退后一步考虑我在范围内有哪些变量以及它们的类型。继续这样做,直到目标路径清晰为止。


1 递归类型通常会有一个对应的递归fmap 定义。 fmap 中发生递归的位置与类型的递归方式完全对应。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-04-28
    • 1970-01-01
    • 1970-01-01
    • 2017-12-29
    • 1970-01-01
    • 1970-01-01
    • 2013-08-14
    • 1970-01-01
    相关资源
    最近更新 更多