In Scrap your boilerplate reloaded, the authors describe a new presentation of Scrap Your Boilerplate, which is supposed to be equivalent to the original.
However, one difference is that they assume a finite, closed set of "base" types, encoded with a GADT
data Type :: * -> * where
Int :: Type Int
List :: Type a -> Type [a]
...
In the original SYB, type-safe cast is used, implemented using the Typeable
class.
My questions are:
- What is the relationship between these two approaches?
- Why was the GADT representation chosen for the "SYB Reloaded" presentation?
[I am one of the authors of the "SYB Reloaded" paper.]
TL;DR We really just used it because it seemed more beautiful to us. The class-based
Typeable
approach is more practical. TheSpine
view can be combined with theTypeable
class and does not depend on theType
GADT.The paper states this in its conclusions:
So, the choice of using a GADT for type representation is one we made mainly for clarity. As Don states in his answer, there are some obvious advantages in this representation, namely that it maintains static information about what type a type representation is for, and that it allows us to implement cast without any further magic, and in particular without the use of
unsafeCoerce
. Type-indexed functions can also be implemented directly by using pattern matching on the type, and without falling back to various combinators such asmkQ
orextQ
.Fact is that I (and I think the co-authors) simply were not very fond of the
Typeable
class. (In fact, I'm still not, although it is finally becoming a bit more disciplined now in that GHC adds auto-deriving forTypeable
, makes it kind-polymorphic, and will ultimately remove the possibility to define your own instances.) In addition,Typeable
wasn't quite as established and widely known as it is perhaps now, so it seemed appealing to "explain" it by using the GADT encoding. And furthermore, this was the time when we were also thinking about adding open datatypes to Haskell, thereby alleviating the restriction that the GADT is closed.So, to summarize: If you actually need dynamic type information only for a closed universe, I'd always go for the GADT, because you can use pattern matching to define type-indexed functions, and you do not have to rely on
unsafeCoerce
nor advanced compiler magic. If the universe is open, however, which is quite common, certainly for the generic programming setting, then the GADT approach might be instructive, but isn't practical, and usingTypeable
is the way to go.However, as we also state in the conclusions of the paper, the choice of
Type
overTypeable
isn't a prerequisite for the other choice we're making, namely to use theSpine
view, which I think is more important and really the core of the paper.The paper itself shows (in Section 8) a variation inspired by the "Scrap your Boilerplate with Class" paper, which uses a
Spine
view with a class constraint instead. But we can also do a more direct development, which I show in the following. For this, we'll useTypeable
fromData.Typeable
, but define our ownData
class which, for simplicity, just contains thetoSpine
method:The
Spine
datatype now uses theData
constraint:The function
fromSpine
is as trivial as with the other representation:Instances for
Data
are trivial for flat types such asInt
:And they're still entirely straightforward for structured types such as binary trees:
The paper then goes on and defines various generic functions, such as
mapQ
. These definitions hardly change. We only get class constraints forData a =>
where the paper has function arguments ofType a ->
:Higher-level functions such as
everything
also just lose their explicit type arguments (and then actually look exactly the same as in original SYB):As I said above, if we now want to define a generic sum function summing up all
Int
occurrences, we cannot pattern match anymore, but have to fall back tomkQ
, butmkQ
is defined purely in terms ofTypeable
and completely independent ofSpine
:And then (again exactly as in original SYB):
For some of the stuff later in the paper (e.g., adding constructor information), a bit more work is needed, but it can all be done. So using
Spine
really does not depend on usingType
at all.