1

Suppose I have a type T a b and I want to write an instance declarations e.g. an instance declaration for Functor that ranges over a and not b. Is this possible without defining a newtype?

I read What is the rule of the order of multiple type variables in haskell? which leads me to believe that this is not possible, but this seems completely arbitrary to me.

What I'm hoping for is something like:

instance Functor (T * b)
fredefox
  • 681
  • 3
  • 11
  • You might be able to get away with `type Flip f a b = f b a` and `TypeSynonymInstances`, but really you should change the argument order or use a newtype instead. – Lazersmoke May 23 '17 at 12:58
  • 1
    @Lazersmoke: That won't work, since type synonyms need to be fully applied. – Antal Spector-Zabusky May 23 '17 at 14:06
  • 1
    I know it doesn't answer the question directly but perhaps make it a Bifunctor so you can map over a or b or both? – ppb May 23 '17 at 15:22
  • 1
    @ppb 's suggestion is a good alternative. It also give you the option of using [`Data.Bifunctor.Flip`](https://hackage.haskell.org/package/bifunctors-5.4.1/docs/Data-Bifunctor-Flip.html) (yes, it's a newtype, but at least it's one you don't have to define). – duplode May 23 '17 at 16:09

1 Answers1

6

It's not arbitrary. It's how Hindley-Milner type inference works with higher-kinded types and curried type constructor application. Unification of type variables is based on the concept of generative type constructors. That is, if (f a) ~ (g b), then f ~ g and a ~ b, where ~ is type equality.

Let's apply that to unification of an expression like (fmap f someT), for someT :: T a b. I'll start by giving everything other than someT fresh type variables, to work the way unification does.

someT :: T a b
fmap :: (c -> d) -> f c -> f d
f :: e

By the fact that fmap is provided f as a first argument, we unify (c -> d) ~ e. So..

fmap f :: f c -> f d
someT :: T a b

This is where generativity comes into it. From here, we see (f c) ~ (T a b). Let me add some additional parenthesis for clarity: (f c) ~ ((T a) b). This is how type constructors work in Haskell - they are curried in the same way as term-level functions. By generativity, f ~ (T a) and c ~ b.

Then from that:

fmap f someT :: T a d

So it's necessarily true that the Functor instance must operate only on the second type argument of the type T.

Of course this all goes back to the type inference algorithm. You could give up Hindley-Milner or curried type constructors to make things work differently. But the result would be very different from Haskell in far more ways than just allowing a couple instances that Haskell doesn't.

Carl
  • 26,500
  • 4
  • 65
  • 86
  • Ahh. Stack overflow. One of the few places on the internet where people are genuinely​ helpful. Thank you very much for such and elaborate answer! – fredefox May 23 '17 at 23:09