parameterized SQL queries, but they’re always untyped thing = 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? Justin Woo Superior string spaghetti with PureScript October 12 2018 2 / 17
0.12, we can! “ABC” -> “A” “BC” class Cons (head :: Symbol) (tail :: Symbol) (symbol :: Symbol | head tail -> symbol, symbol -> head tail Now we can parse Symbols! class ParseParamName (x :: Symbol) (xs :: Symbol) (acc :: Symbol) (out :: Symbol) | x xs -> acc out Justin Woo Superior string spaghetti with PureScript October 12 2018 3 / 17
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 Justin Woo Superior string spaghetti with PureScript October 12 2018 4 / 17
chains (groups) First come, first served, with regular fundep-based instance matching (not constraints/guards) instance endRParseParamName :: ( Symbol.Append acc x out ) => ParseParamName x "" acc out -- note the else here: else instance spaceParseParamName :: ParseParamName " " xs out out Instance chain (groups) can only be implemented within a module, which is fine Justin Woo Superior string spaghetti with PureScript October 12 2018 5 / 17
a parser at the type level, and we can synthesize row types as usual: 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 Superior string spaghetti with PureScript October 12 2018 6 / 17
current character (xs :: Symbol) -- tail (params :: # Type) -- row type of parsed parameters | x xs -> params -- fundeps, params are determined -- base case, no more to extract at end of string: instance endExtractParamsParse :: ExtractParamsParse x "" () Justin Woo Superior string spaghetti with PureScript October 12 2018 8 / 17
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 Otherwise, continue else instance nExtractParams :: ( Symbol.Cons y ys xs , ExtractParamsParse y ys row ) => ExtractParamsParse x xs row Justin Woo Superior string spaghetti with PureScript October 12 2018 9 / 17
in PureScript 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 Justin Woo Superior string spaghetti with PureScript October 12 2018 10 / 17
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 Superior string spaghetti with PureScript October 12 2018 13 / 17
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 Justin Woo Superior string spaghetti with PureScript October 12 2018 15 / 17
on Symbol.Cons and his printf library: http://kcsongor.github.io/purescript-safe-printf/ Twitter: @jusrin00 Justin Woo Superior string spaghetti with PureScript October 12 2018 16 / 17