true-name- Template Haskell hack to violate module abstractions

Safe HaskellSafe



Refer to these examples.



summon :: String -> Name -> Q Name Source #

Summons a Name using template-haskell's reify function.

The first argument is a String matching the Name we want: either its nameBase, or qualified with its module. The second argument gives the Name to reify.

If no match is found or there is some ambiguity, summon will fail with a list of Names found, along with the output of reify for reference.

Suppose we are given a module M that exports a function s, but not the type T, the constrcutor C, nor the field f:

module M (s) where
newtype T = C { f :: Int }
s :: T -> T
s = C . succ . f

In our own module we have no legitimate way of passing s an argument of type T. We can get around this in a type-safe way with summon:

{-# LANGUAGE TemplateHaskell #-}
module Main where
import Language.Haskell.TH.Syntax
import Unsafe.TrueName
import M

type T = $(fmap ConT $ summon "T" 's)
mkC :: Int -> T; unC :: T -> Int; f :: T -> Int
mkC = $(fmap ConE $ summon "C" =<< summon "T" 's)
unC $(fmap (`ConP` [VarP $ mkName "n"]) $ summon "C" =<< summon "T" 's) = n
f = $(fmap VarE $ summon "f" =<< summon "T" 's)

main :: IO ()
main = print (unC t, n) where
    t = s (mkC 42 :: T)
    n = f (s t)

Note that summon cannot obtain the Name for an unexported function, since GHC does not currently return the RHS of function definitons. The only workaround is to copypasta the definition. D:

truename :: QuasiQuoter Source #

A more convenient QuasiQuoter interface to summon.

The first space-delimited token gives the initial Name passed to summon: it must be ‘quoted’ with a ' or '' prefix to indicate whether it should be interpreted in an expression or a type context, as per the usual TH syntax. Subsequent tokens correspond to the String argument of summon, and are iterated over. Thus

[truename| ''A B C D |]

is roughly equivalent to:

summon "D" =<< summon "C" =<< summon "B" ''A

but with the resulting Name wrapped up in ConE, VarE, ConP, or ConT, depending on the context. (There is no quoteDec.)

Variable bindings are given after a | token in a Pat context:

[truename| ''Chan Chan | chanR chanW |] <- newChan

These may be prefixed with ! or ~ to give the usual semantics. A single .. token invokes RecordWildCards in Pat contexts, and for record construction in Exp contexts. Nested or more exotic patterns are not supported.

With this, the example from summon may be more succinctly written:

{-# LANGUAGE QuasiQuotes #-}
module Main where
import Unsafe.TrueName
import M

type T = [truename| 's T |]
mkC :: Int -> T; unC :: T -> Int; f :: T -> Int
mkC = [truename| 's T C |]
unC [truename| 's T C | n |] = n
f = [truename| 's T f |]

main :: IO ()
main = print (unC t, n) where
    t = s (mkC 42 :: T)
    n = f (s t)