First-class types

oleg@pobox.com oleg@pobox.com
Mon, 3 Mar 2003 14:14:49 -0800 (PST)


The following is a more flexible alternative to overloading. We
essentially define a function on types and invoke it, seemingly at run
time. No Dynamics or unsafe computations are employed. We only need
existential types, multi-parameter classes and functional
dependencies. The code also shows how to manipulate values which
cannot be manipulated.

As an example, we define a _function_ (not a method!) 'add' such that:

Main> add True False 
1
Main> add () (5::Int)
5
Main> add () (5::Float)
5.0
Main> add (4::Int) True
5
Main> add (10::Int) (5::Float)
15.0

The example works both in GHC and Hugs. The signature of add is quite
revealing:
	forall b a2 a1.
	(Num b, Coerce a2 b, Coerce a1 b, D a1 a2 b) =>
	a1 -> a2 -> b

That is, 'add' is capable of adding any two _things_, provided they both can
be "generalized" and "coerced" into a number. The "type function" D
computes the generalization of two types -- in a manner we specify.

The function add is actually quite simple:

> add x y = let general_type = typeof x y
>               x' = coerce x general_type
>               y' = coerce y general_type
> 	  in x' + y'

The function 'typeof' is also interesting. It has a type
	forall b a2 a1. (D a1 a2 b) => a1 -> a2 -> b
Note that it returns 'b' forall b! In a sense, the function performs a
type computation at run time.

The code follows. We should note that we could have achieved the same
effect by defining an appropriate class with the method 'add'. In our
solution however, the acts of generalizing, coercion, and addition are
all separated. If we later decide to subtract things rather than add
them, we do not need to alter the class and all the instances. We
merely need to introduce the subtraction function. This makes the
maintenance of the code easier.

The distinct characteristic of the following code is an indirect
manipulation of an untouchable, existential value. We were able to
force a value to be of a specific type without any means of accessing
the value directly.

> class Type a where
>     name :: a -> String
    
> instance Type Bool where
>     name a = "Bool"
    
> instance Type Int where
>     name a = "Int"

> --instance Type Char where
> --    name a = "Char"

> instance Type () where
>     name a = "()"

> instance Type Float where
>     name a = "Float"

> -- Type generalization function: type -> type -> type
> class (Type b) => D a1 a2 b | a1 a2-> b
   
> instance D Bool Bool Int
> instance D Int Bool Int
> instance D Bool Int Int
> instance D Int Int Int
> instance D () Int Int
> instance D Int () Int
> instance D () () Int
> instance D Int Float Float
> instance D () Float Float
> instance D Float Int Float
> instance D Float Float Float

> -- The coercion function

> class Coerce a b where
>     coerce :: a -> b -> b
    
> instance Coerce () Int where
>     coerce _ _ = 0

> instance Coerce () Float where
>     coerce _ _ = 0

> instance Coerce Int Int where
>     coerce = const

> instance Coerce Float Float where
>     coerce = const

> instance Coerce Int Float where
>     coerce x _ = fromInteger $ toInteger x
   
> instance Coerce Bool Int where
>     coerce True _ = 1
>     coerce False _ = 0

> newtype M a1 a2 = M (forall b.(D a1 a2 b) => b)
> data M1 a1 a2 = M1 a1 a2 (M a1 a2)
> typeof v1 v2 = case (M1 v1 v2 (M undefined)) of M1 _ _ (M y) -> y
> typeof1 v1 v2 = case (M1 v1 v2 (M undefined)) of M1 _ _ y -> y

> add x y = let general_type = typeof x y
>               x' = coerce x general_type
>               y' = coerce y general_type
> 	  in x' + y'