240 likes | 367 Views
Transposing F to C ♯. Andrew Kennedy & Don Syme Microsoft Research Cambridge, U.K. Transposing what?. As musical keys, F and C ♯ are far apart As programming languages, (System) F and (Generic) C ♯ are far apart But (message of talk):.
E N D
Transposing F to C♯ Andrew Kennedy & Don SymeMicrosoft Research Cambridge, U.K.
Transposing what? • As musical keys, F and C♯ are far apart • As programming languages, (System) F and (Generic) C♯ are far apart • But (message of talk): Polymorphism in Generic C♯is as expressive as polymorphism in System F
Polymorphic Programming Languages Standard ML Eiffel O’Caml C++ Ada C♯ (coming soon…) Mercury Clu GJ
Polymorphic features Impredicative polymorphism Parameterized classes Parameterized datatypes For-all types Polymorphic functions Higher-order polymorphism & kinds Polymorphic methods Type classes Bounds on type parameters Constraints on type parameters Variance
System F and C♯ • System F (a.k.a. polymorphic lambda calculus) is very different from C♯
System F into C♯ • Despite these differences, we can formalize a translation from System F into (Generic) C♯ that • is fully type-preserving (no loss of information) • is sound (preserves program behaviour) • (second message of talk) demonstrates that… polymorphic virtual methodsexpressfirst-class polymorphism
ML-style polymorphism into C♯ • Define a datatype: datatype ’a Tree = Leaf of ’a | Node of ’a Tree*’a Tree • Write a polymorphic function:fun reflect (t : ’a Tree) =case t of Leaf a => Leaf a| Node(l,r) => Node(reflect r,reflect l)
Compare: • Define parameterized classes: abstract class Tree<A> { ... }class Leaf<A> : Tree<A> { A value; ... }class Node<A> : Tree<A> { Tree<A> left; Tree<A> right; ... } • Add a method:abstract class Tree<A> { virtual Tree<A> reflect(); } class Leaf<A> : Tree<A> { ... override Tree<A> reflect() { return this; } } class Node<A> : Tree<A> { ... override Tree<A> reflect() { return new Node<A>(right.reflect(), left.reflect()); }}
In general • ML-style datatypes can be translated into parameterized classes • Polymorphic functions can be translated into methods inside a parameterized class e.g.class Mapper<A,B> { List<B> Map(Function<A,B> f, List<A> xs) { ...} List<int> li = ...;List<string> ls = new Mapper<int,string>().Map(myFun, li);
So: Core ML polymorphism can be encodedusing parameterized classes alone
Polymorphic virtual methods • Define an interface or abstract class:interface Sorter { void Sort<T>(T[] a, IComparer<T> c); } • Implement the interface:class QuickSort : Sorter { ... }class MergeSort : Sorter { ... } • Use instances at many type instantiations:void TestSorter(Sorter s, int[] ia, string[] sa) { s.Sort<int>(ia, IntComparer); s.Sort<string>(sa, StringComparer);}TestSorter(new QuickSort(), ...);TestSorter(new MergeSort(), ...);
Compare: • Define an SML signature:signature Sorter = sig val Sort : ‘a array * (‘a*’a->order) –> unit end • Define structures that match the signature:structure QuickSort :> Sorter = ... structure MergeSort :> Sorter = ... • Use structures at many type instantiations:functor TestSorter(S : Sorter) = struct fun test (ia, sa) = (S.Sort(ia, Int.compare); S.Sort(sa, String.compare) endstructure TestQuickSort = TestSorter(QuickSort); TestQuickSort.test(...);structure TestMergeSort = TestSorter(MergeSort); TestMergeSort.test(...);
Or (Russo first-class modules): • Define an SML signature:signature Sorter = sig val Sort : ‘a array * (‘a*’a->order) –> unit end • Define structures that match the signature:structure QuickSort :> Sorter = ... structure MergeSort :> Sorter = ... • Use a function to test the structures:fun TestSorter (s, ia, sa) = let structure S as Sorter = s in (S.Sort(ia, Int.compare); S.Sort(sa, String.compare)) endTestSorter ([structure QuickSort as Sorter], ...);TestSorter ([structure MergeSort as Sorter], ...);
Question: Can System F “first-class polymorphism” be encoded using polymorphic virtual methods ?
To answer the question... • Take System F + recursion + call-by-value evaluation order • Formalize a type-preserving translation into Generic C# • Prove that it works
In more detail • Source is System F + CBV recursion:(Types) A,B ::= X | A -> B | forall X.A(Terms) M,N ::= x | M N | rec y(x:A):B.M | M A | ΛX.V(Values) V ::= rec y(x:A):B.M | ΛX.V • Target is “C# minor” • a tiny, purely functional subset of Generic C# • very similar to Featherweight Java (Pierce, Igarashi, Wadler) • includes just enough for our translation plus a bit more (run-time types)
Translation: functions • Represent function types using a parameterized class:class Arrow<X,Y> { public virtual Y app(X x); } • Function application is just invocation of app method • Represent function values by instances of “closure classes” implementing Arrow e.g. λx:X->Y.x y translates to new C<X,Y>(y) withclass C<X,Y> : Arrow<Arrow<X,Y>,Y> { X y; public override Y app(Arrow<X,Y> x) { return x.app(this.y); }} • Note: • fields store free variables of function • class is parameterized over free type variables of function
Translation: recursion • Translate recursion into self-reference through this • For example, rec y(x:X):X. y(x) translates to new C<X>() withclass C<X> : Arrow<X,X> { public override X app(X x) { return this.app(x); }}
Translation: polymorphism • We can’t use a single class definition for “forall” types • Instead use different class for each such typee.g. forall X. X->Y isclass ForAll_X_XtoY<Y> { public virtual Arrow<X,Y> tyapp<X>();} • Type application is just invocation of tyapp method • Represent polymorphic values by instances of “closure classes” implementing appropriate ForAll class • close over free variables and free type variables just as with functions
Translation: polymorphism, cont. • Problem: translation of types doesn’t commute with substitution i.e.([A/X]B)* ≠ [A*/X]B* • Example: • forall X.X->Y translates to All_X_XtoY<Y> • Now substitute forall Z.Z for Y • We get forall X.(X->forall Z.Z) which translates to All_X_XtoAll_Z_Z • Solution (Läufer & Odersky): use a single class to represent a whole family of related polymorphic types
Properties of translation • Fully type-preserving:A* = B* iff A=B • Translation preserves types of terms: If M:A translates to e then e:A* • Translation preserves convergence behaviour of closed terms (i.e. the translation is “adequate”): If M:A translates to e then M converges iff e converges
Observations • Strictly speaking the translation is not compositional (global generation of names for forall types) • The translation is harnessing the power of polymorphic virtual methods • Generic C#, GJ, NextGen permit polymorphic methods to be virtual • Eiffel, C++ do not • Distinctiveness of polymorphic virtual methods shows up in (type-preserving) implementations • requires execution-time type application
Future work • Proof of semantic correctness (adequacy) of translation • use method of logical relations • Harper, Morrisett, Minamide use similar technique to prove correctness of typed closure conversion • But: we have impredicative polymorphism and recursion; this makes things tricky • Compositional, partial type-preserving translations • Fω • Dynamic types • Power of nominal vs structural equivalence of types