[英]How do I make my custom data type an instance of Functor?
我有这种数据类型:
data F a b = F (a -> b)
我的任务是使这个类型成为Functor
class 的一个实例。我知道如何用通常的例子来做到这一点。 即Maybe
。
通常,给定的 function 应用于从包装器中提取的值。 然后将结果替换为相同的包装器类型。 在Maybe
类型为Just
的情况下的包装器。
但是,我的数据类型表示a -> b
类型的 function。 我不明白如何将这个概念转移到使用包装的 function。创建这个数据类型的原因以及实现这个想法的方法都让我迷失了。
我假设我没有完全理解仿函数背后的概念,或者只是错过了给定任务背后的意图,我必须专门定义和使用上面的自定义数据类型......
请帮助我更正以下...
instance Functor (F a) where
我认为其他答案解决了一旦知道要编写的实例后如何编写实例的问题,但并没有给出很好的直觉来确定您首先应该编写的实例。 在这个答案中,我将尝试这样做。
我将从非常简单的开始。 最简单的事情可能是:一个甚至没有任何值的仿函数真的非常容易fmap
。
data F0 a = F0
instance Functor F0 where fmap f F0 = F0
好的,让我们稍微加强我们的游戏。 如果我们只有一个值呢? 好吧,我们只需将 function 应用于该值。 明智的。
data F1 a = F1 a
instance Functor F1 where fmap f (F1 a0) = F1 (f a0)
一旦我们得到两个值,应该发生什么可能就不是那么明显了。 但我要声明,最自然的做法是将传入的 function 应用于两个值:
data F2 a = F2 a a
instance Functor F2 where fmap f (F2 a0 a1) = F2 (f a0) (f a1)
对于三个,我们要将f
应用于所有值:
data F3 a = F3 a a a
instance Functor F3 where fmap f (F3 a0 a1 a2) = F3 (f a0) (f a1) (f a2)
该模式继续如下:无论其中一个类型包含多少a
, fmap
正确的做法是将f
应用于所有这些类型。
事实上,这个模式非常有规律,我认为我们可以将它抽象出来。 让我们编写具有零、一、二、三等值(而不是字段)的类型。 所以:
data N0
data N1 = N1_0
data N2 = N2_0 | N2_1
data N3 = N3_0 | N3_1 | N3_2
这些类型根本没有参数化。 N3
不像F3 a
那样是值的集合,它只是一个索引。 事实上,如果我们愿意,我们可以写一个索引 function:
index3 :: F3 a -> N3 -> a
index3 (F3 a0 a1 a2) n = case n of
N3_0 -> a0
N3_1 -> a1
N3_2 -> a2
不过,这有点令人沮丧。 我们需要为每个尺寸创建一个新的索引 function。 如果事情能更统一一点就好了——一旦我们有了索引类型,我们就想自动获取一个容器,其中包含这些值作为索引。 我们该怎么做? 好吧,一种方法是只存储索引 function 本身; 例如, index3
的预应用版本。 所以,对于我们的新类型F
,我们将有
F N3 a ~= N3 -> a
我们新的与大小无关的索引将只返回 function。举个简单的例子,之前我们可能会写x = F3 5 16 (-92)
,现在我们将这样写:
x ~= \n -> case n of
N3_0 -> 5
N3_1 -> 16
N3_2 -> (-92)
您可以看到如何将其视为预应用索引。 快速思考fmap
,我们可以使用上面的定义: fmap f (F3 a0 a1 a2) = F3 (f a0) (f a1) (f a2)
。 所以:
fmap f x ~= \n -> case n of
N3_0 -> f 5
N3_1 -> f 16
N3_2 -> f (-92)
你可能想在这里暂停一下,想想你如何编写一个 function 来对x
对一些特定的f
进行这种转换,比如\v -> v+1
左右。 因此它的类型类似于foo:: (N3 -> Int) -> (N3 -> Int)
。 请注意, foo
不必进行任何模式匹配; x
,我们将作为它的第一个参数传递给foo
,它已经有感兴趣的模式匹配,所以foo
应该能够使用x
作为它的“模式匹配器”。
让我们写下我们的新类型:
data F n a = F (n -> a)
好的。 现在我已经带您沿着花园小径前进,我们可以对仿函数实例应该做什么有一个扎实的想法。 它应该将传入的 function 应用于所有值,无论它们的索引如何!
凭借这种直觉,我怀疑实例本身会很自然地出现(尽管可能并不容易!很难弄清楚自然选择是什么)。
instance Functor (F n) where
fmap f (F index) = F (\n -> {- ... -})
function 已经是Functor
的一个实例。 因此,您可以使用DeriveFunctor
扩展:
{-# LANGUAGE DeriveFunctor #-}
data F a b = F (a -> b) deriving Functor
如果你想手动实现一个仿函数的实例,你可以看看类型。 如果您为 function 实现此功能,则fmap
的签名应为:
fmap :: Functor f => (b -> c) -> f b -> f c
因此对于f ~ (->) a
,这意味着:
fmap :: (b -> c) -> ((->) a b) -> ((->) a c) -- fmap for (->) a
或更紧凑:
fmap :: (b -> c) -> (a -> b) -> (a -> c) -- fmap for (->) a
这种fmap
只有一种直接的实现。 如果您随后要实现它, instance Functor (F a)
,唯一的区别是从F
数据构造函数中解包 function 并将结果包装回F
数据构造函数中。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.