3
votes

Sorry about bad title. I have a problematic data type, which I'm trying to define as instance of functor.

So basically, what I have is something, which has

sample_logp :: s a -> a

and it should be transformed using

(a -> b)

to

sample_logp :: s b -> b

. The following code does not quite accomplish this, and succeeds only in

sample_logp :: s a -> b  

.

data Model s a = Model {
  sample_logp :: s a -> a
}

instance Functor (Model s) where
  fmap f m = Model {
    sample_logp = sample_logp'
  } where sample_logp' x = (f . (sample_logp m)) x

Is what I'm trying even possible? If so, how could this code be updated to achieve this?

1
It cannot be a functor: a is at both covariant and contravariant position. - zakyggaps
Thank you! In that case, is there some other type class which would achieve this? - tero
You may check Profunctor, but the design choice depends heavily on what s is. - zakyggaps
@tero If you can restrict s to be a Contravariant, you can define a functor instance for Model s. It's hard to tell whether that would be useful for your case. - chi
You can write an Invariant instance for it, but that may not win you much. - dfeuer

1 Answers

9
votes

The standard approach here is to add more type variables.

data Model s a b = Model {
  sample_logp :: s a -> b
}

Once you have split the type variables, you have access to more tools. The Profunctor class is appropriate here. (Not typechecked since I don't have ghc on this system - comment or just fix it if my implementation is off.)

instance (Functor s) => Profunctor (Model s) where
    dimap f g (Model h) = Model $ g . h . fmap f
    lmap f (Model h) = Model $ h . fmap f
    rmap g (Model h) = Model $ g . h

Now, given that you have a Model s a a, which is the equivalent of your Model s a, you can convert it to a Model s b b by using dimap bToA aToB.

As the comments say, your original data type is invariant because it uses the same type variable in positive and negative positions. This means you need to supply conversion functions in each direction. Adding an extra type variable lets you take advantage of existing tools for doing that, like Profunctor.


Note that all of the above is based on the assumption that you are using covariant types for s. If s is contravariant, then you can write a direct Functor instance for your original type, as chi's comment says. That's a much less common situation, though.