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

Superior string spaghetti with PureScript

Justin Woo
October 12, 2018

Superior string spaghetti with PureScript

Lightning talk I gave at HaskellX 2018

Justin Woo

October 12, 2018
Tweet

More Decks by Justin Woo

Other Decks in Programming

Transcript

  1. Superior string spaghetti with PureScript
    Justin Woo
    October 12 2018
    Justin Woo Superior string spaghetti with PureScript October 12 2018 1 / 17

    View Slide

  2. Problem: Untyped Parameterized SQL Queries
    We want to work with 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

    View Slide

  3. What if you could Cons Symbol like Lists?
    In PureScript 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

    View Slide

  4. The problem with normal instance matching
    When parsing, we need 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

    View Slide

  5. Instance chains at work
    In PureScript 0.12, we have instance 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

    View Slide

  6. How do these solve our problem?
    Now we can write 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

    View Slide

  7. Top level function
    queryDB :: forall query params
    . IsSymbol query => ExtractParams query params
    => SQLite3.DBConnection
    -> SProxy query
    -> { | params }
    -> Aff Foreign
    class ExtractParams (query :: Symbol) (params :: # Type)
    | query -> params
    instance extractParams ::
    ( Symbol.Cons x xs query
    , ExtractParamsParse x xs params
    ) => ExtractParams query params
    Justin Woo Superior string spaghetti with PureScript October 12 2018 7 / 17

    View Slide

  8. Parsing and Extracting Params
    class ExtractParamsParse
    (x :: Symbol) -- 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

    View Slide

  9. On “$”, parse out the parameter name and add 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
    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

    View Slide

  10. Row.Cons?
    Remember that record types are parameterized by row type 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

    View Slide

  11. Figure 1:
    Justin Woo Superior string spaghetti with PureScript October 12 2018 11 / 17

    View Slide

  12. Back to the top
    Hopefully now this all makes sense:
    queryDB :: forall query params
    . IsSymbol query => ExtractParams query params
    => SQLite3.DBConnection
    -> SProxy query
    -> { | params }
    -> Aff Foreign
    Justin Woo Superior string spaghetti with PureScript October 12 2018 12 / 17

    View Slide

  13. 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 13 / 17

    View Slide

  14. Available as a library
    https://github.com/justinwoo/purescript-jajanmen
    Justin Woo Superior string spaghetti with PureScript October 12 2018 14 / 17

    View Slide

  15. Conclusion
    With PureScript 0.12, we can. . .
    Extract a 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

    View Slide

  16. Thanks
    More detailed post here
    https://github.com/justinwoo/my-blog-posts#
    well-typed-parameterized-sqlite-parameters-with-purescript
    Csongor Kiss’s post 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

    View Slide

  17. Figure 3: Your face on PureScript
    Justin Woo Superior string spaghetti with PureScript October 12 2018 17 / 17

    View Slide