问题
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