A Haskell Programmer's Guide to Chameleon

This document briefly outlines the major features of the Chameleon programming language, as well as use of the system, from a Haskell programmer's point of view.

The Chameleon Language

Chameleon is a Haskell-style language to experiment with advanced type extensions. Chameleon covers most of the Haskell syntax (Take a look here for a detailed list of missing features). In contrast to Haskell, we only support singly-overloaded methods. For example, consider the following Haskell 98 type class and instance declarations

class Eq a where (==) :: a->a->Bool 
instance Eq a => Eq [a] where 
 (==) [] [] = True 
 (==) (x:xs) (y:ys) = ((==) x y) && ((==) xs ys) 
 (==) _ _ False 

which is written as follows in Chameleon

------------
-- eqC.ch --
------------
overload eqC

instance EqC (a->a->Bool) => EqC ([a]->[a]->Bool) where
 eqC [] [] = True 
 eqC (x:xs) (y:ys) = (eqC x y) && (eqC xs ys) 
 eqC _ _ False 

rule EqC a ==> a=t->t->Bool

In Chameleon each constraint Method t refers to an overloaded use of identifier method at type t. Hence, the statement overload eqC is similar to class EqC a where eqC :: a in Haskell. The novelty of Chameleon is to write almost arbitrary programs (on the level of types) in terms Constraint Handling Rules (CHRs). The Chameleon programmer can introduce such CHRs via the rule keyword. For example, the above CHR rule EqC a ==> a=t->t->Bool (in combination with overload eqC) essentially mimics the Haskell class declaration class Eq a where (==) :: a->a->Bool. Note that in Chameleon we do not impose any conditions on type class constraints appearing in the instance context. E.g. Haskell 98 demands that all type class parameters must be variables.

In the following, we present an overview of the major features of the Chameleon programming language, as well as use of the system. We refer the interested reader to the publication section for details regarding type inference and evidence translation in the presence of CHRs.

Constraint Handling Rules

In general, type inference works by generating and solving constraints. Here, constraint solving is performed w.r.t. the relations imposed on overloaded identifiers which are specified in terms of CHRs. For example, in Haskell the declaration class Eq a where (==) :: a->a->Bool states that any use of (==) must be of shape t->t->Bool where the constraint Eq t refers to a particular instance of the equality type class. In Chameleon, we can simply write

rule EqC a ==> a=t->t->Bool

to achieve the same behaviour (keep in mind that constraints in Chameleon refer to exactly one method). Via the rule keyword the Chameleon programmer can introduce the following two forms of CHRs.

rule Method1 t1, ..., Methodn tn ==> s=t

and

rule Method1 t1, ..., Methodn tn ==> Method t

Both kinds are referred to as CHR propagation rules. The first kind allows us to impose stronger conditions on the shape of types. The second kind of CHRs is useful to impose dependencies among overloaded instances. For example, consider the following Chameleon program.
overload ordC
rule OrdC a ==> EqC a
rule OrdC a ==> a=t->t->Bool
instance OrdC (a->a->Bool) => OrdC ([a]->[a]->Bool)

Note that if the instance body is omitted we assume the instance definition is undefined. The constraint symbol OrdC refers to an ordering relation. Hence, it is natural to require that each such instance implies an equality instance EqC. Indeed the above CHRs are pretty much equivalent to class Eq a => Ord a where (<) :: a->a->Bool in Haskell.

Confluence

In Chameleon, CHRs are checked for "consistency" before we proceed with type inference. Consider the following Chameleon example where we have dropped OrdC (a->a->Bool) from the instance context.

overload eqC
overload ordC

instance EqC (a->a->Bool) => EqC ([a]->[a]->Bool) where  -- (E1)
 eqC [] [] = True 
 eqC (x:xs) (y:ys) = (eqC x y) && (eqC xs ys) 
 eqC _ _ = False 

rule EqC a ==> a=t->t->Bool

instance {-OrdC (a->a->Bool)=>-} OrdC ([a]->[a]->Bool)   -- (O1)

rule OrdC a ==> a=t->t->Bool
 
rule OrdC a ==> EqC a                                    -- (E2)

