This is very cool! Thanks for putting this together. I also like extensible-hasclass
, but extensible-th
is strictly more general. Specifically, you can define extensibleTHFromExtensibleHasClass
to get the behaviour of extensible-hasclass
in terms of extensible-th
. Here’s an example using your extensible-hasclass
version of sql
in terms of extensible-th
.
By contrast, there are things that extensible-th
can do that extensible-hasclass
cannot, so I think the former should be preferred. In fact, I suspect that extensnible-th
generalizes all the others.
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
module MyProject where
import Data.String (IsString (..))
import Data.String.Syntax.ExtensibleHasClass (HasClass (HasClass))
import Data.String.Syntax.ExtensibleTH (Interpolate (..))
import Data.Text (Text)
import qualified Data.Text as Text
import Language.Haskell.TH
extensibleTHFromExtensibleHasClass ::
-- | @([Either String (HasClass c)] -> a)@
Q Exp ->
[Either String (Q Exp)] ->
Q Exp
extensibleTHFromExtensibleHasClass k parts =
[|$k $(listE (map go parts))|]
where
go = \case
Left str -> [|Left str|]
Right e -> [|Right (HasClass $e)|]
sqlImpl :: [Either String (HasClass ToSqlValue)] -> SqlQuery
sqlImpl = mconcat . map go
where
go :: Either String (HasClass ToSqlValue) -> SqlQuery
go = \case
Left s -> SqlQuery s []
Right (HasClass v) -> SqlQuery "?" [toSqlValue v]
sql :: [Either String (Q Exp)] -> Q Exp
sql = extensibleTHFromExtensibleHasClass [|sqlImpl|]
Using it gives the expected results:
Summary
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module MyProjectUse where
import Data.String (IsString (..))
import Data.String.Syntax.ExtensibleTH (Interpolate)
import Data.Text (Text)
import MyProject
data Person = Person {name :: String, age :: Int}
instance Interpolate Person where
interpolate Person {..} = s"Person<${name}, ${age}>"
main :: IO ()
main = do
let name = "Alice" :: String; age = 30 :: Int; person = Person {..}
print (s"Hello ${name}! Your age is ${age}" :: String)
print (s"You are: ${person}" :: String)
let name2 = fromString "Alice" :: Text
print (s"Hello ${name2}! Your age is ${age}" :: Text)
-- use our own `sql` function defined in Lib
print sql"SELECT * FROM user WHERE name ILIKE ${name} AND age = ${age}"
ghci> main
"Hello Alice! Your age is 30"
"You are: Person<Alice, 30>"
"Hello Alice! Your age is 30"
SqlQuery {sqlText = "SELECT * FROM user WHERE name ILIKE ? AND age = ?", sqlValues = [SqlString "Alice",SqlInt 30]}