Type classes: pattern matching for types

3b48c91bf6b6f0bfd0fda50625598656?s=47 Justin Woo
October 18, 2018

Type classes: pattern matching for types

Talk given at PureScript Helsinki meetup held in Tampere

3b48c91bf6b6f0bfd0fda50625598656?s=128

Justin Woo

October 18, 2018
Tweet

Transcript

  1. Type classes: pattern matching for types Justin Woo October 18

    2018 Justin Woo Type classes: pattern matching for types October 18 2018 1 / 23
  2. What is PureScript? A language similar to Haskell Users program

    using types and values Values match the types, usage of values type-checked Users can statically derive values via types Justin Woo Type classes: pattern matching for types October 18 2018 2 / 23
  3. What are data types? data Maybe a = Just a

    | Nothing Key parts: data: begins the data type declaration Maybe: the name of our data type a: a parameter of our data type Just: A constructor of Maybe |: Separates constructors in our type For a given data type, there are one or more constructors. Justin Woo Type classes: pattern matching for types October 18 2018 3 / 23
  4. What are constructors? Constructors are values that are or can

    be used to create values of our data type: data Maybe a = Just a | Nothing mkJust :: forall a. a -> Maybe a mkJust = Just mkNothing :: forall a. Maybe a mkNothing = Nothing Justin Woo Type classes: pattern matching for types October 18 2018 4 / 23
  5. What is pattern matching? We’ve seen how to construct values

    of data types, but how do we then match on constructors of data types? Using case expressions: isJust :: forall a. Maybe a -> Boolean isJust m = case m of Just x -> true Nothing -> false At definition sites: isJust' :: forall a. Maybe a -> Boolean isJust' (Just x) = true isJust' Nothing = false Justin Woo Type classes: pattern matching for types October 18 2018 5 / 23
  6. Recap: data types Users can define data types The most

    commonly useful data types have multiple constructors* Sum members can be pattern matched for *i.e. they are Sum types Justin Woo Type classes: pattern matching for types October 18 2018 6 / 23
  7. To the type level Four things we need to learn

    to work with the type level: 1 What are type classes? 2 What are instances? 3 What are kinds? 4 What are proxies? Justin Woo Type classes: pattern matching for types October 18 2018 7 / 23
  8. 1. What are type classes? Type classes are a way

    to match on types based on kinds, allowing for defining methods for types which have an instance of a given class class Show a where show :: a -> String Justin Woo Type classes: pattern matching for types October 18 2018 8 / 23
  9. 2. What are instances? An instance is a definition for

    a class for a matched type by an instance head instance showString :: Show String where show s = s myString :: String myString = show "value" Justin Woo Type classes: pattern matching for types October 18 2018 9 / 23
  10. 3. What are kinds? Kinds are a “type of types”,

    of which the most common is Type and has associated runtime values. foreign import data ForeignData :: Type foreign import kind MaybeTy foreign import data JustTy :: Type -> MaybeTy foreign import data NothingTy :: MaybeTy Justin Woo Type classes: pattern matching for types October 18 2018 10 / 23
  11. 4. What are Proxies? A proxy is a data type

    that has a parameter of a given kind, where the parameter is not exposed as a value. -- no kind signature: inferred as Type data Proxy ty = Proxy data Proxy2 (ty :: Type) = Proxy2 data MaybeTyProxy (mt :: MaybeTy) = MaybeTyProxy justIntProxy :: MaybeTyProxy (JustTy Int) justIntProxy = MaybeTyProxy Justin Woo Type classes: pattern matching for types October 18 2018 11 / 23
  12. How do these come together? With these pieces, now we

    can see the correspondence: values : Types :: types : kinds case of : class branch : instance Justin Woo Type classes: pattern matching for types October 18 2018 12 / 23
  13. Example Now let’s try pattern matching on the MaybeTy: class

    IsJustTy (mt :: MaybeTy) where isJustTy :: MaybeTyProxy mt -> Boolean instance justTyIsJustTy :: IsJustTy (JustTy a) where isJustTy _ = true instance nothingTyIsJustTy :: IsJustTy NothingTy where isJustTy _ = false result :: Boolean result = isJustTy justIntProxy Justin Woo Type classes: pattern matching for types October 18 2018 13 / 23
  14. What if we wanted to return the result in the

    type level? We can use multi-param type classes, along with functional dependencies (“fundeps”): class IsJustTy (mt :: MaybeTy) (result :: Type.Data.Boolean) | mt -> result instance justTypeIsJustTy :: IsJustTy (JustTy a) Type.Data.True instance nothingTypeIsJustTy :: IsJustTy NothingTy Type.Data.False The parameter mt determines result, i.e. instances can be matched for by mt alone. Justin Woo Type classes: pattern matching for types October 18 2018 14 / 23
  15. Example usage in function definition: getIsJustTy :: forall mt b.

    IsJustTy mt b => MaybeTyProxy mt -> Type.Data.BProxy b getIsJustTy _ = Type.Data.BProxy result' :: Type.Data.BProxy Type.Data.True result' = getIsJustTy (MaybeTyProxy :: MaybeTyProxy (JustTy Int)) Justin Woo Type classes: pattern matching for types October 18 2018 15 / 23
  16. Some important notes Kinds are not closed, unlike sum types

    If anything, the parallel is closer with an open polymorphic variant. Instances are “partial” Compilation fails when instances cannot be found with No type class instance was found for _. There are some limited ways of doing wildcard matches for type classes e.g. using instance chains (groups) in PureScript 0.12 to constrain instances to a module in a continuous group. Justin Woo Type classes: pattern matching for types October 18 2018 16 / 23
  17. So why do we care about Type Classes? We should

    be able to derive information and/or values from static information. Computers should do work for us, not the other way around. Runtime checks for static information leave much to be desired Leaving calculations to the runtime that should be verified in compile time leaves us with dead code paths at best. And really, is there actually a difference between an incorrect program and crashes and an incorrect program that “never crashes” but always ends up in an invalid path? Why shouldn’t we actually program using type information? Why should we use types only to check some usages of values rather than reduce error-prone work and more powerfully extract values from static information? Justin Woo Type classes: pattern matching for types October 18 2018 17 / 23
  18. Example: JSON decoding Common error cases when manually writing code

    that should be derived from the type information: type DecodeResult a = Either DecodeError a type MyRecord = { apple :: String, banana :: Int } readMyRecord1 :: Foreign -> DecodeResult MyRecord readMyRecord1 f = do apple <- readString =<< readProperty "apple" banana <- readInt =<< readProperty "apple" pure { apple, banana } Justin Woo Type classes: pattern matching for types October 18 2018 18 / 23
  19. readMyRecord2 :: Foreign -> DecodeResult MyRecord readMyRecord2 f = do

    apple <- readString =<< readProperty "apple" f banana <- readString =<< readProperty "banana" f pure { apple, banana } type OtherRecord = { cherry :: String, durian :: String } mkOtherRecord :: String -> String -> OtherRecord mkOtherRecord -- ... readOtherRecord :: Foreign -> DecodeResult OtherRecord readOtherRecord f = mkOtherRecord <$> readString =<< readProperty "durian" f <*> readString =<< readProperty "cherry" f Justin Woo Type classes: pattern matching for types October 18 2018 19 / 23
  20. If all you want to do is read the type

    at the given field with the correct label and type, why even deal with this? Use Simple-JSON github.com/justinwoo/purescript-simple-json type MyJSON = { apple :: String , banana :: Int , cherry :: Maybe Boolean } decodeToMyJSON :: String -> SimpleJSON.E MyJSON decodeToMyJSON = SimpleJSON.readJSON This is also flexible for inferred field type changing, field addition/removal, etc. Justin Woo Type classes: pattern matching for types October 18 2018 20 / 23
  21. Example 2: Parse parameters out of a static type-level string

    If we have a static parameterized SQLite query, we should be able to build up the request query type: getEm :: forall a b. AllowedParamType a => AllowedParamType b => DBConnection -> { "$name" :: a, "$count" :: b } -> Aff Foreign getEm db = J.queryDB db $ SProxy :: SProxy """ select name, count from mytable where name = $name and count = $count """ Justin Woo Type classes: pattern matching for types October 18 2018 21 / 23
  22. And we can: queryDB :: forall query params. IsSymbol query

    => ExtractParams query params => SQLite3.DBConnection -> SProxy query -> { | params } -> Aff Foreign See more: speakerdeck.com/justinwoo/superior-string-spaghetti-with-purescript Justin Woo Type classes: pattern matching for types October 18 2018 22 / 23
  23. Conclusion There is a parallel between pattern matching of values

    of a Type and type classes of types of a kind. The parallel is not a perfect correspondence, which also sometimes helps us more easily use type information to derive routines and values from types. We can avoid error-prone (non-)boilerplate and use type classes to allow us to take advantage of dependent-typed techniques with PureScript. You don’t have to rely on code generation or the whims of thoughtleaders to make things work the way you see fit! Justin Woo Type classes: pattern matching for types October 18 2018 23 / 23