$30 off During Our Annual Pro Sale. View Details »

Purescript Typeable

Anupam
January 18, 2021

Purescript Typeable

A talk presented at the Purescript semi-monthly meetup on 18 Jan 2021.

The github repo for purescript-typeable is here - https://github.com/ajnsit/purescript-typeable/

Anupam

January 18, 2021
Tweet

More Decks by Anupam

Other Decks in Technology

Transcript

  1. ANUPAM JAIN
    Type generic programming
    PURESCRIPT - TYPEABLE

    View Slide

  2. data Company = Company [Dept]
    data Dept = Dept Name Manager [SubUnit]
    data SubUnit = EmpUnit Employee | DeptUnit Dept
    data Employee = Employee Person Salary
    data Person = Person Name Address
    data Salary = Salary Number
    type Manager = Employee
    type Name = String
    type Address = String
    MOTIVATION
    increase :: (Salary -> Salary) -> Company -> Company

    View Slide

  3. increase :: (Salary->Salary) -> Company -> Company
    increase k (Company ds) = Company (map (incD k) ds)
    incD :: (Salary->Salary) -> Dept -> Dept
    incD k (Dept nm mgr us) = Dept nm (incE k mgr) (map (incU k) us)
    incU :: (Salary->Salary) -> SubUnit -> SubUnit
    incU k (EmpUnit e) = EmpUnit (incE k e)
    incU k (DeptUnit d) = DeptUnit (incD k d)
    incE :: (Salary->Salary) -> Employee -> Employee
    incE k (Employee p s) = Employee p (incS k s)
    incS :: (Salary->Salary) -> Salary -> Salary
    incS = identity
    BOILERPLATE

    View Slide

  4. increase :: forall a. (Salary -> Salary) -> a -> a
    DESIRED SOLUTION

    View Slide

  5. class MapSalary a where
    increase ::(Salary -> Salary) -> a -> a
    instance mapSalary :: MapSalary Salary where
    increase f a = f a
    else
    instance mapSalary :: MapSalary a where
    increase _ a = a
    AD HOC OVERLOADING?
    Too much boilerplate. Need a general solution.

    View Slide

  6. increase :: forall a. (Salary -> Salary) -> a -> a
    increase f a
    | a `instanceOf` Salary = f a
    | otherwise = a
    DESIRED SOLUTION
    instanceOf :: forall a. a -> Type?? -> Boolean

    View Slide

  7. -Proxy a = Proxy
    -booleanType :: Proxy Boolean
    booleanType = Proxy
    -instanceOf :: forall a b. a -> Proxy b -> Boolean
    instanceOf a Proxy = ???
    PROXY DOESN’T SUFFICE

    View Slide

  8. -TypeRep a
    -typeRep :: forall a. TypeRep a
    -eqTypeRep :: forall a b. TypeRep a -> TypeRep b -> Boolean
    -instanceOf :: forall a. a -> TypeRep b -> Boolean
    instanceOf a t = eqTypeRep (typeRep :: _ a) t
    REIFY TYPES
    Predefined
    a == b => TypeRep a == TypeRep b

    View Slide

  9. data TypeRep a where
    TInt :: Type Int
    TChar :: Type Char
    TArray :: Type a -> Type (Array a)
    TFunc :: Type b -> Type c -> Type (Function b c)
    TTuple :: Type a -> Type b -> Type (Tuple a b)
    eqTypeRep :: forall a b. TypeRep a -> TypeRep b -> Boolean
    eqTypeRep TInt TInt = True

    MANUALLY - GADTS

    View Slide

  10. data TypeRep a
    = TInt (Int ~ a)
    | TChar (Char ~ a)
    | TArray (Type b) (a ~ Array b)
    | TFunc (Type b) (Type c) (a ~ Function b c)
    | TTuple (Type b) (Type c) (a ~ (Tuple b c))
    eqTypeRep :: forall a b. TypeRep a -> TypeRep b -> Boolean
    eqTypeRep (TInt w1) (TInt w2) = Just $ symm w1 >>> w2

    MANUALLY - WITHOUT GADTS
    Not extensible
    Explained later

    View Slide

  11. typeRep :: forall a. TypeRep a ??
    class Typeable a where
    typeRep :: TypeRep a
    instance Typeable Int where
    typeRep :: TInt
    else

    CREATING TYPEREPS

    View Slide

  12. increase :: forall a. (Salary -> Salary) -> a -> a
    increase f a
    | a `instanceOf` typeRep = f a
    | otherwise = a
    SOLUTION

    View Slide

  13. increase :: forall a. (Salary -> Salary) -> a -> a
    increase f a
    | a `instanceOf` typeRep = f a
    | otherwise = a
    SOLUTION
    Cannot match a with Salary

    View Slide

  14. increase :: forall a. (Salary -> Salary) -> a -> a
    increase f a
    | a `instanceOf` typeRep = f (coerceToSalary a)
    | otherwise = a
    coerceToSalary :: forall a. a -> Salary
    coerceToSalary = unsafeCoerce
    SOLUTION

    View Slide

  15. increase :: forall a. (Salary -> Salary) -> a -> a
    increase f a = case a `instanceOf` typeRep of
    Just coercion = f (coercion a)
    Nothing -> a
    instanceOf :: forall a. a -> TypeRep b -> Maybe (a -> b)
    instanceOf ta tb
    | eqTypeRep ta tb = Just (unsafeCoerce identity)
    | otherwise = Nothing
    ENCAPSULATE UNSAFE BEHAVIOUR (TYPE WITNESSES)

    View Slide

  16. increase :: forall a. (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 f -> f a
    EQUIVALENTLY
    Runtime cast

    View Slide

  17. 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
    LEIBNIZ EQUALITY

    View Slide

  18. eqT :: forall a b. TypeRep a -> TypeRep b -> Maybe (a ~ b)
    eqT ta tb
    | eqTypeRep ta tb = Just (unsafeCoerce (identity :: Leibniz a a))
    | otherwise = Nothing
    cast :: forall a b. Typeable a => Typeable b => a -> Maybe b
    cast a =
    eqT (typeRep :: _ a) (typeRep :: _ b) :: Maybe (Identity a -> Identity b)
    # map \f -> runLeibniz f (Identity a) :: Maybe (Identity b)
    # map unwrap :: Maybe b
    CAST USING LEIBNIZ

    View Slide

  19. class Typeable a => Data a where
    gmapT :: (forall b. Data b => b -> b) -> a -> a
    instance Data Employee where
    gmapT f (E per sal) = E (f per) (f sal)
    instance Data Bool where
    gmapT f x = x
    instance Data a => Data [a] where
    gmapT f [] = []
    gmapT f (x:xs) = f x : f xs
    WRAPPING IT UP

    View Slide

  20. everywhere :: Data a => (forall b. Data b => b -> b) -> a -> a
    everywhere f x = f (gmapT (everywhere f) x)
    solution :: (Salary->Salary) -> Company -> Company
    Solution k = everywhere (increase k)
    NO BOILERPLATE

    View Slide

  21. data Dynamic' t a = Dynamic' (TypeRep a) (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) (eqT ti ta)
    ANOTHER EXAMPLE - DYNAMIC VALUES

    View Slide

  22. IMPLEMENTATION DETAILS

    View Slide

  23. data Proxy0 (t :: Type)
    data Proxy1 (t :: Type -> Type)

    foreign import proxy0 :: forall t. Proxy0 t
    foreign import proxy1 :: forall t. Proxy1 t

    PROXY

    View Slide

  24. class Tag0 a where tag0 :: Proxy0 a
    class Tag1 (a :: Type -> Type) where tag1 :: Proxy1 a

    instance tagInt :: Tag0 Int where
    tag0 = proxy0
    instance tagArray :: Tag1 Array where
    tag1 = proxy1

    TAGS
    User Defined Instances

    View Slide

  25. instance tag1FromTag2 :: (Tag2 t, Typeable a) => Tag1 (t a) where
    tag1 = proxy1FromTag2
    instance tag0FromTag1 :: (Tag1 t, Typeable a) => Tag0 (t a) where
    tag0 = proxy0FromTag1

    foreign import proxy1FromTag2
    :: forall t a. Tag2 t => Typeable a => Proxy1 (t a)
    foreign import proxy0FromTag1
    :: forall t a. Tag1 t => Typeable a => Proxy0 (t a)

    TAG CHAINING
    A -> B -> [A,B]
    [A,B] -> C -> [A,B,C]

    View Slide

  26. instance Tag0 t => Typeable t where
    typeRep = typeRepDefault0
    foreign import typeRepDefault0
    :: forall a. Tag0 a => TypeRep a
    TYPEABLE
    A -> A

    View Slide

  27. data TypeRow (r :: RL.RowList)
    foreign import typeRowToTypeRep
    :: RL.RowToList r rl => TypeRow rl -> TypeRep (Record r)
    foreign import typeRowNil
    :: TypeRow RL.Nil
    foreign import typeRowCons
    :: SProxy s -> TypeRep t -> TypeRow rs -> TypeRow (RL.Cons s t rs)
    TYPEABLE FOR ROW TYPES
    A -> A
    []
    S -> T -> R -> (S,T):R

    View Slide

  28. instance(RL.RowToList rs ls, TypeableRecordFields ls)
    => Typeable (Record rs) where
    typeRep =
    typeRowToTypeRep (typeableRecordFields (RLProxy :: _ ls))
    TYPEABLE FOR ROW TYPES

    View Slide

  29. class TypeableRecordFields rowlist where
    typeableRecordFields :: RLProxy rowlist -> TypeRow rowlist
    instance TypeableRecordFields RL.Nil where
    typeableRecordFields _ = typeRowNil
    instance TypeableRecordFields (RL.Cons key focus rowlistTail) where
    typeableRecordFields _
    = typeRowCons key (reflectSymbol key) (typeRep :: _ focus) tail
    where
    key = SProxy :: _ key
    tail = typeableRecordFields (RLProxy :: _ rowlistTail)
    TYPEABLE FOR ROW TYPES

    View Slide