9

I've just been exploring Rank2Types and RankNTypes to try to get familiar with them. But I can't work out why the following does not work.

g :: (forall a. forall b. a -> b) -> x -> y -> (u,v)
g p x y = (p x, p y)

This definition is accepted by the compiler, but it fails when I try to use it:

ghci> g id 1 2

<interactive>:35:3:
    Couldn't match type `a' with `b'
      `a' is a rigid type variable bound by
          a type expected by the context: a -> b at <interactive>:35:1
      `b' is a rigid type variable bound by
          a type expected by the context: a -> b at <interactive>:35:1
    Expected type: a -> b
      Actual type: a -> a
    In the first argument of `g', namely `id'
    In the expression: g id 1 2

I'm struggling to understand why a->a is not acceptable type for the expected a->b.

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
  • 4
    You require a function of type `a -> b` for *all* `a` and `b`. Since `id` is of type `forall a. a -> a` (i.e. it only satisfies the first signature if `a ~ b`) it is not a valid input to `g`. – Chris Taylor Oct 16 '12 at 10:47

2 Answers2

10

For all types a and b a function of type forall a. forall b. a -> b must be able to take a value of type a and produce a value of type b. So it must, for example, be possible to put in an Int and get out a String.

You can not get a String out of id if you put in an Int - you only ever get back the same type that you put in. So id is not of type forall a. forall b. a -> b. In fact there can be no total function of that type without typeclass constraints.


It turns out, you can sort-of-kind-of do something close(ish) to what you want using ConstraintKinds, but it's neither pretty to write nor pretty to use:

The idea is to parametrize g with constraints which specify which conditions need to be met by x, y, u and v and what the relationships between x and u and between y and v need to be. Since we don't need all those constraints in all cases, we also introduce two dummy typeclasses (one for constraints on individual parameters and one for the "relationship constraints), so that we can use those as the constraints where no constraints are needed (GHC is not able to infer constraints if we don't specify them ourselves).

Some example constraints to make this more clear:

  • If we pass in id as the function, x must be equal to u and y must be equal to v. There are no constraints on x, y, u or v individually.
  • If we pass in show, x and y must be instances of Show and u and v must be equal to String. There are no constraints on the relationship between x and u or y and v.
  • If we pass in read . show, x and y need to be instances of Show and u and v need to be instances of Read. Again no constraints on the relationships between them.
  • If we have a type class Convert a b where convert :: a -> b and we pass in convert, then we need Convert x u and Convert y v and no constraints on individual parameters.

So here's the code to implement this:

{-# LANGUAGE Rank2Types, ConstraintKinds, FlexibleInstances, MultiParamTypeClasses #-}

class Dummy a
instance Dummy a

class Dummy2 a b
instance Dummy2 a b

g :: forall c. forall d. forall e. forall x. forall y. forall u. forall v.
     (c x, c y, d u, d v, e x u, e y v) =>
     (forall a. forall b. (c a, d b, e a b) => a -> b) -> x -> y -> (u,v)
g p x y = (p x, p y)

And here's how to use it:

Using show . read to convert between different types of numbers:

> (g :: (Show x, Show y, Read u, Read v, Dummy2 x u, Dummy2 y v) => (forall a. forall b. (Show a, Read b, Dummy2 a b) => a -> b) -> x -> y -> (u,v)) (read . show) 1 2 :: (Double, Int)
(1.0,2)

Using id:

> (g :: (Dummy x, Dummy y, x~u, y~v) => (forall a. forall b. (Dummy a, Dummy b, a~b) => a -> b) -> x -> y -> (u,v)) id 1 2.0
(1,2.0)

Using show:

> (g :: (Show x, Show y, String~u, String~v, Dummy2 x u, Dummy2 x y) => (forall a. forall b. (Show a, String~b, Dummy2 a b) => a -> b) -> x -> y -> (u,v)) show 1 2.0
("1","2.0")

As you can see, this is horribly verbose and unreadable because you need to specify a signature for g every time you use it. Without this, I don't think it's possible to get GHC to correctly infer the constraints (or at least I don't know how).

sepp2k
  • 363,768
  • 54
  • 674
  • 675
  • So, how do I express that the type of `p` can take any type and returns any type? Ie, that it can be `id` or `show` or `(< 10)` etc. Given the implementation of the function, what should its type be? – Peter Hall Oct 16 '12 at 10:47
  • 1
    @PeterHall You can't. Also you won't be able to pass in `show` either way because `x` and `y` aren't instances of `Show`. – sepp2k Oct 16 '12 at 10:51
  • Ah, I've understood part of it now. If I need the arguments to be an `Int` and a `Bool` but the result to be an `(Int, String)` then there is no such function that could satisfy that. The compiler can't deduce that fact, but it's going to reject everything that I try. – Peter Hall Oct 16 '12 at 10:53
  • @PeterHall There's no **total** function that can satisfy that. Something like `f x = f x` (or simply `undefined`) can easily satisfy that. And if you add some typeclass constraints, you can even get total functions that satisfy it, like for example `read . show` if `x` and `y` are instances of `Show` and `u` and `v` are instances of `Read`. Using ConstraintKinds, you could even get this to be somewhat general/useful. You still won't be able to make it do what you had in mind though. – sepp2k Oct 16 '12 at 11:04
  • @PeterHall Turns out it is kind of possible to do what you want with KindConstraints, but I don't recommend it. See my edit. – sepp2k Oct 16 '12 at 12:07
  • Urgh that signature looks horrible... I was just trying to do something similar with just `FunctionalDependencies` somewhat nicer and more general, but it doesn't seem possible. But interesting that it _kind of_ works with `ConstraintKinds`. – leftaroundabout Oct 16 '12 at 12:18
  • Ok, so I feel like I should be able to do something *similar* to: `g :: (forall a. a -> h a) -> x -> y -> (h x, h y)`. The main ugliness is unwrapping the `h`. Is it possible to define type, `Id` similar to `Identity` where `Id x == x`? - I guess not or it would break referential transparency (or would it??) – Peter Hall Oct 16 '12 at 15:27
4

When you look at a function forall a. forall b. a -> b it means it takes a value of any type and can return a value of any type. Suppose f is such a function then you can feed say f 1 to any function or f "hello" to any function because return type of f is still polymorphic while on the other hand once you give a value to id the return type is fixed (it will be of the same type as input value) and hence the error.

Satvik
  • 11,238
  • 1
  • 38
  • 46