Making a datatype an instance of Functor to map on a field which is of parametric type

守給你的承諾、 提交于 2020-05-16 04:06:12

问题


Follow up on this question about Learn You a Haskell for Great Good.

The author, at the end of Chapter 8 declares this datatype (slighly simplified, I hope it's fine)

data Barry t k p = BarryV p (t k) deriving (Show)

and then makes it an instance of Functor

instance Functor (Barry a b) where
  fmap f (BarryV x y) = BarryV (f x) y

then concluding

There we go! We just mapped the f over the first field.

Yes. The first. So my question is: what if I want to map over, say, the second field?

Actually the second field cannot be of a type as simple as Int, Char, Float, and so on; it has to be of a type which can be obtained as a type constructor applied to a concreate type (the italicised text is the same as "parametric type", right?), such as Just 3, Right "hello", "hello", [1..10], and so on; therefore mapping on the second field and mapping on the content of the second field seems different.

I'm really confused, but I guess the last paragraph is enough of an effort that I show.


回答1:


The easiest way is to just make a function with your type as a concrete type that does it:

mymap :: (t1 k1 -> t2 k2) -> Barry t1 k1 p -> Barry t2 k2 p
mymap f (BarryV x y) = BarryV x (f y)

If you really wanted to do it with an existing typeclass, you could probably chain together enough transformations through wrapper types like Product or Compose to get something like a Bifunctor to pop out, but I don't think that's worth doing in this case.




回答2:


The Functor type class is too general to apply a map over the type t k of the second field, but it could apply a map over the concrete type k within the type of the second field. So, using the terminology from your question, we can't use Functor to map over the second field of type t k, but we can use it to map over the content of type k within the second field of type t k (provided t is the sort of structure that allows mapping over its contents).

With respect to trying to use Functor to map over the type t k, the problem is that it allows transformations that would violate the definition of the Barry type. The following function:

censor :: (Functor f) => f a -> f ()
censor = (() <$)

should apply to any functor instance, replacing fields of the targetted type a with unit (). For example:

> censor (Just 5)
Just ()
> censor [1..5]
[(),(),(),(),()]

If Barry was somehow a functor in the type t k of its second field, then I would be able to take a valid Barry value:

> let myBarry = BarryV 10 "hello" :: Barry [] Char Int

and apply censor to it to censor its second field:

> censor myBarry
BarryV 10 ()

But what is the type of this value? It's clearly Barry t k Int for some t and k such that t k = (), but that's impossible. There's no way to "split" the type () into two parts t and k. So, BarryV 10 () isn't a value of a valid Barry type, and it's existence would mean we'd constructed an invalid Barry type in our program.

On the other hand, we could create a Functor instance for Barry in the k parameter. We can't do this directly, because Haskell syntax only allows us to define Functor instances for a type expression that target its "last" parameter. So Barry t k p can be made a Functor in the last parameter p by defining a Functor instance for Barry t k, but it can't be made a Functor in the middle parameter k.

If we had a variant with the parameters in a different order:

data Larry p t k = LarryV p (t k) deriving (Show)

then we could define the Functor instance:

instance Functor (Larry p t) where
  fmap f (LarryV p tk) = LarryV p (fmap f tk)

This gives a type error, saying that there's no Functor instance for t, but if we restrict ourselves to defining this instance only when we have Functor t, it works fine:

instance Functor t => Functor (Larry p t) where
  fmap f (LarryV p tk) = LarryV p (fmap f tk)

Now, as long as t is a Functor, we have Larry p t a Functor. For example:

> let myLarry = LarryV 10 "hello"
> :t myLarry
myLarry :: Num p => Larry p [] Char
> import Data.Char
> fmap toUpper myLarry
LarryV 10 "HELLO"

This works because t = [] is a Functor, so we get the instance we need.

Note that in practical code, instead of introducing a new type Larry, the standard way of defining a Functor instance in a "middle" parameter is to use a newtype wrapper, something like:

newtype Barry' p t k = Barry' (Barry t k p)
instance Functor t => Functor (Barry' p t) where
  fmap f (Barry' (BarryV p tk)) = Barry' (BarryV p (fmap f tk))


来源:https://stackoverflow.com/questions/61738702/making-a-datatype-an-instance-of-functor-to-map-on-a-field-which-is-of-parametri

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!