GHC String Interpolation - Prototypes

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]}
1 Like