Upgrade to Pro — share decks privately, control downloads, hide ads and more …

RowList Fun With PureScript 2nd Edition

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.
Avatar for Justin Woo Justin Woo
November 24, 2017

RowList Fun With PureScript 2nd Edition

Modified version of the RowList talk I gave at Zendesk

More information about RowList and other row type operations in PureScript here: https://github.com/justinwoo/awesome-rowlist

Avatar for Justin Woo

Justin Woo

November 24, 2017
Tweet

More Decks by Justin Woo

Other Decks in Programming

Transcript

  1. 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!
  2. 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
  3. 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))
  4. 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
  5. 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)
  6. 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
  7. 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))
  8. 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 };
  9. 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)
  10. 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"
  11. “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
  12. • 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.
  13. “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
  14. 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
  15. But PureScript is here today, so you don’t have to

    wait for tool X to develop features for working with row types!
  16. Thanks! Ask me Purescript questions on Twitter: @jusrin00 IRC: #purescript

    on freenode Slack: #purescript on Functional Programming Slack Reddit: /r/purescript