Make Q (TExp a) into a newtype ==============
Leave blank. This will be filled in when the proposal is accepted.
#16177
Leave blank. This will be filled in with the first GHC version which implements the described feature.
haskell
This proposal is discussed at this pull request. After creating the pull request, edit this file again, update the number in the link, and delete this bold sentence.
I propose to modify the typed template Haskell API to make the Code
type more abstract. In particular we introduce a new data type called Code
which is abstract such that Code a
represents an expression of type a
.
There are three problems with the current API:
Q (TExp a)
leaks implementation details to the user. Using effects fromQ
means that splices can fail to run which goes against the principle of typed staged programming. The principle is that we should never fail when running a splice. Errors should occur whilst constructing the value that we want to splice in so that we ensure that we can only construct well-scoped and well-typed terms.
In particular at least
qAddTopDecls
,qRunIO
andqReport
are all unsafe in this manner as they cause errors to happen whilst splicing an expression.Making
Code
abstract means that users have to explicitly opt into this unsafe behaviour.
It is hard to properly write instances for
Q (TExp a)
as its the composition of two data types. Doing so in your program involves making your own newtype and doing a lot of wrapping/unwrapping.For example, if I want to create a language which I can either run immediately or generate code from I could write the following with the new API. :
class Lang r where _int :: Int -> r Int _if :: r Bool -> r a -> r a -> r a instance Lang Identity where _int = Identity _if (Identity b) (Identity t) (Identity f) = Identity (if b then t else f) instance Lang Code where _int = liftTyped _if cb ct cf = [|| if $$cb then $$ct else $$cf ||]
Q (TExp a)
is ugly to read and write.
A newtype is defined called Code
.:
newtype Code a = Code (Q (TExp a))
There are three constructs that the proposal affects.
Quoting an expression e :: T
now produces an expression of typed Code T
.:
-- foo :: Q (TExp Int)
foo :: Code Int
foo = [|| 5 ||]
Splicing requires an expression of type Code T
and produces a value of type T
.:
bar :: Int
bar = $$foo
The return type of lifting a value is changed from Q (TExp a)
to Code a
.:
class Lift a where
liftTyped :: a -> Code a
A new method is added in order to perform unsafe Q
actions inside of Code
.:
unsafeQ :: Q (TExp a) -> Code a
unsafeQ = Code
The proposal solves both problems as now users must use unsafeQ
in order to escape into the Q
monad. It is also straightforward to write instances because Code
is a standard data type.
The main drawback is that this will break all users of typed template Haskell who write type signatures. However, I feel like I am the only user so the impact will be minimal.
I can't think of any apart from not doing anything.
Implementation is straightforward.