简体   繁体   English

如何使我的自定义数据类型成为 Functor 的实例?

[英]How do I make my custom data type an instance of Functor?

I have this data type:我有这种数据类型:

data F a b = F (a -> b)

My task is to make this type an instance of the Functor class. I understand how to do this with the usual examples.我的任务是使这个类型成为Functor class 的一个实例。我知道如何用通常的例子来做到这一点。 ie Maybe .Maybe

Usually, the given function is applied to the value which is extracted from the wrapper.通常,给定的 function 应用于从包装器中提取的值。 Then the result is replaced in the same wrapper type.然后将结果替换为相同的包装器类型。 The wrapper in the case of the Maybe type being Just .Maybe类型为Just的情况下的包装器。

However, my data type represents a function of type a -> b .但是,我的数据类型表示a -> b类型的 function。 I don't understand how to transfer this concept to use a wrapped function. The reason to create this data type as well as the way to implement this idea are both lost on me.我不明白如何将这个概念转移到使用包装的 function。创建这个数据类型的原因以及实现这个想法的方法都让我迷失了。

I'm assuming I'm not fully understanding the concept behind functors or just missing the intention behind the given task where I must specifically define and use the custom data type above...我假设我没有完全理解仿函数背后的概念,或者只是错过了给定任务背后的意图,我必须专门定义和使用上面的自定义数据类型......

Please help me to correct the following...请帮助我更正以下...

instance Functor (F a) where

I think the other answers address how to write an instance once you know what instance you want to write, but don't give a very good intuition for working out what instance you should want to write in the first place.我认为其他答案解决了一旦知道要编写的实例后如何编写实例的问题,但并没有给出很好的直觉来确定您首先应该编写的实例。 In this answer, I'll attempt to do that.在这个答案中,我将尝试这样做。

I'm going to start very simple.我将从非常简单的开始。 The simplest thing that could possibly be: a functor that doesn't even have any values in it is really, really easy to fmap over.最简单的事情可能是:一个甚至没有任何值的仿函数真的非常容易fmap

data F0 a = F0
instance Functor F0 where fmap f F0 = F0

Okay, let's step up our game slightly.好的,让我们稍微加强我们的游戏。 What if we've got one value?如果我们只有一个值呢? Well, we just apply our function to that value.好吧,我们只需将 function 应用于该值。 Sensible.明智的。

data F1 a = F1 a
instance Functor F1 where fmap f (F1 a0) = F1 (f a0)

Once we get to two values, it may not be blindingly obvious what should happen.一旦我们得到两个值,应该发生什么可能就不是那么明显了。 But I'm going to claim that the most natural thing is to apply the incoming function to both values:但我要声明,最自然的做法是将传入的 function 应用于两个值:

data F2 a = F2 a a
instance Functor F2 where fmap f (F2 a0 a1) = F2 (f a0) (f a1)

With three, we want to apply f to all the values:对于三个,我们要将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)

The pattern continues like this: no matter how many a s one of these types contain, the right thing for fmap to do is to apply f to all of them.该模式继续如下:无论其中一个类型包含多少afmap正确的做法是将f应用于所有这些类型。

In fact, the pattern is so regular, that I think we can abstract it.事实上,这个模式非常有规律,我认为我们可以将它抽象出来。 Let's write types that have zero, one, two, three, and so on values (rather than fields).让我们编写具有零、一、二、三等值(而不是字段)的类型。 So:所以:

data N0
data N1 = N1_0
data N2 = N2_0 | N2_1
data N3 = N3_0 | N3_1 | N3_2

These types aren't parameterized at all.这些类型根本没有参数化。 Rather than being the collection of values, like F3 a was, an N3 is just an index. N3不像F3 a那样是值的集合,它只是一个索引。 In fact, if we wanted, we could write an indexing function:事实上,如果我们愿意,我们可以写一个索引 function:

index3 :: F3 a -> N3 -> a
index3 (F3 a0 a1 a2) n = case n of
    N3_0 -> a0
    N3_1 -> a1
    N3_2 -> a2

