with parameterized SQL queries ◦ But they’re always untyped queryDB :: forall params. String -> Record params -> Aff Foreign aff = queryDB "select name, count from mytable where name = $name and count = $count" { "$name": "Bill", "$count": 10 } • But we know statically what the query string is here! • What if we could use it at the type level and extract a type from it?
Type-kinded) ◦ Meaning that they have no value representation ◦ “Carried” around in compile-time using string proxies data SProxy (sym :: Symbol) = SProxy • You can reflect these to a value whenever you need it class IsSymbol (sym :: Symbol) where reflectSymbol :: SProxy sym -> String What is a “type level string”?
with them at the type level with constraints symbolAppend :: forall left right appended. Symbol.Append left right appended => IsSymbol appended => SProxy left -> SProxy right -> String symbolAppend _ _ = reflectSymbol (SProxy :: SProxy appended) • The “appended” here is determined by the left and right by the functional dependencies in Symbol.Append
which parameters of a type class determine the others • Think Sum: ◦ A + B = C ◦ A and B determine C ◦ C and B determine A ◦ C and A determine B class Append (left :: Symbol) (right :: Symbol) (appended :: Symbol) | left right -> appended , right appended -> left , appended left -> right
to work with both the head and the tail • E.g. when parsing a param name until a space or end of the string class ParseParamName (x :: Symbol) (xs :: Symbol) (acc :: Symbol) (out :: Symbol) | -- ... -- invalid overloading instances! instance endRParseParamName :: ( Symbol.Append acc x out ) => ParseParamName x "" acc out instance spaceParseParamName :: ParseParamName " " xs out out • X and XS are overlapping
order these overlapping instances should match • First come, first served • Uses functional dependencies to match instances (not constraints) instance endRParseParamName :: ( Symbol.Append acc x out ) => ParseParamName x "" acc out else instance spaceParseParamName :: ParseParamName " " xs out out
a complete picture of how to parse Symbols • We can use this to extract specific labels to construct a record type for the parameters in our queries • Hence, PureScript-Jajanmen getEm :: forall a b. AllowedParamType a => AllowedParamType b => DBConnection -> { "$name" :: a, "$count" :: b } -> Aff Foreign getEm db = J.queryDB db queryP where queryP = SProxy :: SProxy "select name, count from mytable where name = $name and count = $count"
and tail class ExtractParamsParse (x :: Symbol) (xs :: Symbol) (params :: # Type) | x xs -> params • If the tail is empty, we know there are no row types to synthesize: instance endExtractParamsParse :: ExtractParamsParse x "" ()
it to our record else instance paramExtractParams :: ( Symbol.Cons y ys xs , ParseParamName y ys "$" out , Symbol.Cons z zs ys , Row.Cons out ty row' row , AllowedParamType ty , ExtractParamsParse z zs row' ) => ExtractParamsParse "$" xs row ExtractParamsParse (cont.) • Otherwise, we can continue else instance nExtractParams :: ( Symbol.Cons y ys xs , ExtractParamsParse y ys row ) => ExtractParamsParse x xs row • What is Row.Cons? • What is AllowedParamType?
are parameterized by row type data Record :: # Type -> Type type MyRecord = { a :: Int, b :: String } ~ Record (a :: Int, b :: String ) • So it makes sense we can use RowCons to add to it class Cons (label :: Symbol) (a :: Type) (tail :: # Type) (row :: # Type) | label a tail -> row, label row -> a tail
to be parameters class AllowedParamType ty instance stringAllowedParamType :: AllowedParamType String instance intAllowedParamType :: AllowedParamType Int instance numberAllowedParamType :: AllowedParamType Number
ParseParamName (x :: Symbol) (xs :: Symbol) (acc :: Symbol) (out :: Symbol) | x xs -> acc out • Multiple stop conditions, with a general case for accumulating the parameter name: else instance nParseParamName :: ( Symbol.Cons y ys xs, Symbol.Append acc x acc' , ParseParamName y ys acc' out ) => ParseParamName x xs acc out
lot of information from Symbols ◦ Use instance chains to safely use overlapping instances ◦ Use existing techniques from 0.11.x to synthesize types using the extracted information from Symbols • Hopefully this gives you some ideas of what you might try tackling next
Csongor Kiss’s post on his printf library: http://kcsongor.github.io/purescript-safe-printf/ • PureScript 0.12.0 release notes ◦ https://github.com/purescript/purescript/releases/tag/v0.12.0 • Rough getting started with PureScript guide ◦ https://github.com/justinwoo/my-blog-posts#setting-up-purescript-in-march-2018 • Also see managing package sets ◦ https://github.com/justinwoo/my-blog-posts#managing-psc-package-sets-with-dhall