Chameleon complains that CHRs are "non-confluent". Note that each instance C => U t implies a (single-headed) simplification CHR of the form rule U t <==> C (the programmer does not have to specify such simplification rules explicitely, they are automatically generated from instances). We generally require that CHRs are confluent, i.e. the order of CHR applications does not matter. This is not the case for the above example. In case we encounter Ord([a]->[a]->Bool), we can either apply rule (O1) which yields the empty constraint represented by True. Alternatively, we can apply rule (E2) which yields Ord([a]->[a]->Bool),Eq ([a]->[a]->Bool). Applications of rules (E1) and (O1) finally leads to Eq (a->a->Bool) which is different from True. Hence, CHRs are non-confluent. In fact, a similar check is enforced in Haskell, however, the notion of confluence is more general. Note that if one may wish, the confluence check can be switched off via the "--no-conf" option.

Termination

For terminating CHRs, we can test for confluence by checking all "critical pairs". In fact, if the confluence check in Chameleon is invoked we assume that CHRs are terminating. However, we can easily write a "non-terminating" Chameleon program.

instance Erk [a] => Erk a

The CHR implied by the above instance is non-terminating. By default Chameleon performs a simple termination check. Note that CHRs are a Turing-complete language. Hence, any termination check must be necessarily incomplete. Via the "--no-term" option, the user can switch off the termination check.

Range-Restriction

Consider

instance C (Int->Int->Int)
instance C (a->b->c) => D ([a]->[b]) -- (D1)

Chameleon complains that the CHR resulting from (D1) is not "range-restricted". Note that if we ground variables a and b we still find the non-ground term c in the instance context. Range-restrictedness ensures that there are no unconstrained variables which potentially endangers completeness of type inference. Below we consider an instance (Select) which is non-rangestricted but safe! Via the "--no-rr" option, the user may switch off the check for range-restriction.

CHR Solver

A description of the internal details of the CHR solver are beyond the scope of this document. We refer to the publication section for some technical background material. In fact, Chameleon comes with a fully-fledged CHR solver (on the level of types). That is, we also support multi-headed simplification rules. The reader not interested in this additional feature can safely skip the following and move on to Translating Chameleon. In the following Chameleon program we model stack operations (on the level of types) such as push and pop. Their meaning is defined in terms of CHRs.

--------------
-- stack.ch --
--------------
interface Prelude -- will be explained in detail later
                  -- in essence, we teach Chameleon how to use the Haskell 98 Prelude

-- we model the stack as a singleton list
data Nil = Nil
data Cons x xs = Cons x xs

-- some stack elements
data V1 = V1
data V2 = V2
data V3 = V3

overload push
overload pop
overload stack

-- two multi-headed simplification rules
rule Push (x), Stack (s)      <==> Stack (Cons x s)
rule Pop (), Stack (Cons x s) <==> Stack (s)
rule Pop (), Stack (s)         ==> s=Cons x s'

-- we introduce a CHR to model initial constraint store
overload initStore
rule InitStore a <==> Stack (Nil), Push (V1), Push (V2)

run = initStore
Obviously, we cannot run (on the level of values) the above program. But we can execute the CHR program by invoking Chameleon's CHR-based type inference mechanism. For example, the call chameleon --no-term -d stack.ch invokes Chameleon in pure type inference mode (-d) where we have switched of our CHR termination check (--no-term). We reach a command-line environment with similar functionality compared to Hugs or GHCi (besides excuting program code). For example,
stack.ch> :t run
(Stack(Cons V2 (Cons V1 Nil))) => a
allows us to excute the above CHR program. The constraint component of the above type holds the result of running the above CHR program on the given constraint store.

Type Inference

Type inference in Chameleon proceeds by generating the appropriate constraints out of the program text and solving them w.r.t. the given set of CHRs. A program is type correct if there are no inconsistent constraints and all user-provided type annotations are correct. Additionally, we also check that expressions are unambiguous (though the user can switch off this check via the --no-unambig option). In case of a type error, incorrect type annotation or ambiguous expression Chameleon will issue an error message. The issue can then invoke the Chameleon Type Debugger to get some assistance in fixing such errors.

In contrast to other Haskell implementations such as Hugs and GHC we do not pretty-print types. Consider

-------------
-- eq2C.ch --
-------------
interface Prelude

overload eqC
overload ordC

rule EqC a ==> a=t->t->Bool
rule OrdC a ==> a=t->t->Bool
rule OrdC a ==> EqC a

f = ordC

If we invoke Chameleon's type inference/debugging environment (via chameleon -d eq2C.ch) and query the type of f we find the following
eq2C.ch> :t f
(EqC(a -> a -> Bool),OrdC(a -> a -> Bool)) => a -> a -> Bool

The constraint EqC(a -> a -> Bool) might be considered redundant (but harmless). Indeed, OrdC(a -> a -> Bool)) => a -> a -> Bool is equivalent to the above type. We plan to incorporate pretty-printing of types in future versions of Chameleon.

In our current implementation type inference is more "lazy" compared to the Hugs or GHC implementation. Consider

-----------------
-- morelazy.ch --
-----------------
interface Prelude

f x = let h = (x::Int)
      in x

Chameleon reports that f has type a->a whereas e.g. Hugs reports Int->Int. Note that h only constraints the type of x without actually being used in f's function body. Hence, Chameleon neglects the constraints associated to h. Obviously, we could easily enforce "strict" type inference but we have not done so in our current implementation.

Note also that Chameleon does not enforce the Haskell monomorphism restriction. Furthermore, there is no defaulting in Chameleon. For example, the following program is accepted by Chameleon, while Haskell would reject it.

p :: (Int, Float)
p = let n = 1
    in  (n,n)
The monomorphism restriction and defaulting often eliminate bothersome sources of ambiguity in Haskell programs. Consequently, there are ambiguous Chameleon programs whose Haskell counterparts are unambiguous. Consider the following:
one = if 1 == 1 then 1
		else undefined
Chameleon will report this as ambiguous, whereas Haskell will accept it.

It has been observed that all known Haskell implementations reject some seemingly well-typed programs. Here is a recast of a Haskell program published on the Haskell-Cafe mailing list in terms of Chameleon.

-------------------------
-- jones-nordlander.ch --
-------------------------
interface Prelude

-- we model
-- class C t where op :: t->Bool
-- instance C [a]
hconstraint C
extern op :: C t => t->Bool
rule C [a] <==> True

p y = (let {-f :: c->Bool-}
           f x = op (y >> return x)
       in f,  y ++ [])

q y = (y ++ [],
       let f :: c->Bool
           f x = op (y >> return x)
       in f)

Note that Hugs and GHC either reject p or q whereas Chameleon accepts both functions. However, there are still some well-typed programs which are also rejected by the current Chameleon release. A discussion of how to achieve principal types will be part of a forthcoming report.

Translating Chameleon

To resolve overloading on the value level we apply the well-known "evidence" translation scheme. Chameleon is translated to "plain" Haskell. Below, we show how to integrate existing Haskell classes and instances in Chameleon. However, there are some subtle differences among Chameleon-style and Haskell-style overloading. Consider (note that CHRs are now confluent)

overload eqC
overload ordC

instance EqC (a->a->Bool) => EqC ([a]->[a]->Bool) where  
 eqC [] [] = True 
 eqC (x:xs) (y:ys) = (eqC x y) && (eqC xs ys) 
 eqC _ _ = False 

rule EqC a ==> a=t->t->Bool

instance OrdC (a->a->Bool)=> OrdC ([a]->[a]->Bool) 

rule OrdC a ==> a=t->t->Bool
 
rule OrdC a ==> EqC a                              

f :: OrdC (a->a->Bool) => a->a->Bool           -- incorrect type annotation!
f = eqC

The above looks like a perfectly reasonable program. Surprisingly, Chameleon rejects the above program because f's type annotation is incorrect. Note that in Haskell Ord a refers to a type class which contains a method (<) of type a->a->Bool(==) of type a->a->Bool. However, in Chameleon, each constraint Method t refers exactly to an overloaded identifier method of type t. Hence, the statement rule OrdC a ==> EqC a has only a meaning on the level of types, i.e. for each OrdC t there must also be a EqC t. In order to resolve overloading on the value level, we need to explicitely mention a constraint Method t for each use of method at type t.

Consider the following variation of the above program.

---------------
-- eqordC.ch --
---------------
interface Prelude -- will be explained in detail later
                  -- in essence, we teach Chameleon how to use the Haskell 98 Prelude

overload eqC
overload ordC

instance EqC (a->a->Bool) => EqC ([a]->[a]->Bool) where  
 eqC [] [] = True 
 eqC (x:xs) (y:ys) = (eqC x y) && (eqC xs ys) 
 eqC _ _ = False 

rule EqC a ==> a=t->t->Bool

instance OrdC (a->a->Bool)=> OrdC ([a]->[a]->Bool) where
 ordC = undefined
-- if the instance body is left empty we assume the "undefined" instance body by default

rule OrdC a ==> a=t->t->Bool
 
rule OrdC a ==> EqC a                              

f :: OrdC (a->a->Bool) => [a]->[a]->Bool           
f = ordC

The translation yields the following result (call chameleon -o eqordC.hs eqordC.ch).

import Prelude

ec3 :: ((->) (((->) (a)) (((->) (a)) (Bool)))) (((->) ([(a)])) (((->) ([(a)])) (Bool)))
ec3 (pEqCIIARR_v18026I_IIARR_v18026I_BoolII) ([]) ([]) = let {pEqCIIARR_a18373I_IIARR_a18373I_BoolII  
= pEqCIIARR_v18026I_IIARR_v18026I_BoolII} in  (True)
ec3 (pEqCIIARR_v18026I_IIARR_v18026I_BoolII) ((:) (x) (xs)) ((:) (y) (ys)) = let {pEqCIIARR_a18373I_II
ARR_a18373I_BoolII  = pEqCIIARR_v18026I_IIARR_v18026I_BoolII} in  ((&&) (((pEqCIIARR_a18373I_IIARR_a18
373I_BoolII) (x)) (y)) ((((ec3) (pEqCIIARR_a18373I_IIARR_a18373I_BoolII)) (xs)) (ys)))
ec3 (pEqCIIARR_v18026I_IIARR_v18026I_BoolII) (_) (_) = let {pEqCIIARR_a18373I_IIARR_a18373I_BoolII  = 
pEqCIIARR_v18026I_IIARR_v18026I_BoolII} in  (False)

ec83 :: ((->) (((->) (a)) (((->) (a)) (Bool)))) (((->) ([(a)])) (((->) ([(a)])) (Bool)))
ec83 (pOrdCIIARR_v18174I_IIARR_v18174I_BoolII) = let {pOrdCIIARR_a18427I_IIARR_a18427I_BoolII  = pOrdC
IIARR_v18174I_IIARR_v18174I_BoolII} in  (undefined)

f :: ((->) (((->) (a)) (((->) (a)) (Bool)))) (((->) ([(a)])) (((->) ([(a)])) (Bool)))
f (pOrdCIIARR_v18236I_IIARR_v18236I_BoolII) = let {pOrdCIIARR_a18542I_IIARR_a18542I_BoolII  = pOrdCIIA
RR_v18236I_IIARR_v18236I_BoolII} in  ((ec83) (pOrdCIIARR_a18542I_IIARR_a18542I_BoolII))

which can be executed by any Haskell compiler. Briefly, we turn instance declarations into function definitions. E.g. ec3 corresponds to instance EqC (a->a->Bool) => EqC ([a]->[a]->Bool). Note the correspondence between ec3's (pretty-printed) type annotation (a->a->Bool)->[a]->[a]->Bool) and the instance declaration.

Obviously, the Chameleon user does not need to understand the the details of the implementation scheme behind Chameleon. The curious reader is referred to Implementing Overloading in Chameleon . (Note in our latest release we have incorporated the weak entailment check discussed on page 6).

Functional Dependencies

The real fun of programming in Chameleon starts when writing programs on the level of types. We briefly illustrate Chameleon's type level programming ability in connection with "functional dependencies". Functional dependencies are a popular type class extension where the programmer can take (to some extent) control over the type inference process. Consider the following Haskell program.

class C a b | a->b where c :: a->b
instance C Int Bool

The functional dependency | a->b states that in C a b the first component uniquely determines the second component. For example, if type inference encounters C a b and C a c, types are improved by adding the equality constraint b=c. In case of C Int b the functional dependency in combination with above the instance implies that b=Bool. In Chameleon, we can make such improvement rules explicit in terms of CHRs. Here is the Chameleon equivalent of the above program.

overload c
rule C x ==> x=a->b
-- FD rule
rule C (a->b), C (a->c) ==> b=c
instance C (Int->Bool)
-- instance improvement rule
rule C (Int->b) ==> b=Bool

In fact, CHRs not only give us a sound foundation for functional dependencies, CHRs even allow us to go beyond functional dependencies. We present two examples. The interested reader may take a look at "Sound and Decidable Type Inference for Functional Dependencies" which can be found in the publication section .

A Generic Family of Zip Functions

We start off with an encoding in Haskell, point out some limitations of functional dependencies and then present a solution in Chameleon.

-- Haskell encoding
  zip2 :: [a]->[b]->[(a,b)]
  zip2 (a:as) (b:bs) = (a,b) : (zip2 as bs)
  zip2 _      _      = []

  class Zip a b c | a c -> b, b c -> a where 
    zip :: [a] -> [b] -> c
  instance Zip a b [(a,b)] where 
    zip = zip2
  instance Zip (a,b) c e => Zip a b ([c]->e) where
    zip as bs cs = zip (zip2 as bs) cs

  e2 = head (zip [True,False] ['a','b','c'])
  e3 = head (zip [True,False] ['a','b','c'] [1::Int,2])

Note that the inferred types of e2 and e3 are
  e2 :: Zip Bool Char [a] => a
  e3 :: Zip (Bool,Char) Int [a] => a
rather than
  e2 :: (Bool,Char)
  e3 :: ((Bool,Char),Int)
Indeed, the relavant improvement rule here stated in terms of a CHRs is as follows

rule Zip a d [(a,b)]      ==> d=b      

To achieve the desired typing for e2 and e3 we would like to state a slighly stronger condition such as

rule Zip a d [c] ==> c=(a,b), d=b

However, there is no mechanism in Haskell which allows us to do so.

------------
-- zip.ch --
------------
-- Chameleon encoding
-- we have point out parts where Chameleon allows us to impose a stronger improvement rule
interface Prelude  

overload zip

zip2 :: [a] -> [b] -> [(a,b)]
zip2 (x:xs) (y:ys) = (x,y) : (zip2 xs ys) 
zip2 _ _ = [] 

-- enforce
-- class Zip ([a]->[b]->c) where zip :: [a]->[b]->c
rule Zip x ==> x=[a]->[b]->c

-- FD
rule Zip (a->b->c), Zip (a->d->c) ==> b=d
rule Zip (a->b->c), Zip (d->b->c) ==> a=d

-- base case
instance Zip ([a]->[b]->[(a,b)])  where
   zip= zip2

-- instance-improvement
-- rule Zip ([a]->c->[(a,b)]) ==> c=[b]   -- as specified by FD
rule Zip ([a]->d->[c]) ==> d=[b],c=(a,b)  -- though we employ a slightly stronger rule
                                          -- to achieve the desired typing

rule Zip (c->[b]->[(a,b)]) ==> c=[a]

-- general case
instance Zip ([(a,b)]->[cs]->r) => Zip ([a]->[b]->[cs]->r) where
         zip as bs cs = zip (zip2 as bs) cs 

-- instance improvements for this case are trivial

-- annoations are not necessary here
--e1 :: [(Bool,Char)] 
e1 = head (zip [True,False] ['a','b','c'])
--e2 :: [((Bool,Char),Int)]
e2 = head (zip [True,False] ['a','b','c'] [1::Int,2])

-- to run this program
-- you need to call "chameleon -o zip.hs zip.ch"
-- you can then run zip.hs


Obviously, we should not add arbitrary CHRs otherwise we might endanger soundness and decidability of type inference.

Extensible Records

As another application we present an encoding of extensible records in Chameleon. The basic idea is to encode field labels and records in terms of singleton types. Note that a similar encoding with functional dependencies would be too "weak".

---------------
-- record.ch --
---------------
interface Prelude

-- we use singleton lists to model records

data Nil = Nil
data Cons x xs = Cons x xs

-- we use singleton numbers to model record labels

data Zero   = Zero
data Succ n = Succ n

-- result of type level computations
data T = T
data F = F


------ overloaded identifiers ------

overload eqR
overload select
overload select'
overload remove
overload remove'
overload extend
overload check
overload unique
overload unique'


------ equality among types ------

-- class declaration
rule EqR x ==> x=a->b->c

-- FD
rule EqR (a->b->c), EqR (a->b->d) ==> c=d

-- instances
instance EqR (Zero -> Zero -> T) where
         eqR Zero Zero = T
instance EqR (a->b->c) => EqR (Succ a -> Succ b -> c) where  -- (E2)
         eqR (Succ a) (Succ b) = eqR a b
instance EqR (Zero -> Succ a -> F) where
         eqR Zero (Succ a) = F
instance EqR (Succ a -> Zero -> F) where
         eqR (Succ a) Zero = F

-- instance improvement, note that for (E2) instance improvement is trivial
rule EqR (Zero->Zero->a)         ==> a=T
rule EqR (Succ a->Zero->b)       ==> b=F 
rule EqR (Zero->Succ a->b)       ==> b=F     


------ record selection ------

-- usage: select Record Label --> FieldEntry

-- class declaration
rule Select x ==> x=r->l->a

-- FD
rule Select (r->l->a), Select (r->l->b) ==>a=b

-- instance
instance (Select' (Cons (x1,v) e -> x2 -> b -> v'), EqR (x1->x2->b))
                => Select (Cons (x1,v) e -> x2 -> v') where
         select (Cons (x1,v) e) x2 = select' (Cons (x1,v) e) x2 (eqR x1 x2)
-- Chameleon complains that this instance is not range-restricted
-- However, we can argue that variable b (which is the trouble-maker) is
-- functionally defined by variables in the instance head

-- instance improvement is trivial here

-- class declaration
rule Select' x ==> x=e->c->a->b

-- FD
rule Select' (e->c->a->b),Select' (e->c->a->d) ==> b=d

instance Select' (Cons (x,v) e -> x -> T -> v) where
         select' (Cons (_,v) e) _ T = v
instance Select (e->x2->v') => Select' (Cons (x1,v) e -> x2 -> F -> v') where  -- (S'3)
         select' (Cons (x1,v) e) x2 F = select e x2

rule Select' (Cons (a, b) e -> c -> T -> d) ==> b=d 

-- things we couldn't do with FDs
-- stronger improvement rules

rule Select (Nil->l->a) ==> False
rule Select' (Nil->l->t->a) ==> False
rule Select' ((Cons (x1,v1) e)->x2->T->v2) ==> x1=x2, v1=v2

------ record extension ------

-- we don't check whether a field already exists
-- usage: extend (Label,Entry) Record --> Record


-- class declaration

rule Extend a ==> a=(l,x)->r->r'

-- FD
rule Extend (a->b->r), Extend(a->b->r') ==> r=r'

-- instances
instance Extend ((l,x)->xs->Cons (l,x) xs) where
         extend (l,x) xs = Cons (l,x) xs

-- instance improvement
rule Extend ((l,x)->xs->ys) ==> ys=Cons (l,x) xs


------ check that all record labels are unique ------

-- usage: check Record --> Record
-- the identify operation if record is unique, otherwise type error

instance (Unique x) => Check (x->x) where
         check x = x

rule Check a ==> a=t->t
 
instance Unique Nil where
         unique = undefined

instance (Unique ys, Unique' (l,ys)) => Unique (Cons (l,x) ys) where
         unique = undefined

instance Unique' (l,Nil) where
         unique' = undefined

instance (EqR (l->l'->a), Fail a, Unique' (l',ys)) => Unique' (l', Cons (l,x) ys) where
         unique' = undefined

instance Fail F where
         fail = undefined

rule Fail T ==> False

------ field removal ------

-- we don't check whether a field doesn't exist
-- usage: remove Label Record --> Record

-- class declaration
rule Remove a ==> a=l->r->r'

-- FD
rule Remove (l->r->r'), Remove (l->r->r'') ==> r'=r''

-- instances
instance Remove (l -> Nil -> Nil) where
         remove l Nil = Nil

instance (EqR (l->l'->a), Remove' (a->l->(Cons (l',v) r)->r')) 
                 => Remove (l -> (Cons (l',v) r) -> r') where
         remove l (Cons (l',v) r) = remove' (eqR l l') l (Cons (l',v) r)

-- instance improvements
rule Remove (l->Nil->r') ==> r'=Nil

-- class declaration
rule Remove' x ==> x=t->l->r->r'

-- FD
rule Remove' (t->l->r->r1), Remove' (t->l->r->r2) ==> r1=r2

-- instances
instance Remove' (T -> l -> (Cons (l',v) r) -> r) where
         remove' T l (Cons (l',v) r) = r

instance Remove (l->r->r') => Remove' (F -> l -> (Cons (l',v) r) -> (Cons (l',v) r')) where
         remove' F l (Cons (l',v) r) = Cons (l',v) (remove l r)

-- instance improvement
rule Remove' (T->a->(Cons (b,c) d)->e) ==> e=d
rule Remove' (F->a->(Cons (b,c) d)->e) ==> e=Cons (b,c) e'

------ examples ------

l0 = Zero
l1 = Succ l0
l2 = Succ l1

data MyString = A | B | C
data Phone  = P1 | P2


r1 = Nil
--r2 :: Cons (Zero,MyString) Nil
r2 = extend (l0,A) r1
r2' = check r2

r3 = extend (l1,P1) r2
r3' = check r3
-- r3 = extend (l0,P1) r2 yields type error,
--  check r3 verifies that all labels are unique!

x :: MyString
x = select r2 l0

y = remove l0 r3

z = extend (l1, P2) r3
-- note that (check z) yields an error

-- to run this program
-- call "chameleon --no-term --no-rr -o record.hs record.ch"
--  --no-term switches off the termination checker
--  --no-rr allows for non-range-restricted CHRs

Using Haskell from Chameleon

Programs written in Chameleon may make use of Haskell libraries directly, provided that an appropriate Chameleon interface file exists for each library. Library interfaces must be imported into a Chameleon program before the corresponding Haskell library can be used.

The keyword interface Module, allows you to access all of the functions and constructors defined in the Haskell module with the same name, provided that a corresponding interface file exists in the current directory. The interface file for Haskell module Module must be called Module.ch, and must be present in the current directory.

The following Chameleon program makes use of functions provided by the Haskell 98 Prelude.

interface Prelude

myReverse = foldl (flip (:)) []

palin xs = myReverse xs == xs

In our translation of Chameleon programs, we simply replace interface by import statements.

An interface file for the Haskell 98 Standard Prelude is already available, and distributed along with the Chameleon source code. Interfaces for other libraries remain to be created.

Interface Files

Interface files are nothing more than Chameleon source files. From the point-of-view of the compiler, an interface declaration causes the contents of the interface file to be treated as though they were part of the one program. Consequently, interface files should declare only the types of functions or constructors they provide, since their implementation is already part of an existing Haskell library.

The extern keyword can be used to declare the types of both functions and data constructors without providing a definition. It may also be used to declare type constructors.

Its two forms are:

  • extern sig
  • Declares a function/constructor, with the given type.
    e.g. extern fst :: (a,b) -> a , declares a function fst with the usual type; extern Just :: a -> Maybe a , declares the Just constructor of the standard Haskell Maybe type.
  • extern id
  • Declares a new type constructor, id.
    e.g. extern Maybe declares the Maybe type constructor which could then be used to declare the type of Just, as above.

    Note that the Chameleon system currently does not perform any kind checking.

    Consider the following Haskell library code (taken from the Haskell 98 Prelude Documentation):

    data Maybe a = Nothing | Just a

    maybe :: b -> (a -> b) -> Maybe a -> b
    maybe n f Nothing = n
    maybe n f (Just x) = f x

    To make use of this, Chameleon requires an interface file containing the following:

    extern Maybe
    extern Nothing :: Maybe a
    extern Just :: a -> Maybe a

    extern maybe :: b -> (a -> b) -> Maybe a -> b

    Note that the correctness of the interface with respect to the original Haskell code must be ensured by the user.

    Class and instance declarations should be translated to CHRs as described in "A Theory of Overloading" which can be found here (we plan to incorporate an automatic translation in the near future). All Haskell constraints (type class constraints) must also be declared using hconstraint TC declarations. E.g. the interface to a library declaring a type class Eq, must contain the declaration: hconstraint Eq. Note that the user must guarantee that the number of parameters for each type class are consistent throughout the program.

    See the Prelude interface file (Prelude.ch) for more examples. Note that we assume that inferface files must be in the same directory as the Chameleon program.

    Basic Types and Literals in Chameleon

    Chameleon has built-in support for:

    Any of these may appear as terms or as patterns.

    The following types are also available by default: function (infix) t1->t2, list [t], and tuples up to, at least, 7 arguments ; and basic types Bool, Int, Char. These type constructors must currently be fully applied in a syntactically valid Chameleon program. Note that the Prelude interface provides access to all of the standard Haskell types - although their corresponding values might not be easily expressed in Chameleon for now (e.g. Rationals).

    The following Haskell type class constraints are also known to Chameleon: Num, Fractional and Enum. No instances are declared by default - the standard Prelude interface provides all the type class instances you'd expect in Haskell.

    Missing Haskell Features

    Although Chameleon is closely related to Haskell, not all Haskell language features are supported in Chameleon. Notable omissions from Chameleon include:

    Please note also the differences between Haskell and Chameleon type inference as outlined above.

    Support for these (and other features) may be added in future.

    Mixing Haskell and Chameleon Overloading

    Though, we cannot define full Haskell classes and instances we can make use of Chameleon's overloading mechanism. Indeed, as long as all Haskell constraints are declared as such (hconstraint), we can freely mix the two forms of overloading.

    Consider the following Chameleon program.
    interface Prelude
    overload insert
    instance Ord a => Insert (a->[a]->[a]) where
    insert x [] = [x]
    insert x (y:ys) = if x < y then x:y:ys else y:(insert x ys)
    Note that Ord is a Haskell type class whereas Insertrefers to a overloaded Chameleon identifier.

    In Haskell, we would write the following:

    classInsert ce e where insert :: e->ce->ce
    instanceOrd a => Insert [a] a where
    insert x [] = [x]
    insert x (y:ys) = if x < y then x:y:ys else y:(insert x ys)

    When translating such mixed code, we perform evidence translation of overloaded Chameleon identifiers as described above. Haskell type classes are left untouched and will be dealt with when running the translated Chameleon program through a Haskell compiler.

    In fact, by mixing Haskell and Chameleon overloading via interfaces allows us to directly express Examples zip and record in terms of a user-definable Haskell extension! Assume in MZip.hs we find the following Haskell code.

    -------------
    -- MZip.hs --
    -------------
    module MZip where
      mzip2 :: [a]->[b]->[(a,b)]
      mzip2 (a:as) (b:bs) = (a,b) : (mzip2 as bs)
      mzip2 _      _      = []
    
      class MZip a b c | a c -> b, b c -> a where 
          mzip :: [a] -> [b] -> c
      instance MZip a b [(a,b)] where 
          mzip = mzip2	
      instance MZip (a,b) c e => MZip a b ([c]->e) where
          mzip as bs cs = mzip (mzip2 as bs) cs
    

    Assume in MZip.ch we find the following interface specification which correctly describes the class and instance relations from above (Check out "Sound and Decidable Type Inference for Functional Dependencies" which can be found here how to translate functional dependencies to CHRs).

    -------------
    -- MZip.ch --
    -------------
    hconstraint MZip
    extern mzip :: MZip a b c => [a] -> [b] -> c
    rule MZip a b c, MZip a d c ==> b=d       -- (FD1)
    rule MZip a b c, MZip d b c ==> a=d       -- (FD2)
    
    rule MZip a b [(a,b)] <==> True             -- (Inst1)
    rule MZip a b ([c]->e) <==> MZip (a,b) c e  -- (Inst2)
    
    -- instance improvement linked to (Inst1)
    rule MZip a d [(a,b)]      ==> d=b         -- (Impr1a)
    rule MZip d b [(a,b)]      ==> d=a         -- (Impr1b)
    
    -- note that instance improvement for (Inst2) is trivial
    

    Then, we can define our own Haskell extension as follows.

    --------------------
    -- MZip-strong.ch --
    --------------------
    interface Prelude
    interface MZip
    
    -- we're strengthening rules (Impr1a) and (Impr1b)
    rule MZip a d [c] ==> c=(a,b), d=b 
    rule MZip d b [c] ==> c=(a,b), d=a 
    
    e2 = head (mzip [True,False] ['a','b','c'])
    e3 = head (mzip [True,False] ['a','b','c'] [1::Int,2])
    

    Chameleon translates the above program as follows (via chameleon -o MZip-strong.hs MZip-strong.ch).

    import Prelude
    import MZip
    
    e2 :: (Bool,Char)
    e2 = head (mzip [True,False] ['a','b','c'])
    e3 :: ((Bool,Char),Int)
    e3 = head (mzip [True,False] ['a','b','c'] [1::Int,2])
    

    That is, in our own Haskell extension we could sufficiently improve types such that a Haskell compiler can finally run the above program.

    Running Chameleon Programs

    Chameleon programs are translated into Haskell, which must be further compiled, before they can be run. The process is as follows.

    1. Run chameleon InputFile.ch -o OutputFile.hs If any type errors are reported, you may wish to either
    2. Run your favourite Haskell compiler on the resulting output file.
    The Chameleon system also contains a type debugger. To launch the type debugger, run the chameleon executable with the -d flag.

    Command Line Options

    The Chameleon executable accepts the following command line arguments.
    -c, --complete Attempt to complete non-confluent CHRs
    --h98-term Use Haskell 98 termination conditions
    --ck-simp-overlap Reject programs with overlapping simplification rules
    --no-rr Skip range-restrictedness check
    --no-conf Skip confluence check
    --no-term Skip termination (of CHRs) check
    --no-unambig Skip check for unambiguity of type schemes
    --no-checks Skip all of the above mentioned checks
    -s lvl, --solver=lvl Set initial solver mode for type checking - accepted values are 0,1,2. Higher values result in better error messages, at the cost of potentially slower type checking. The default is 0.
    -g, --jp-global Report type errors using global justifications
    -d, --debugger Invoke the type debugger (won't compile source file)
    -o file Output filename (for translated Chameleon code)
    --verbose Reports slightly more about what's going on
    -v, --version Displays the release version
    -h, --help Displays command line help


    The Chameleon Type Debugger

    We refer to the "The Chameleon Type Debugger (Tool Demonstration)" and the technical papers found here for more details about the Chameleon type debugger.


    Chameleon References

    More information about Chameleon and type inference with CHRs can be found at: