What is PureScript? ● Pure, Strict, Functional ● Higher Kinded 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!
What are Row types? ● An unordered collection of fields ○ 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
Collection? How about as a List? ● What if we 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))
Type classes: “type-level case statements” ● How do you match 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
Cons instance of class Keys 1. Pattern match on the 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)
Exposing our method in a reasonable way ● We wrote 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
Simple-JSON: Get JSON de/serialization for free for record type aliases and more! Sample Applications of RowLists (1 of n) class ReadForeignFields (xs :: RowList) (from :: # Type) (to :: # Type) | xs -> from to where getFields :: RLProxy xs -> Foreign -> F (Builder (Record from) (Record to))
OhYes: Constrain PureScript types to be directly interoperable with TypeScript, 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 };
ChocoPie/Cycle-Run: Libraries for creating a cycle of Events, such that 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)
HomeRunBall: Define validations to be performed on a value using 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"
“This seems like a lot of work for something I 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
● Static keys -- there are explicit guarantees of keys 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.
“Will I need to do this if I [use / 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
FAQ-ish ● “Surely, we already had this with row polymorphism 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
Thanks! Ask me Purescript questions on Twitter: @jusrin00 IRC: #purescript on freenode Slack: #purescript on Functional Programming Slack Reddit: /r/purescript