The type of family evaluation is not lazy, therefore If ctf will evaluate all c , t and f . (Actually, the order in which the family order is determined is not really defined right now.) Therefore, it is not surprising that you have finished an endless cycle - you always evaluate Simplify (op (Simplify x) (Simplify y)) , even when this Simplify (op xy) !
You can avoid this by splitting recursion and simplification into parts, for example:
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} module Simplify where import Data.Type.Bool import Data.Type.Equality type family Simplify1 x where Simplify1 (op () x) = x Simplify1 (op x ()) = x Simplify1 (op xy) = op (Simplify1 x) (Simplify1 y) Simplify1 x = x type family SimplifyFix xx' where SimplifyFix xx = x SimplifyFix xx' = SimplifyFix x' (Simplify1 x') type Simplify x = SimplifyFix x (Simplify1 x)
The idea is this:
Simplify1 takes one simplification step.SimplifyFix takes x and its one-step simplification x' , checks to see if they are equal, and if they do not take another simplification step (thus finding a fixed point Simplify1 ).Simplify just starts with the SimplifyFix chain with a call to Simplify1 .
Since matching patterns of a type family is lazy, SimplifyFix correctly delays the evaluation, preventing infinite loops.
And really:
*Simplify> :kind! Simplify (String, Int) Simplify (String, Int) :: * = (String, Int) *Simplify> :kind! Simplify (String, ((), (Int, ()))) Simplify (String, ((), (Int, ()))) :: * = ([Char], Int)
Antal spector-zabusky
source share