This is kind of frustrating, though.不过,这有点令人沮丧。 We need a new indexing function for each size.我们需要为每个尺寸创建一个新的索引 function。 It would be nice if things were a bit more uniform -- once we have a type for indices, we'd like to automatically get a container with those values as indices.如果事情能更统一一点就好了——一旦我们有了索引类型,我们就想自动获取一个容器,其中包含这些值作为索引。 How can we do that?我们该怎么做? Well, one way is to just store the indexing function itself;好吧,一种方法是只存储索引 function 本身; a pre-applied version of index3 , for example.例如, index3的预应用版本。 So, for our new type F , we'll have所以,对于我们的新类型F ,我们将有

F N3 a ~= N3 -> a

and our new size-independent indexing will just return that function. As a quick example, where before we might have written x = F3 5 16 (-92) , now we'll write something like this:我们新的与大小无关的索引将只返回 function。举个简单的例子,之前我们可能会写x = F3 5 16 (-92) ,现在我们将这样写:

x ~= \n -> case n of
   N3_0 -> 5
   N3_1 -> 16
   N3_2 -> (-92)

You can see how this might be thought of as a pre-applied index.您可以看到如何将其视为预应用索引。 Thinking about fmap quickly, we can use our definition from above: fmap f (F3 a0 a1 a2) = F3 (f a0) (f a1) (f a2) .快速思考fmap ,我们可以使用上面的定义: fmap f (F3 a0 a1 a2) = F3 (f a0) (f a1) (f a2) So:所以:

fmap f x ~= \n -> case n of
    N3_0 -> f 5
    N3_1 -> f 16
    N3_2 -> f (-92)

You might want to pause here and think about how you might write a function that did this transformation on x for some specific f like \v -> v+1 or so.你可能想在这里暂停一下,想想你如何编写一个 function 来对x对一些特定的f进行这种转换,比如\v -> v+1左右。 So it would have a type like foo:: (N3 -> Int) -> (N3 -> Int) .因此它的类型类似于foo:: (N3 -> Int) -> (N3 -> Int) Note that foo shouldn't have to do any pattern matching;请注意, foo不必进行任何模式匹配; x , which we are going to pass in to foo as its first argument, already has the pattern match of interest in it, so foo should be able to use x as its "pattern matcher". x ,我们将作为它的第一个参数传递给foo ,它已经有感兴趣的模式匹配,所以foo应该能够使用x作为它的“模式匹配器”。

Let's write down our new type:让我们写下我们的新类型:

data F n a = F (n -> a)

Okay.好的。 Now I've led you down the garden path, and we can have a grounded thought about what the functor instance should do.现在我已经带您沿着花园小径前进,我们可以对仿函数实例应该做什么有一个扎实的想法。 It should apply the incoming function to all the values, no matter their index!它应该将传入的 function 应用于所有值,无论它们的索引如何!

With that intuition, I suspect the instance itself will come quite naturally (though perhaps not easily! it can be hard to figure out what the natural choice is).凭借这种直觉,我怀疑实例本身会很自然地出现(尽管可能并不容易!很难弄清楚自然选择是什么)。

instance Functor (F n) where
    fmap f (F index) = F (\n -> {- ... -})

A function is already an instance of Functor . function 已经是Functor的一个实例。 So you can make use of the DeriveFunctor extension:因此,您可以使用DeriveFunctor扩展:

{-# LANGUAGE DeriveFunctor #-}

data F a b = F (a -> b) deriving Functor

if you want to manually implement an instance of functor, you can look at the types.如果你想手动实现一个仿函数的实例,你可以看看类型。 If you are implementing this for a function, then the signature of fmap should be:如果您为 function 实现此功能,则fmap的签名应为:

fmap :: Functor f => (b -> c) -> f b -> f c

and thus for f ~ (->) a , this means:因此对于f ~ (->) a ,这意味着:

fmap :: (b -> c) -> ((->) a b) -> ((->) a c)  -- fmap for (->) a

or more compact:或更紧凑:

fmap :: (b -> c) -> (a -> b) -> (a -> c)  -- fmap for (->) a

there is only one straightforward implementation for such fmap .这种fmap只有一种直接的实现。 If you are then implementing this for instance Functor (F a) , the only difference is to unwrap the function from the F data constructor and wrap the result back in an F data constructor.如果您随后要实现它, instance Functor (F a) ,唯一的区别是从F数据构造函数中解包 function 并将结果包装回F数据构造函数中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM