Skip to content

Instantly share code, notes, and snippets.



Last active Aug 12, 2020
What would you like to do?
Statically checked overloaded strings

Statically checked overloaded strings

This gist demonstrates a trick I came up with which is defining IsString for Q (TExp a), where a is lift-able. This allows you to write $$("...") and have the string parsed at compile-time.

This offers a light-weight way to enforce compile-time constraints. It's basically OverloadedStrings with static checks.

This trick works already in existing (old) GHCs.

{-# LANGUAGE TemplateHaskell, OverloadedStrings #-}
import Network.URI
import Path
import PATHTH
import SHA256TH
import URITH
uri :: URI
uri = $$("")
sha256 :: SHA256
sha256 = $$("7725b35d9ca8be3668abe63481f0731ca4730509419b4eb29fa0b0baa4798458")
home :: Path Abs Dir
home = $$("/home/chris")

Versus QuasiQuotes

This is more light-weight and overloaded than, e.g.


which requires stating the name of the quoter you want (sometimes you'd rather not), requires the QuasiQuotes extension, and leaves syntax highlighters not sure how to highlight your content properly.

A step to make this syntax even lighter

It'd be nice to relax GHC's parser a little to support $$"..." to mean the same thing. This wouldn't conflict with any existing syntax that I am aware of, or of any existing plans or proposals.

{-# LANGUAGE FlexibleInstances #-}
module PATHTH where
import Data.String (IsString(..))
import Language.Haskell.TH.Syntax (Q, TExp(..), lift)
import Path
instance IsString (Q (TExp (Path Rel Dir))) where
fromString = fmap TExp . mkRelDir
instance IsString (Q (TExp (Path Abs Dir))) where
fromString = fmap TExp . mkAbsDir
instance IsString (Q (TExp (Path Rel File))) where
fromString = fmap TExp . mkRelFile
instance IsString (Q (TExp (Path Abs File))) where
fromString = fmap TExp . mkAbsFile
{-# LANGUAGE DeriveLift, FlexibleInstances, TemplateHaskell #-}
module SHA256TH where
import Data.ByteString (ByteString)
import qualified Data.ByteString as S
import qualified Data.ByteString.Base16 as Hex
import Data.String
import Instances.TH.Lift ()
import Language.Haskell.TH.Syntax (Q, TExp(..), Lift(..))
newtype SHA256 = SHA256 ByteString deriving (Eq, Ord, Lift)
instance IsString (Q (TExp SHA256)) where
fromString i =
if length i == 64
then case Hex.decode (fromString i) of
(result, wrong)
| S.null wrong -> fmap TExp (lift (SHA256 result))
_ -> fail "Invalid SHA256 format."
else fail "Incorrect length for SHA256."
{-# LANGUAGE NamedFieldPuns, FlexibleInstances, TemplateHaskell #-}
module URITH where
import Data.String (IsString(..))
import Language.Haskell.TH.Syntax (Q, TExp(..), lift)
import Network.URI (URI(..), parseURI, URIAuth(..))
instance IsString (Q (TExp URI)) where
fromString i =
case parseURI i of
Nothing -> fail ("Invalid URI: " ++ show i)
Just uri -> liftURI uri
liftURI :: URI -> Q (TExp URI)
liftURI URI {uriScheme, uriAuthority, uriPath, uriQuery, uriFragment} =
fmap TExp [|URI {uriScheme, uriAuthority = $(mauthority), uriPath, uriQuery, uriFragment}|]
mauthority = maybe [|Nothing|] liftAuthority uriAuthority
liftAuthority URIAuth {uriUserInfo, uriRegName, uriPort} =
[|Just (URIAuth {uriUserInfo, uriRegName, uriPort})|]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment