Dept Name Manager (Array SubUnit) data SubUnit = EmpUnit Employee | DeptUnit Dept data Employee = Employee Person Salary data Person = Person Name Address data Salary = Salary Number Give everyone a raise increase �� (Salary �� Salary) �� Company �� Company 2 / 90
ds) incD k (Dept nm mgr us) = Dept nm (incE k mgr) (map (incU k) us) incU k (EmpUnit e) = EmpUnit (incE k e) incU k (DeptUnit d) = DeptUnit (incD k d) incE k (Employee p s) = Employee p (incS k s) incS k (Salary s) = Salary (k s) 3 / 90
ds) incD k (Dept nm mgr us) = Dept nm (incE k mgr) (map (incU k) us) incU k (EmpUnit e) = EmpUnit (incE k e) incU k (DeptUnit d) = DeptUnit (incD k d) incE k (Employee p s) = Employee p (incS k s) Six functions of plumbing… 4 / 90
�� Salary) �� a �� a increase f a | a `instanceOf` Salary = f a | otherwise �� `Salary` is a�� Type? = a instanceOf: forall a. a �� Type�� �� Boolean 8 / 90
(an abstraction) more real, concrete, or tangible. foreign data TypeRep typeRep �� forall a. Proxy a �� TypeRep instanceOf �� forall a. a �� TypeRep �� Boolean instanceOf a t = eqTypeRep (typeRep (Proxy �� _ a)) t 10 / 90
TypeRep | TFunc TypeRep TypeRep eqTypeRep �� TypeRep �� TypeRep �� Boolean eqTypeRep TInt TInt = true eqTypeRep (TArray a) (TArray b) = eqTypeRep a b ��� Now there is something to compare at runtime. 12 / 90
Salary) �� a �� a increase f a | a `instanceOf` (typeRep (Proxy �� _ Salary)) = f (coerceToSalary a) | otherwise = a coerceToSalary �� forall a. a → Salary coerceToSalary = unsafeCoerce 16 / 90
(Salary �� Salary) �� a �� a increase f a | a `instanceOf` (typeRep (Proxy �� _ Salary)) = f (coerceToSalary a) | otherwise = a coerceToSalary �� forall a. a → Salary coerceToSalary = TRUST ME BRO I WAS THERE We checked the TypeRep at runtime. We know a is a Salary . How do we bear witness to the compiler? 17 / 90
evidence away. Return the proof instead. The most useful proof that a is a Salary is a function a �� Salary . increase �� forall a. Typeable a �� (Salary �� Salary) �� a �� a increase f a = case a `instanceOf` (typeRep (Proxy �� _ Salary)) of Just coerce �� f (coerce a) Nothing �� a 18 / 90
(a �� ���) If we don't constrain the return type then instanceOf �� forall a b. a �� TypeRep �� Maybe (a �� b) let Just coerce = "hello" `instanceOf` (typeRep (Proxy �� _ String)) coerce "hello" �� Int �� proof about String, used to make an Int! b is unconstrained, so the caller picks it. Nothing can implement this. 19 / 90
Proxy a �� TypeRep a instanceOf �� forall a b. a �� TypeRep b �� Maybe (a ~ b) instanceOf a t = eqTypeRep (typeRep (Proxy �� _ a)) t TypeRep a can only represent the typerep for the type it reifies. a ~ b is a value witnessing that a and b are the same type coerce �� forall a b. (a ~ b) �� a �� b �� all we need for now Haskell calls it a :~: b . We will build it in the next section. 20 / 90
�� forall a. TypeRep a �� SomeTypeRep �� PureScript type SomeTypeRep = Exists TypeRep �� Conceptually stored as, but really unsafeCoerced for efficiency type Exists f = forall r. (forall a. f a �� r) �� r mkExists �� forall f a. f a �� Exists f runExists �� forall f r. (forall a. f a �� r) �� Exists f �� r The type variable a is now Existential 21 / 90
�� TypeRep Int TChar �� TypeRep Char TArray �� TypeRep a �� TypeRep (Array a) TFunc �� TypeRep b �� TypeRep c �� TypeRep (Function b c) TTuple �� TypeRep a �� TypeRep b �� TypeRep (Tuple a b) eqTypeRep �� forall a b. TypeRep a �� TypeRep b �� Maybe (a :~: b) eqTypeRep TInt TInt = ��� ��� 22 / 90
an explicit field data TypeRep a = TInt (Int ~ a) | TChar (Char ~ a) | TArray (TypeRep b) (a ~ Array b) | TFunc (TypeRep b) (TypeRep c) (a ~ Function b c) | TTuple (TypeRep b) (TypeRep c) (a ~ Tuple b c) eqTypeRep �� forall a b. TypeRep a �� TypeRep b �� Maybe (a ~ b) eqTypeRep (TInt ia) (TInt ib) = ��� ��� The compiler no longer infers the equality. You apply it. ~ again! Simplified: b and c are existential; the real constructors wrap them in Exists . 23 / 90
a �� Int f x = x + 1 Part of the type system, not a library. data a :~: b where Refl �� a :~: a coerce �� (a :~: b) �� a �� b coerce Refl x = x �� inside this branch, `a` and `b` are the same type 24 / 90
value, so it fits in a field. No GADT needed data TypeRep a = TInt (Int :~: a) | TChar (Char :~: a) | forall b. TArray (TypeRep b) (Array b :~: a) | forall b c. TFunc (TypeRep b) (TypeRep c) (Function b c :~: a) | forall b c. TTuple (TypeRep b) (TypeRep c) (Tuple b c :~: a) useAsInt �� TypeRep a �� a �� Int useAsInt (TInt proof) n = coerce (symm proof) n + 1 25 / 90
Same (a �� b) (b �� a) instance Category Same where identity �� forall a. Same a a identity = Same identity identity (���) �� forall a b c. Same a b �� Same b c �� Same a c (���) (Same ab ba) (Same bc cb) = Same (ab ��� bc) (cb ��� ba) 26 / 90
a a refl = identity symm �� forall a b. Same a b �� Same b a symm (Same a b) = Same b a trans �� forall a b c. Same a b �� Same b c �� Same a c trans = (���) 27 / 90
a class. The type selects the instance, the method delivers a value class Typeable a where typeRep �� TypeRep a instance Typeable Int where typeRep = TInt identity instance Typeable a �� Typeable (Array a) where typeRep = TArray typeRep identity Each constructor carries identity , which is reflexivity. Haskell: since GHC 7.10 the solver supplies Typeable for every type; you cannot write one. PureScript: you write them all. 30 / 90
�� (Salary �� Salary) �� a �� a increase f a = case cast a of Just n �� f n Nothing �� a cast �� forall a b. Typeable a �� Typeable b �� a �� Maybe b cast a = case a `instanceOf` (typeRep �� _ b) of Nothing �� Nothing Just (Same ab _) �� Just (ab a) 32 / 90
any context newtype Leibniz a b = Leibniz (forall f. f a �� f b) infix 4 type Leibniz as ~ runLeibniz �� forall f a b. (a ~ b) �� f a �� f b runLeibniz (Leibniz f) = f 33 / 90
forall a. Leibniz a a identity = Leibniz identity (���) �� forall a b c. Leibniz a b �� Leibniz b c �� Leibniz a c Leibniz ab ��� Leibniz bc = Leibniz (ab ��� bc) Why bother, when Same already worked? coerceArray �� forall a b. Same a b �� Array a �� Array b coerceArray (Same ab _) = map ab �� O(n), allocates coerceArray �� forall a b. (a ~ b) �� Array a �� Array b coerceArray w = runLeibniz w �� free: pick f = Array 34 / 90
a refl = Leibniz identity symm �� forall a b. Leibniz a b �� Leibniz b a symm w = unFlip (runLeibniz w (Flip identity)) �� f = Flip a trans �� forall a b c. Leibniz a b �� Leibniz b c �� Leibniz a c trans (Leibniz ab) (Leibniz bc) = Leibniz (ab ��� bc) newtype Flip a b = Flip (Leibniz b a) 35 / 90
Same sameLeib �� forall a b. Leibniz a b �� Same a b sameLeib (Leibniz f) = Same ab ba where ab �� a �� b ab a = un Identity (f (wrap a)) �� f = Identity g �� forall h. h b �� h a �� symmetry, via Symm a g = runLeibniz (un Symm (f (Symm refl))) ba �� b �� a ba b = un Identity (g (wrap b)) newtype Symm a b = Symm (Leibniz b a) Isomorphic. But Leibniz composes for free. 36 / 90
�� Typeable b �� a �� Maybe b cast a = eqTypeRep (typeRep �� _ a) (typeRep �� _ b) �� �� Maybe (Leibniz a b) # map \w �� runLeibniz w (Identity a) �� �� Maybe (Identity b) # map unwrap �� �� Maybe b 37 / 90
(map (incD k) ds) incD k (Dept nm mgr us) = Dept nm (incE k mgr) (map (incU k) us) incU k (EmpUnit e) = EmpUnit (incE k e) incU k (DeptUnit d) = DeptUnit (incD k d) incE k (Employee p s) = Employee p (incS k s) incS k (Salary s) = Salary (k s) We have Typeable . We still have six functions. 38 / 90
a �� (Salary �� Salary) �� a �� a increase f a = case cast a of Just s �� f s Nothing �� a increase on a Company asks one question. Is this a Salary ? No. Done. The Dept s, Employee s and Salary s inside are never looked at. 39 / 90
any data we know how to recurse into. Call that Recursable . increase �� forall anything. Recursable anything �� (Salary �� Salary) �� anything �� anything Now that we have TypeRep , we can write it. 40 / 90
�� (Salary �� Salary) �� anything �� anything And the function that does the recursing is everywhere everywhere �� forall a. Data a �� (forall b. Data b �� b �� b) �� a �� a 41 / 90
Data a where gmapT �� (forall b. Data b �� b �� b) �� a �� a instance Data Employee where gmapT f (Employee per sal) = Employee (f per) (f sal) instance Data Boolean where gmapT f x = x instance Data a �� Data (Array a) where gmapT f xs = map f xs 42 / 90
Data a where gmapT �� (forall b. Data b �� b �� b) �� a �� a Employee holds a Person and a Salary gmapT f (Employee per sal) = Employee (f per) (f sal) �� ^^^^^ ^^^^^ �� f �� Person �� Person �� f �� Salary �� Salary A rank-1 (b �� b) fixes one b at the call site. f has to stay polymorphic inside gmapT . 43 / 90
a = case cast a of Just s �� f s Nothing �� a Abstract the function out and it works for any type. Call it mkT mkT �� forall a b. Typeable a �� Typeable b �� (b �� b) �� a �� a mkT f = case cast f of Just g �� g Nothing �� identity raiseSalary �� (Salary �� Salary) �� (forall b. Data b �� b �� b) raiseSalary k = mkT k Data only because that's the shape everywhere wants. It works because Data is a subclass of Typeable class Typeable a �� Data a 44 / 90
Typeable a �� Data a where gmapT �� (forall b. Data b �� b �� b) �� a �� a �� ^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^ �� b: so f can recurse a: so we have gmapT at all a is the class parameter, so gmapT only exists for types we can take apart. b is the child, and everywhere hands gmapT a function that recurses everywhere f x = gmapT (everywhere f) (f x) �� ^^^^^^^^^^^^^^ needs `Data` on whatever it touches Without Data b , f could change a child but never descend into it. 45 / 90
(forall b. Data b �� b �� b) �� a �� a everywhere f x = gmapT (everywhere f) (f x) everywhere f (Employee per sal) = gmapT (everywhere f) (f (Employee per sal)) | | | +�� apply f to the node itself +�� then recurse into each child = Employee (everywhere f per) (everywhere f sal) 46 / 90
b. Data b �� b �� b) �� a �� a everywhere f x = gmapT (everywhere f) (f x) increase �� forall anything. Data anything �� (Salary �� Salary) �� anything �� anything increase k = everywhere (raiseSalary k) One line, replacing the six we started with, for any type. 47 / 90
where gfoldl �� forall c . (forall d b. Data d �� c (d �� b) �� d �� c b) �� apply to a child �� (forall g. g �� c g) �� lift a value �� a �� c a instance Data a �� Data (Maybe a) where gfoldl k z Nothing = z Nothing gfoldl k z (Just a) = z Just `k` a 48 / 90
is a rank-2 type carrying a constraint class Typeable a �� Data a where gfoldl �� (forall d b. Data d �� c (d �� b) �� d �� c b) �� ��� �� ^^^^^^^^ a constraint inside a higher-rank argument PureScript won't accept that. Move the method into a newtype newtype DataDict a = DataDict ( forall c . (forall d b. Data d �� c (d �� b) �� d �� c b) �� (forall g. g �� c g) �� a �� c a ) class Typeable a �� Data a where dataDict �� DataDict a instance Data (Maybe a) where dataDict = DataDict \k z m �� case m of Nothing �� z Nothing Just a �� z Just `k` a 49 / 90
(t a) data Dynamic t = Dynamic (Exists (Dynamic' t)) dynamic �� forall t a. Typeable a �� t a �� Dynamic t dynamic a = Dynamic (mkExists (Dynamic' typeRep a)) unwrapDynamic �� forall t a. TypeRep a �� Dynamic t �� Maybe (t a) unwrapDynamic ta (Dynamic e) = e # runExists \(Dynamic' ti v) �� map (\w �� runLeibniz w v) (eqTypeRep ti ta) Pack a value with its type, recover it only for the matching type. Dynamic typing, safely, inside a static language. 51 / 90
Expr �� Either Err a deserialiseArray �� forall a. TypeRep a �� Expr �� Either Error (Array a) deserialiseArray aTyp (Expr "Array" (Expr typstr [] : rest)) | aTyp �� deserialiseTypeRep typstr = traverse (deserialise aTyp) rest | otherwise = Left $ "Expected type: " �� show aTyp �� ", found " �� show typstr The wire format is checked against the expected type before a single element is decoded. 55 / 90
| TChar ��� 1. Unsafe. Nothing stops you writing instance Typeable Foo where typeRep = TInt ��� 2. Closed sum. For our example we'd need to add | TEmployee (TypeRep Person) (TypeRep Salary) ��� …to the library, for every user type. 57 / 90
from the library. instance Tagged Employee where tag = makeTag unit instance Tagged Person where tag = makeTag unit instance Tagged Salary where tag = makeTag unit All of these are compile errors instance Tagged (Employee p s) where ��� instance Tagged Employee where tag = somethingElse instance Typeable Employee where ��� • Instances only for tags, so they are unique • No way to provide an invalid tag • No user-defined Typeable instances 58 / 90
�� TypeRep a What kind is a ? • Int has kind Type • Array has kind Type �� Type • Either has kind Type �� Type �� Type A monokinded class covers only one of these. 60 / 90
Type data TypeRep a class Typeable �� forall k. k �� Constraint class Typeable a where typeRep �� TypeRep a One class, every arity: Typeable Int , Typeable Array , Typeable Either . PolyKinds in both. Haskell since GHC 7.4, PureScript since 0.14. 61 / 90
@Int) �� 0x8bfd0e2b3f2c9f4e��� GHC assigns every type constructor a fingerprint derived from package:Module.Name . Stable across runs, unforgeable, generated for free. You cannot write a wrong Typeable instance because you cannot write one. 62 / 90
import data Tag �� forall k. k �� Type foreign import makeTag �� forall t. Unit �� Tag t function Tag() { } �� compared with ��� export function makeTag(_) { return new Tag(); �� fresh, unequal to all others } The Unit argument is not decoration. It forces a call foreign import makeTag �� forall t. Tag t �� ✗ one shared value foreign import makeTag �� forall t. Unit �� Tag t �� ✓ called per instance Without it every instance shares the one Tag the module made at load time, so every type compares equal to every other. 63 / 90
�� Constraint class Tagged a where tag �� Tag a instance Tagged Int where tag = makeTag unit instance Tagged Array where tag = makeTag unit ��� 64 / 90
yields another tag instance (Tagged t, Typeable a) �� Tagged (t a) where tag = tagFromTag foreign import tagFromTag �� forall t a. Tagged t �� Typeable a �� Tag (t a) It looks argument-less, but the two constraints are the arguments, passed as dictionaries tagFromTag (tag �� Tag t) (typeRep �� TypeRep a) �� Tag (t a) �� a tag for `t a` pairs the two; eqTypeRep compares pairs structurally export const tagFromTag = (tagT) �� (repA) �� [tagT, repA]; 65 / 90
(A … B) Tagged Array ⇒ Tag Array Tagged Array + Typeable Int ⇒ Tag (Array Int) Tagged Either ⇒ Tag Either Tagged Either + Typeable String ⇒ Tag (Either String) ⇒ Tag (Either String Int) + Typeable Int (user-written) (user-written) 66 / 90
Some a | None instance Tagged Optional where tag = makeTag unit One mechanical line, any arity. Typeable (Optional Int) and Typeable Optional both come free. 67 / 90
a) where typeRep = typeRepFromTag1 else instance Tagged t �� Typeable t where typeRep = typeRepDefault0 foreign import typeRepDefault0 �� forall a. Tagged a �� TypeRep a Tag A → Typeable A Tag (A …) → Typeable B → Typeable (A … B) 68 / 90
unit �� instance Foo �� Tagged Person where tag = makeTag unit �� silently broken A constrained instance is re-evaluated per call site, minting a different tag each time. Two TypeRep Person values then compare unequal. The type checker cannot catch this. 69 / 90
�� Haskell: a record IS a data type, and gets a fingerprint data Person = Person { name �� String } In Haskell there's a constructor, so it gets a fingerprint like anything else. 70 / 90
A convert a finished row foreign import typeRowToTypeRep �� RL.RowToList r rl �� TypeRow rl �� TypeRep (Record r) �� [] the empty row foreign import typeRowNil �� TypeRow RL.Nil �� S �� T �� R �� (S,T):R cons one field on foreign import typeRowCons �� Proxy s �� String �� TypeRep t �� TypeRow rs �� TypeRow (RL.Cons s t rs) 72 / 90
�� Typeable (Record rs) where typeRep = typeRowToTypeRep (typeableRecordFields (RLProxy �� _ ls)) It slots into the same instance chain, ahead of the tag-based cases else instance (Tagged t, Typeable a) �� Typeable (t a) where ��� else instance Tagged t �� Typeable t where ��� 75 / 90
be built with them. GADTs, type equality, TypeRep , existentials and generic traversals are all built into Haskell. PureScript has none of them. We built them all as patterns or libraries. And for everything else, there's the FFI 77 / 90