Implementing Type Classes using Type-Indexed Functions Enrique Martín Martín emartinm@fdi.ucm.es Dpto. Sistemas Informáticos y Computación Universidad Complutense de Madrid II Taller de Programación Funcional Valencia - 7 de Septiembre de 2010 Type classes and FLP Type classes provide a clean and modular way of writing overloaded functions. Type classes are usually implemented using dictionaries. However, dictionaries have some problems in FLP: Bad interaction with non-determinism and call-time choice. Relatively complex translated programs: length, readability... Example using type classes class arb A where arb :: A class arb A => foo A where foo :: bool -> A instance arb bool where arb = true arb = false instance foo bool where foo true = true foo false = arb f :: foo A => (A, A) f = (arb, foo true) Translation with dictionaries (I): classes data dictArb A = dArb A class arb A where arb :: A arb :: dictArb A -> A arb (dArb Farb) = Farb data dictFoo A = dFoo (dictArb A) (bool -> A) class arb A => foo A where foo :: bool -> A foo :: dictFoo A -> A foo (dFoo Darb Ffoo) = Ffoo getArbFromFoo :: dictFoo A -> dictArb A getArbFromFoo (dFoo Darb Ffoo) = Darb Translation with dictionaries (II): instances instance arb bool where arb = true arb = false instance foo bool where foo true = true foo false = arb arbBool :: bool arbBool = true arbBool = false dictArbBool :: dictArb bool dictArbBool = dArb arbBool fooBool :: bool -> bool fooBool true = true arbBool fooBool false = arb dictArbBool dictFooBool :: dictFoo bool dictFooBool = dFoo dictArbBool fooBool Translation with dictionaries (III): functions f :: foo A => (A, A) f = (arb, foo true) f :: dictFoo A -> (A, A) f Dfoo = (arb (getArbFromFoo Dfoo), foo Dfoo true) Drawbacks of dictionaries (I) Missing answers in FLP with non-determinism: [Type-classes and call-time vs. run-time, Wolfgang Lux, Curry mailing list] arbL2 :: arb A => [A] arbL2 = [arb, arb] arbL2 :: dictArb A -> [A] arbL2 Darb = [arb Darb, arb Darb] Drawbacks of dictionaries (I) > arbL2::[bool] arbL2 dictArbBool → [arb dictArbBool, arb dictArbBool] →* [arb (dArb true), arb (dArb true)] →* [true, true] > arbL2::[bool] arbL2 dictArbBool → [arb dictArbBool, arb dictArbBool] →* [arb (dArb false), arb (dArb false)] →* [false, false] Missing answers: [true, false], [false, true] Drawbacks of dictionaries (II) Long and complex resulting program. Efficiency sink: use of projecting functions arb (dArb Farb) = Farb foo (dFoo Darb Ffoo) = Ffoo getArbFromFoo (dFoo Darb Ffoo) = Darb However efficiency can be optimized [Implementing Haskell overloading, Lennart Augustsson]. Main idea: new translation Translation of type classes in FLP using typeindexed functions (TIF): functions with a different behaviour for each different type. The translation is well-typed in a new liberal type system for FLP. Intuition: Each overloaded function (function in a type class) define a TIF. Each instance adds new rules to TIFs. Outline Type system. Translation using TIFs and type witnesses. Example. Advantages of the translation. Conclusions. Future work. Type system New type system for FLP [APLAS'10]. Type declarations are mandatory. Type derivation/inference for expressions is similar to Hindley-Milner. Well-typedness of a program proceeds rule by rule. Posibilities for generic programming: generic functions, type-indexed functions. Type system A program rule is well-typed if the righthand side fixes the types of the arguments and the result less than the left-hand side. It guarantees type preservation. Type system: example size size size size size eq eq eq eq eq :: A -> nat false = s z true = s z z = s z (s X) = s (size X) :: A -> A -> bool true true = true false false = true z z = true (s X) (s Y) = eq X Y Type system: example size :: A -> nat size false = s z size true = s z size z = s z z :: nat X) size ::(s size false natX) = s s(size eq eq eq eq eq :: A -> A -> bool true true = true false false = true z z = true (s X) (s Y) = eq X Y Type system: example size :: A -> nat size false = s z size true = s z size z = s z size (s X) = s (size X) s z :: nat size true :: nat eq :: A -> A -> bool eq true true = true eq false false = true eq z z = true eq (s X) (s Y) = eq X Y Type system: example size size size size size :: A -> nat false = s z true = s z z = s z (s X) = s (size X) eq :: A -> A -> bool eq true true =X :: true X :: nat A size (s :: nat falses (size eqX)false = true X) :: nat eq z z = true eq (s X) (s Y) = eq X Y Type system: example size size size size size eq eq eq eq eq :: A -> nat false = s z true = s z z = s z (s X) = s (size X) :: A -> A -> bool true true = true false false = true z z = true (s X) (s Y) = eq X Y Type system: example size size size size size :: A -> nat false = s z true = s z z = s z (s X) = s (size X) X :: nat X :: A bool Y :: nat eq :: A -> A ->Y :: A true eq (s X) (seq Y) true :: booltrue = eq X Y :: bool eq false false = true eq z z = true eq (s X) (s Y) = eq X Y Type system: example size size size size size eq eq eq eq eq :: A -> nat false = s z true = s z z = s z (s X) = s (size X) :: A -> A -> bool true true = true false false = true z z = true (s X) (s Y) = eq X Y Type system: ill-typed example snd :: A -> B -> B unpack :: (A -> A) -> B unpack (snd X) = X f :: bool -> A f true = z f false = true Type system: ill-typed example snd :: A -> B -> B unpack :: (A -> A) -> B unpack (snd X) = X f :: bool -> A X :: A f true = z unpack (snd X)= ::true B f false X :: C Type system: ill-typed example snd :: A -> B -> B unpack :: (A -> A) -> B unpack (snd X) = X f :: bool -> A f true = z f false = true f true :: A z :: nat Type system: ill-typed example snd :: A -> B -> B unpack :: (A -> A) -> B unpack (snd X) = X f :: bool -> A f true = z f false = true f false :: A true :: bool Type system: ill-typed example snd :: A -> B -> B unpack :: (A -> A) -> B unpack (snd X) = X f :: bool -> A f true = z f false = true Translation of type classes using TIFs Replace each overloaded function by a TIF. Each rule of an overloaded function in an instance is a rule of the corresponding TIF. The TIF is implemented using type witnesses to determine which rules to apply. Translation (I): Type witnesses A way of representing types as values. Extends the data type with a new constructor. Examples: data nat = z | s nat | #nat data bool = true | false | #bool data [A] = nil | cons A (list A) | #list A [bool] → #list #bool :: [bool] [[nat]] → #list (#list #nat) :: [[nat]] Translation (II): classes and instances class arb A where arb :: A class arb A => foo A where foo :: bool -> A arb :: A -> A foo :: A -> bool -> A instance arb bool where arb = true arb = false arb #bool = true arb #bool = false instance foo bool where foo true = true foo false = arb foo #bool true = true foo #bool false = arb #bool Could be optimized Translation (III): functions f :: foo A => (A, A) f = (arb, foo true) f :: A -> (A, A) f WA = (arb WA, foo WA true) Advantages (I): no missing answers No missing answers arbL2 :: arb A => [A] arbL2 = [arb, arb] arbL2 :: A -> [A] arbL2 WA = [arb WA, arb WA] Advantages (I): no missing answers > arbL2::[bool] arbL2 #bool → [arb #bool, arb #bool] → [true, arb #bool] → [true, true] > arbL2::[bool] arbL2 #bool → [arb #bool, arb #bool] → [false, arb #bool] → [false, true] > arbL2::[bool] arbL2 #bool → [arb #bool, arb #bool] → [true, arb #bool] → [true, false] Advantages (II): efficiency Advantages (II): efficiency Test program: traversing a list applying overloaded functions inside a big class hierarchy. Explanation of the results: Big class hierarchy → big dictionaries. (a) Big dictionaries must be passed. (b) Functions must be projected from big dictionaries. Advantages (III): simplicity data dictArb A = dArb A data bool = true | false | #bool arb :: dictArb A -> A arb (dArb Farb) = Farb data dictFoo A = dFoo (dictArb A) (bool -> A) arb :: A -> A foo :: A -> bool -> A foo :: dictFoo A -> A foo (dFoo Darb Ffoo) = Ffoo getArbFromFoo :: dictFoo A -> dictArb A getArbFromFoo (dFoo Darb Ffoo) = Darb arbBool :: bool arbBool = true arbBool = false dictArbBool :: dictArb bool dictArbBool = dArb arbBool fooBool :: bool -> bool fooBool true = true fooBool false = arb dictArbBool dictFooBool :: dictFoo bool dictFooBool = dFoo dictFooBool fooBool f :: dictFoo A -> (A, A) f Dfoo = (arb (getArbFromFoo Dfoo), foo Dfoo true) vs. arb #bool = true arb #bool = false foo #bool true = true foo #bool false = arb #bool f :: A -> (A, A) f WA = (arb WA, foo WA true) Conclusions New and simple translation of type classes for FLP relying on a new liberal type system. Solve some problems of missing answers. Perform faster than dictionaries without optimizations (at least in some cases). Future work Formalize the translation. Implement and integrate into TOY. Study optimizations of the translation. Study how the translation combines with modules and separate compilation (open functions). Thanks!