Opened 10 years ago

Closed 8 years ago

#3714 closed feature request (fixed)

Distinguish type parameters from indices

Reported by: simonpj Owned by: chak
Priority: normal Milestone: 7.4.1
Component: Compiler Version: 6.10.4
Keywords: Cc: martijn@…
Operating System: Unknown/Multiple Architecture: Unknown/Multiple
Type of failure: None/Unknown Test Case:
Blocked By: Blocking:
Related Tickets: Differential Rev(s):
Wiki Page:


Martijn van Steenbergen pointed out this program:

{-# LANGUAGE TypeFamilies #-}

module M where

-- Accepted
type family T1 f e :: *
class C1 f where
  op1 :: T1 f e -> Either e a

-- Rejected
class C2 f where
  type T2 f e :: *
  op2 :: T2 f e -> Either e a

At the moment (HEAD) the C1/T1 version is accepted but the C2/T2 declarations are rejected:

TF.hs:12:3: Not in scope: type variable `e'

I think this is just a bug.


Change History (10)

comment:1 Changed 10 years ago by chak

Summary: Associated types missing useful functionalityImprove error message if an associated family declaration has excess parameters

No, it's not a bug. The second example ought to be written:

class C2 f where
  type T2 f :: * -> *
  op2 :: T2 f e -> Either e a

The parameters of an associated family declaration must be a subset of the class parameters as documented at

However, remember that

type family S1 a :: * -> *


type family S2 a b :: *

are not the same thing. S1 has one type index (and in each occurrence must be applied to at least one argument), whereas S2 has two type indices (and in each occurrence must be applied to at least two type arguments). (BTW, I'm not saying that this is the only design that's possible, but it is what we decided to be most useful at the time.)

However, the error message can certainly be improved!

comment:2 Changed 10 years ago by simonpj

Oh yes, silly me. The point is that in a class instance declaration you only get to write one associated type instance declaration, so there is no point in having a type index that is not also a class index.

The syntax for distinguishing type indices from type parameters is horrible, but it's hard to think of something better.


comment:3 Changed 10 years ago by MartijnVanSteenbergen

Moving the type index e to the right of the ::—turning it into a type parameter, like Manuel suggests—complicates writing certain instances. Before I could write:

type instance T2 () e = e -> Bool

But now I have to write:

instance C2 () where
  type T2 () = T2Unit

newtype T2Unit e = T2Unit (e -> Bool)

introducing a newtype whenever the type is not directly eta-reducible. In my case this defeats the purpose of writing these instances, as the goal is to derive a simpler type from a generic representation of a datatype (instances for generic building blocks), not a more complicated one (with newtypes that weren't necessary before).

Just to be clear, there isn't really a problem, because I can just leave the type family outside the class. So the actual question here is: why are [type family inside type class] and [multiple family indices for writing type lambdas] sometimes mutually exclusive?



comment:4 Changed 10 years ago by simonpj

I'm getting there slowly. If you write

type family T1 f :: * -> *    -- T1 has one type index parameter
type instance T1 [f] e = e

you get the error

    Number of parameters must match family declaration; expected 1
    In the type synonym instance declaration for `T1'

Now I think Martijn is actually doing this

type family T2 f e :: *      -- T2 has two type indices, f and e
type instance T2 [f] e = e

This means that T2 gets arity 2, and has two type indices. In principle you could give another type instance that dispatches on the second parameter:

type instance T2 f Char = [f]

although I don't think Martijn intends that.

Returning to T1, could we reasonably expect to declare a type instance with two parameters like this?

type instance T1 [f] e = e

No, we could not. T1 is declared to have arity 1, so GHC ensures that it is saturated (appears applied to one argument). So it'd be fine to write the type Monad (T [f]) for exmaple. But with the above instance, T [f] is in effect a type synonym; or if you like T [f] is \e.e. So it should not appear un-saturated. So T1's arity depends on its argument. This is not good. That's the reason for the rule.

However, if we had a way to distinguish type indices from type parameters, we could say

type family T3 f !e :: *         -- The ! indicates a type parameter (not an index)
type instance T3 [f] e = e

This would say

  • T3 has arity 2 and must always appear applied to two arguments
  • Only f is an index, so only f can be instantiated in a type instance

Note that the indices would not necessarily be the first parameters, although they usually will be.

This would extend naturally to associated types, so you could write this, just as Martijn wishes:

class C f where
  type T4 f !e :: *



comment:5 Changed 10 years ago by MartijnVanSteenbergen

Yes, I do intend to never use specific types for e when writing instances. In every instance it will be a 'polymorphic' e (if polymorphic is the right word here), so it behaves like argument, not an index (using Simon's terms). I'm sorry I haven't been able to make myself clear sooner. :-) The idea behind the bang solution is exactly what I have in mind.

Part of the confusion I think is that I was already expecting this to work: the problem only occurs inside type classes and instances, and the compiler knows exactly where the bangs should go, namely those indices/parameters on the left of the :: that do not appear in the type class header.

comment:6 Changed 10 years ago by igloo

Milestone: 6.14.1

comment:7 Changed 9 years ago by igloo

Blocked By: 4232 added

comment:8 Changed 9 years ago by igloo

Blocked By: 4232 removed

comment:9 Changed 9 years ago by simonpj

Summary: Improve error message if an associated family declaration has excess parametersDistinguish type parameters from indices
Type: bugfeature request

comment:10 Changed 8 years ago by chak

Resolution: fixed
Status: newclosed

I believe SimonPJ added this functionality a few months back. So, I am closing this ticket.

Note: See TracTickets for help on using tickets.