Types • Type classes with functional dependencies • Tooling with purescript-ide server + client • Docs for packages on Pursuit • Easy-to-use build tool in Pulp • Typed holes with type-directed search • No runtime -- compiles to real JS • Row types • ...and more!
◦ Key is Symbol (a type-level string kind) ◦ Value is of the kind provided to the row data Record :: # Type -> Type -- takes row of Type to define a Type type Person = { name :: String, age :: Number } type Person = Record (name :: String, age :: Number) • Eff works this way! kind Effect data HTTP :: Effect data Eff :: # Effect -> Type -> Type main :: Eff ( http :: HTTP ) Unit
could use these rows as lists of fields? Wouldn’t that be fun? kind RowList data Nil :: RowList data Cons :: Symbol -> Type -> RowList -> RowList type PersonRow = (name :: String, age :: Number) type PersonRowList = (Cons "name" String (Cons "age" Number Nil))
and work with things on the type level? Type classes! ◦ Using instances for each branch • Simple example: getting keys from a record class Keys (xs :: RowList) where keysImpl :: RLProxy xs -> List String -- forall g. g xs -> List String instance nilKeys :: Keys Nil where keysImpl _ = mempty -- Phantom types reminder data Maybe a = Nothing | Just a data Proxy a = Proxy data RLProxy (xs :: RowList) = RLProxy
RowList 2. Reflect the Symbol to a string value 3. Get the rest by calling keysImpl on the tail 4. Cons this string onto the rest of the keys That’s it! instance consKeys :: ( IsSymbol name , Keys tail ) => Keys (Cons name ty tail) where keysImpl _ = first : rest where first = reflectSymbol (SProxy :: SProxy name) rest = keysImpl (RLProxy :: RLProxy tail)
a class for RowList, but this needs to work for row types from Records • RowToList allows us to convert row to RowList for exactly this! keys :: forall row rl. RowToList row rl => Keys rl => Record row -> List String keys _ = keysImpl (RLProxy :: RLProxy rl) • Since we have everything from the type level, the record ends up being a glorified Proxy
with support for union types with a “type” string literal field using Variant (Same with Kancho, for Elm ports) Sample Applications of RowLists (2 of n) type VariantTest = Variant ( a :: String , b :: Number , c :: Boolean ) export type VariantTest = | { type: "a", value: string } | { type: "b", value: number } | { type: "c", value: boolean };
sources -> sinks, sink -> IO source for each pair Sample Applications of RowLists (3 of n) instance cycleRunRowListCons :: ( CycleRunRowList sourceTail sinkTail driverTail ) => CycleRunRowList (Cons k b sourceTail) (Cons k (Stream a) sinkTail) (Cons k ((Stream a) -> Eff e b) driverTail)
row types and have them run. Then use the phantom types to constrain code. Sample Applications of RowLists (4 of n) onlyOnApples :: ValidatedValue (beginsApple :: BeginsWith "Apple") String -> String onlyOnApples _ = "U R COOL"
do with objects/hashes/etc” Records are not hashes/objects and vice versa • Records are mostly heterogenous, objects/hashes are homogenous ◦ “I handle multiple types” -- three options there ▪ Dynamic/Foreign/Object -- a.k.a. Implicit coercion hell ▪ Polymorphic variants -- I use this sometimes too, but… why? ▪ Sum types -- Pretty useful, but still not ideal ◦ If you use any of the above, you now have N * M possible values instead of N (N fields, each field can be one of M) ◦ Common mistake: mixing up multiple values of a type with multiple types of a kind ▪ Can’t naively coerce most records into string maps, though that can be very useful! ▪ But you will never convert a string map to a record without parsing or unsafe coercion
existing in a record ◦ Lookups go to Maybe/Option/Nullable ◦ “I know it exists” -- three options here ▪ Unsafe coercion from Maybe a to a • Don’t do this because you will just crash prod forever ▪ Coercion from phantom type parameter evidence • newtype MyMap (alwaysDefined :: # Cond) = MyMap (StrMap a) getApple :: forall a cond trash alwaysDefined . RowCons “apple” cond trash alwaysDefined => MyMap alwaysDefined a -> a ▪ Refinement types • Hope you like Liquid Haskell Cont.
start using] Purescript?” • Writing type-level code using RowList? ◦ Not really, but sometimes ◦ And you will learn these from usage • Defining your own type classes and working with them? ◦ Not necessarily, but sometimes ◦ And you will learn these from usage • Understanding how to use constraints and throw them around? ◦ Yes and no ◦ You will learn these from usage ◦ You can always start with the most concrete types and use more general ones over time ▪ e.g. Monoid mempty vs [] • Even if you don’t use these directly, libraries will be able to provide you a whole lot by having these available
in other languages?” ◦ Sadly, no, seems not. ◦ What other language has the combination of ▪ Generic row polymorphism i.e. not only Record (row :: # Type) ▪ Higher kinded types ▪ Ad-hoc polymorphism with extra features e.g. MultiParam Type Classes with Functional Dependencies ◦ Nothing seems to come close, other than some Haskell libraries ▪ Unless you want to get into codegen territory, which already throws away composability • “So what if I don’t have this, what can I do?” ◦ Haskellers might very well choose to implement a subset of features available through two methods ▪ Heterogenous type-level lists with a linear search constraint (via type families or whatever) • Could be combined with GHC Generics/Generics-Rep for effect? ▪ Generics-Sums of Products + Records-SOP packages ◦ Codegen like hell