Deriving Via

56

or, How to Turn Hand-Written Instances into an Anti-Pattern

58

1
2
3
4
5

Baldur Blöndal

6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

57

Andres Löh

Ryan Scott

Well-Typed LLP

Indiana University

Abstract

It is tempting to avoid this obvious repetition by defining
an instance for all such types in one fell swoop:

Haskell’s deriving construct is a cheap and cheerful way toquickly generate instances of type classes that follow common patterns. But at present, there are only a subset of such
type class patterns that deriving supports, and if a particular class lies outside of this subset, then one cannot derive
it at all, with no alternative except for laboriously declaring
the instances by hand.
To overcome this deficit, we introduce Deriving Via, an
extension to deriving that enables programmers to compose instances from named programming patterns, thereby
turning deriving into a high-level domain-specific languagefor defining instances. Deriving Via leverages newtypes—analready familiar tool of the Haskell trade—to declare recurring patterns in a way that both feels natural and allows ahigh degree of abstraction.

newtype Endo a = MkEndo (a -> a)

ACM Reference Format:
Baldur Blöndal, Andres Löh, and Ryan Scott. 2018. Deriving Via: or,
How to Turn Hand-Written Instances into an Anti-Pattern. In Proceedings of Haskell Symposium (Submitted to Haskell). ACM, New
York, NY, USA, 12 pages. https://doi.org/10.1145/nnnnnnn.nnnnnnn-- Data.Monoid

36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

1

Introduction

In Haskell, type classes capture common interfaces. Whendefining class instances, we often discover repeated patterns
where different instances have the same definition. For example, the following instances appear in the base library of
the Glasgow Haskell Compiler (GHC):

64
66
67
68
69
71
72
73
74
75
76
77
78

instance Monoid (Endo a) where
mempty = MkEndo id
mappend (MkEndo f) (MkEndo g) = MkEndo (f . g)

82

79
80
81
83
84

instance Monoid a => Monoid (ST s a) where
mempty = pure mempty
mappend = liftA2 mappend

55

2018-04-01 14:57. Page 1 of 1–12.

87
88
89
90
91
93
94
95

In fact, the monoid instance for lists is captured by a different
rule based on Alternative:

96

instance Alternative f => Monoid (f a) where
mempty = empty
mappend = (<|>)

99

97
98
100
101
102

These have completely identical instance bodies. The underlying pattern works not only for IO and ST s, but for any
applicative functor f.

54

86

92

instance Monoid [a] where
mempty = []mappend = (++)

instance Monoid a => Monoid (IO a) where
mempty = pure mempty
mappend = liftA2 mappend

Submitted to Haskell, 09/2018, St. Louis, MO, USA
2018. ACM ISBN 978-x-xxxx-xxxx-x/YY/MM. . . $15.00
https://doi.org/10.1145/nnnnnnn.nnnnnnn

53

63

85

Because instance resolution never backtracks, we can’t define these two distinct rules for Monoid (f a) at the sametime, even with overlapping instances.
The only viable workaround using the Haskell type classsystem is to write the instances for each data type by hand,each one with an identical definition (like the instances for
IO a and ST s a), which is extremely unsatisfactory:

51
52

62

Here, Endo is not an applicative functor, but it still admits aperfectly valid Monoid instance that overlaps with the general instance above:

Moreover, even if we have an applicative functor f on ourhands, there is no guarantee that this is the definition wewant. Notably, lists are the free monoid (the most ‘fundamental’ monoid) but that instance does not coincide withthe rule above and in particular, imposes no (Monoid a) constraint:

32

35

61

70

Unfortunately, this general instance is undesirable as it overlaps with all other (f a)-instances. Instance resolution will
match the instance head first before considering the context,
whether f is applicative or not. Once GHC has commited to
an instance, it will never backtrack. Consider:

31

34

60

65

instance (Applicative f, Monoid a)=> Monoid (f a) where
mempty = pure mempty
mappend = liftA2 mappend

30

33

59

1

103
104
105
106
107
108
109
110

Submitted to Haskell, 09/2018, St. Louis, MO, USA
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125

Baldur Blöndal, Andres Löh, and Ryan Scott• It is not obvious that we are instantiating a generalprinciple.
• Because the general principle is not written down incode with a name and documentation, it has to be communicated through folklore or in comments and is difficult to discover and search for. Our code has lost aconnection to its origin.
• There are many such rules, some quite obvious, butothers more surprising and easy to overlook.
• While the work required to define instances manuallyfor Monoid—which only has two methods—is perhaps
acceptable, it quickly becomes extremely tedious anderror-prone for classes with many methods.
As an illustration of the final point, consider Num. There isa way to lift a Num instance through any applicative functor:1

We thus have no other choice but to write some instancesby hand. This means that we have to provide explicit implementations of at least a minimal subset of the class methods.
There is no middle ground here, and the additional workrequired compared to deriving can be drastic—especially if
the class has many methods—so the option of using deriving
remains an appealing alternative.

128
129
130

133
134

fromInteger = pure . fromInteger

132

135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162

Defining such boilerplate instances manually for concrete
type constructors is so annoying that Conal Elliott introduced a preprocessor [7] for this particular use case several
years ago.
1.1

Deriving

Readers familiar with Haskell’s deriving mechanism may
wonder why we cannot simply derive all the instances wejust discussed. Unfortunately, our options are very limited.
To start, Monoid is not one of the few blessed type classes
that GHC has built-in support to derive. It so happens that
(IO a), (ST s a) and (Endo a) are all newtypes, so they arein principle eligible for generalized newtype deriving (GND),
in which their instances could be derived by reusing the instances of their underlying types [1]. However, this would
give us the wrong definition in all three cases.
Our last hope is that the the Monoid type class has a suitable generic default implementation [10]. If that were thecase, we could use a deriving clause in conjunction with the
DeriveAnyClass extension, and thereby get the compiler togenerate an instance for us.
However, there is no generic default for Monoid, a standard class from the base library (which would be difficult tochange). But even if a generic instance existed, it would still
capture a single rule over all others, so we couldn’t ever use
it to derive both the monoid instance for lists and that for
ST s a.

163

1 Similarly for Floating and Fractional, numeric type classes with a com-164

bined number of 25 methods (15 for a minimal definition).

165

Introducing Deriving Via

We are now going to address this unfortunate lack of abstraction and try to bridge the gap between manually defined instances and the few available deriving mechanisms
we have at our disposal.
Our approach has two parts:
1. We capture general rules for defining new instances
using newtypes.
2. We introduce Deriving Via, a new language constructthat allows us to use such newtypes to explain to thecompiler exactly how to construct the instance without having to write it by hand.
As a result, we are no longer limited to a fixed set of predefined ways to define particular class instances, but caninstead teach the compiler new rules for deriving instances,
selecting the one we want using a high-level description.
Let us look at examples. For the first part, we revisit therule that explains how to lift a monoid instance throughan applicative functor. We can turn the problematic genericand overlapping instance for Monoid (f a) into an entirely
unproblematic instance by defining a suitable adapter newtype [8] and wrapping the instance head in it:

instance (Applicative f, Num a) => Num (f a) where(+) = liftA2 (+)(-) = liftA2 (-)(∗) = liftA2 (∗)
negate = liftA negateabs= liftA abs
signum = liftA signum

131

167
168
169
170
171
172
173

1.2

126
127

166

newtype App f a = App (f a)

174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197

instance (Applicative f, Monoid a)=> Monoid (App f a) where
mempty = App (pure mempty)
mappend (App f) (App g) = App (liftA2 mappend f g)

198

Since GHC 8.4, we also need a Semigroup instance, because
it is now a superclass of Monoid2 :

202

199
200
201
203
204

instance (Applicative f, Semigroup a)=> Semigroup (App f a) where
App f <> App g = App (liftA2 (<>) f g)

206
207

The second part is to now use such a rule in our new formof deriving statement. We can do this when defining a newdata type, such as in

208

data Maybe a = Nothing | Just aderiving Monoid via (App Maybe a)211

This requires that we independently have an Applicative
instance for Maybe, but then we obtain the desired Monoid
instance nearly for free.
In the deriving clause, via is a new language constructthat explains how GHC should derive the instance, namely
2 See Section 4.4 for a more detailed discussion of this aspect.

2

205

2018-04-01 14:57. Page 2 of 1–12.

209
210
212
213
214
215
216
217
218
219
220

Deriving Via
221
222
223
224
225
226
227
228
229
230
231
232

Submitted to Haskell, 09/2018, St. Louis, MO, USA

by reusing the Monoid instance already available for the via
type, App Maybe a. It should be easy to see why this works:
due to the use of a newtype, App Maybe a has the same internal representation as Maybe a, and any instance available on
one type can be made to work on the other by suitably wrapping or unwrapping a newtype. In more precise language,
App Maybe a and Maybe a are representationally equal.
The Data.Monoid module defines many further adaptersthat can readily be used with Deriving Via. For example, therule that obtains a Monoid instance from an Alternative instance is already available through the Alt newtype:test-case generation functionality is the Arbitrary class. Its
primary method is arbitrary, which describes how to generate suitable random values of a given size and type. It also
has a method shrink that is used to try to shrink failing counterexamples of test properties.
Many standard Haskell types, such as Int and lists, arealready instances of Arbitrary. This can be very convenient,because many properties involving these types can be quickchecked without any extra work.
On the other hand, there are often additional constraints
imposed on the actual values of a type that are not sufficiently expressed in their types. Depending on the context
and the situation, we might want to guarantee that we generate positive integers, or non-empty lists, or even sorted
lists.
The QuickCheck library provides a number of newtypebased adapters (called modifiers in the library) for this purpose. As an example, QuickCheck defines:

newtype Alt f a = Alt (f a)

233
234
235
236
237
238
239
240
241

instance Alternative f => Monoid (Alt f a) where
mempty= Alt empty
mappend (Alt f) (Alt g) = Alt (f <|> g)instance Alternative f => Semigroup (Alt f a) where(<>) = mappend

Using adapters such as App and Alt, a vast amount of Monoid
instances that currently have to be defined by hand can instead be derived using the via construct.

newtype NonNegative a =NonNegative {getNonNegative :: a}which comes with a predefined instance of the form

242
243

1.3

244

The paper is structured as follows: In Section 2, we use the
QuickCheck library as a case study to explain in more detail how Deriving Via can be used, and how it works. In Section 3, we explain in detail how to typecheck and translate
Deriving Via clauses. In Section 4, we discuss several additional applications of Deriving Via. We discuss related ideas
in Section 5, describe the current status of our extension in
Section 6 and conclude in Section 7.
Our extension is fully implemented in a GHC branch3 , andwe are working on a proposal to incorporate it into GHC
proper, so it will hopefully be available in a future release of
GHC.
The idea of Deriving Via is surprisingly simple, yet it has
a number of powerful and equally surprising properties:• It further generalizes the generalized newtype deriving
extension. (Section 3.2.1).
• It additionally generalizes the concept of default signatures. (Section 4.2).
• It provides a possible solution to the problem of introducing additional boilerplate code when introducing
new superclasses (such as Applicative for Monad, Section 4.4).
• It allows for reusing instances not just between representationtally equal types, but also between isomorphic or similarly related types (Section 4.3).

245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273

Contributions and structure of the paper

instance (Num a, Ord a, Arbitrary a)=> Arbitrary (NonNegative a)that explains how to generate and shrink non-negative numbers. A user who wants a non-negative integer can now use
NonNegative Int rather than Int to make this obvious.
This approach, however, has a drastic disadvantage: wehave to wrap each value in an extra constructor, and the
newtype and constructor are QuickCheck-specific. An implementation detail (the choice of testing library) leaks into
the data model of an application. While we might be willingto use domain-specific newtypes for added type safety, suchas Age or Duration, we might not be eager to add QuickCheck
modifiers everywhere. And what if we need more than one
modifier? And what if other libraries export their own set ofmodifiers as well? We certainly do not want to change theactual definition of our data types (and corresponding code)
whenever we start using a new library.
With Deriving Via, we have the option to reuse the existing infrastructure of modifiers without paying the price ofcluttering up our data type definitions. We can choose an
actual domain-specific newtype such as
newtype Duration = Duration Int -- in seconds

and now specify exactly how the Arbitrary should be derived for this:deriving Arbitrary via (NonNegative Int)

2

Case study: QuickCheck

This yields an Arbitrary instance which only generates nonnegative integers. Only the deriving clause changes, not thedata type itself. If we later decide we want only positive integers as durations, we replace NonNegative with Positive

QuickCheck [3] is a well-known Haskell library for randomized property-based testing. At the core of QuickCheck’s

274

3 https://github.com/RyanGlScott/ghc/tree/deriving-via

275

2018-04-01 14:57. Page 3 of 1–12.

3

276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330

Submitted to Haskell, 09/2018, St. Louis, MO, USA
331
332

Baldur Blöndal, Andres Löh, and Ryan Scottin the deriving clause. Again, the data type itself is unaffected. In particular, we do not have to change any constructor names anywhere in our code.

2.3

Parameterized modifiers

386
387

335

2.1

336

Multiple modifiers can be combined. For example, there isanother modifier called Large that will scale up the size of
integral values being produced by a generator. It is definedasSometimes, we might want to parameterize a generator withextra data. We can do so by defining a modifier that has extra arguments and using those extra arguments in the associated Arbitrary instance.
An extreme case that also makes use of type-level programming features in GHC is a modifier that allows us to
specify a lower and an upper bound of a generated natural
number.

newtype Large a = Large {getLarge :: a}newtype Between (l :: Nat) (u :: Nat) = Between Integer

396

instance (KnownNat l, KnownNat u)=> Arbitrary (Between l u) where
arbitrary = Between <$>choose (natVal @l Proxy, natVal @u Proxy)

397

333
334

337
338
339

Composition

342
343
344
345
346

with a corresponding Arbitrary instance:
instance (Integral a, Bounded a) => Arbitrary (Large a)(Note that this instance makes use of visible type application [6] in natVal @l and natVal @u.)
We can then equip an application-specific type for years
with a generator that lies within a plausible range:

deriving Arbitrary via (NonNegative (Large Int))

349
350
351
352
353
354

and derive an instance which only generates Duration values that are both non-negative and large. This works because Duration still shares the same runtime representationas NonNegative (Large Int) (namely, that of Int), so the latter’s Arbitrary instance can be reused.

newtype Year = Year Integer
deriving Show
deriving Arbitrary via (Between 1900 2100)

357
358
359
360
361
362
363
364
365
366
367
368

2.2

Adding new modifiers

Of course, we can add add our own modifiers if the set ofpredefined modifiers is not sufficient. For example, it is difficult to provide a completely generic Arbitrary instance thatworks for all data types, simply because there are too many
assumptions about what makes good test data that need tobe taken into account.
But for certain groups of data types, there are quite reasonable strategies of coming up with generic instances. For
example, for enumeration types, one strategy is to desire a
uniform distribution of the finite set of values. QuickCheckeven offers such a generator, but it does not expose it as a
newtype modifier:

371
372
373
374
375
376
377
378
379

382
383

arbitraryBoundedEnum :: (Bounded a, Enum a) => Gen a

But from this, we can easily define our own:

393
394

398
399
400
402
403
404
405
406
407
408
410
411
412

415

• How Deriving Via clauses are typechecked.
• What code Deriving Via generates behind the scenes.
• How to determine the scoping of type variables in Deriving Via clauses.

419

3.1

instance (Bounded a, Enum a)=> Arbitrary (BoundedEnum a) where
arbitrary = BoundedEnum <$> arbitraryBoundedEnum

Deriving Via grants the programmer the ability to put extra types in her programs, but the flip side to this is thatit’s possible for her to accidentally put total nonsense intoa Deriving Via clause, such as:

We can then use this functionality to derive Arbitrary for a
new enumeration type:Well-typed uses of Deriving Via

416
417
418
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434

newtype S = S Charderiving Eq via Maybe

data Weekday = Mo | Tu | We | Th | Fr | Sa | Su
deriving (Enum, Bounded)
deriving Arbitrary via (BoundedEnum Weekday)

414

Seeing enough examples of Deriving Via can give the impression that it is a somewhat magical feature. In this section, we aim to explain the magic underlying Deriving Via
by giving a more precise description of:

newtype BoundedEnum a = BoundedEnum a

435
436

384
385

Typechecking and translation

To avoid clutter, we assume that all types have monomorphic kinds. However, it is easy to incorporate kind polymorphism [12], and our implementation of these ideas in GHC
does so.

380
381

392

413

3

369
370

391

409

In general, we can use this technique of adding extra parameters to a newtype to support additional ways to configure the behavior of derived instances.

355
356

390

401

For our Duration type, we can easily write

347
348

389

395

340
341

388

4

In this section, we describe a general algorithm for when a
Deriving Via clause should typecheck, which will allow us
to reject ill-formed examples like the one above.

437

2018-04-01 14:57. Page 4 of 1–12.

440

438
439

Deriving Via
441

3.1.1

442

Suppose we are deriving the following instance:

Submitted to Haskell, 09/2018, St. Louis, MO, USA

Aligning kinds

In the first example, we have the class Eq, which is of kind
* -> Constraint. The argument to Eq, which is of kind *,does not require that we eta-reduce any variables. As a result, we check that A a is of kind *, which is the case.
In the second example, we have the class Functor, which
is of kind (* -> *) -> Constraint. The argument to Functor
is of kind (* -> *), which requires that we eta-reduce one
variable from B b to obtain B. We then check that B is kindof (* -> *), which is true.

443
444
445
446
447
448
449
450
451
452

data D d1 . . . dm
deriving (C c1 . . . cn ) via (V v1 . . . vp )In order for this declaration to typecheck, we must check thekinds of each type. In particular, the following conditions
must hold:
1. The type C c1 . . . cn must be of kind (k1 -> . . . -> kr ->*) -> Constraint for some kinds k1 , . . . , kr . The reason is that the instance we must generate,

454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
473

data Foo a = Foo a aderiving Functor

474
475

478

despite the fact that Foo a has kind * and the argument toFunctor has kind (* -> *). This is because the code that actually gets generated has the following shape:

479

instance Functor Foo where . . .

476
477

480
481
482
483
484
485
486
487
488
489
490
491
492
494
495

2018-04-01 14:57. Page 5 of 1–12.

Generalized newtype deriving (GND)

499
500
501
502
503
504
506
507
508
509
510
511
512
514

The process by which Deriving Via generates code is heavily
based off of the approach that the GND takes, so it is informative to first explain how GND works. From there, Deriving Via is a straightforward generalization—so much so that
Deriving Via could be thought of as “generalized GND”.
Our running example in this section will be the newtype
Age, which is a simple wrapper around Int (which we willcall the representation type):

515

newtype Age = MkAge Int
deriving Enum

524

516
517
518
519
520
521
522
523
525
526

A naïve way to generate code would be to manually wrap
and unwrap the MkAge constructor wherever necessary, suchas in the code below:
instance Enum Age where
toEnum i = MkAge (toEnum i)
fromEnum (MkAge x) = fromEnum x
enumFrom (MkAge x) = map MkAge (enumFrom x)

527
528
529
530
531
532
533
534

This works, but is somewhat unsatisfying. After all, a newtype is intended to be a zero-cost abstraction that acts identically to its representation type at runtime. Accordingly,
any function that mentions a newtype in its type signature
should be able to be converted to a new function with all
occurrences of the newtype in the type signature replacedwith the representation type, and moreover, that new function should behave identically to the old one at runtime.
Unfortunately, the implementation of enumFrom may not
uphold this guarantee. While wrapping and unwrapping the
MkAge constructor is certain to be a no-op, the map function
is definitely not a no-op, as it must walk the length of a list.
But the fact that we need to call map in the first place feels
rather silly, as all we are doing is wrapping a newtype ateach element.

To put it differently, we have eta-reduced away the a in Foo
a before applying Functor to it. The power to eta-reduce
variables from the data type is part of what makes deriving
clauses so flexible.
To determine how many variables to eta-reduce, we must
examine the kind of C c1 . . . cn , which by condition (1) isof the form ((k1 -> ... -> kr -> ∗) -> Constraint) for somekinds k1 , . . . , kr . Then the number of variables to eta-reduce
is simply r , so to compute the i in D d1 . . . di , we take i =m − r.
This is better explained by example, so consider the following two scenarios, both of which typecheck:
newtype A a = A a deriving Eq
via (Identity a)newtype B b = B b deriving Functor via Identity

493

3.2.1

Note that in the conditions above, we specify D d1 . . . di (forsome i), instead of D d1 . . . dm . That is because in general,
the kind of the argument to C c1 . . . cn is allowed to be different from the kind of D d1 . . . dm ! For instance, the following example is perfectly legitimate:class Functor (f :: * -> *) where . . .

498

513

Eta-reducing the data type472

Code generation

Once the typechecker has ascertained that a via type is fullycompatible with the data type and the class for which an
instance is being derived, GHC proceeds with generating thecode for the instance itself. This generated code is then fed
back into the typechecker, which acts as a final sanity checkthat GHC is doing the right thing under the hood.

requires that we can apply C c1 . . . cn to another typeD d1 . . . di (where i ⩽ m, see Section 3.1.2). Therefore,
it would be nonsense to try to derive an instance of C
c1 . . . cn if it had kind, say, Constraint.
2. The kinds V v1 . . . vp and D d1 . . . di , and the kind of
the argument to C c1 . . . cn must all unify. This check
rules out the above example of deriving Eq via Maybe,
as it does not even make sense to talk about reusing
the Eq instance for Maybe—which is of kind (* -> *)—as Eq instances can only exist for types of kind *.
3.1.2

497

505

3.2

instance C c1 . . . cn (D d1 . . . di ) where . . .

453

496

5

535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550

Submitted to Haskell, 09/2018, St. Louis, MO, USA
551
552

Baldur Blöndal, Andres Löh, and Ryan Scott

Luckily, there is a convenient solution to this problem: thesafe coerce function [1]:instance Coercible (Age -> [Age]) (Int -> [Int])instance Coercible (Int -> [Int]) (Age -> [Age])

coerce :: Coercible a b => a -> bThis demonstrates the ability to cast through the function
and list type constructors. This ability is important, as ourderived enumFrom instance would not typecheck otherwise!
Another crucial fact about Coercible that we rely on isthat it is transitive: if Coercible a b and Coercible b c hold,then Coercible a c also holds. This is perhaps unsurprising if one views Coercible as an equivalence relation, but
it a fact that is worth highlighting, as the transitivity of
Coercible is what allows us to coerce between newtypes. For
instance, if we have these two newtypes:

553
554
555
556
557
558
559
560
561
562
563
564
565
566

Operationally, coerce can be thought of as behaving like its
wily cousin, unsafeCoerce, which takes a value of one typeas casts it to a value at a another type. Unlike unsafeCoerce,
which can break programs if used carelessly, coerce is completely type-safe due to its use of the Coercible constraint.
We will explain Coercible in more detail later, but for now,
it suffices to say that a Coercible a b constraint witnesses
the fact that two types a and b have the same representation
at runtime, and thus any value of type a can be safely castto type b.
Armed with coerce, we can show what code GND would
actually generate for the Enum Age instance above:

567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599

instance Enum Age where
toEnum =coerce @(Int -> Int) @(Int -> Age) toEnum
fromEnum =coerce @(Int -> Int) @(Age -> Int) fromEnum
enumFrom =coerce @(Int -> [Int]) @(Age -> [Age]) enumFrom

602
603
604
605

608
609
610
611
612
613
614
615
616
617
618
619

then GHC is able to conclude that Coercible (A Int) B holds,
because we have the following Coercible rules

621

620
622
623

as well as transitivity. As we will discuss momentarily, Deriving Via in particular makes heavy use of the transitivityof Coercible.

624
625
626
627
628
629

3.2.3

From GND to Deriving Via

As we saw in Section 3.2.1, the code which GND generates
relies on coerce to do the heavy lifting. In this section, we
will generalize this technique slightly to give us a way togenerate code for Deriving Via.
Recall the following GND-derived instance:

630
631
632
633
634
635
636

newtype Age = MkAge Int deriving EnumAs stated above, it generates the following code for enumFrom:The Coercible constraint

instance Enum Age where...
enumFrom =coerce @(Int -> [Int]) @(Age -> [Age]) enumFrom

A Coercible constraint can be thought of as evidence that
GHC can use to cast between two types. Coercible is not a
type class, so it is impossible to write a Coercible instanceby hand. Instead, GHC can generate and solve Coercible
constraints automatically as part of its built-in constraint
solver, much like it can solve equality constraints. (Indeed,
Coercible can be thought of as a broader notion of equality
among types.)
As mentioned in the previous section, a newtype can besafely cast to and from its representation type, so GHC treats
them as inter-Coercible. Continuing our earlier example,
this would mean that GHC would be able to conclude that:

Here, there are two crucially important types: the representation type, Int, and the original newtype itself, Age. The
implementation of enumFrom simply sets up an invocation of
coerce enumFrom, with explicit type arguments to indicatethat we should reuse the existing enumFrom implementation
for Int and reappropriate it for Age.
The only difference in the code that GND and Deriving
Via generate is that in the former strategy, GHC always picks
the representation type for you, but in Deriving Via, the user
has the power to choose this type. For example, if a programmer had written this instead:instance Coercible Age Int
instance Coercible Int Age

600
601

607

newtype A a = A [a]newtype B = B [Int]instance Coercible (A Int) [Int]instance Coercible [Int] B

Now we have a strong guarantee that the Enum instance forAge has exactly the same runtime characteristics as the instance for Int. As an added benefit, the code ends up being
simpler, as every method can be implemented as a straightforward application of coerce. The only interesting part isgenerating the two explicit type arguments [6] that are being used to specify the source type (using the representationtype) and the target type (using the newtype) of coerce.
3.2.2

606

639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
656

newtype Age = MkAge Int deriving Enum via T

then the following code would be generated:
6

638

655

newtype T = T Int
instance Enum T where . . .

But this is not all that Coercible is capable of. A key property is that GHC’s constraint solver can look inside of othertype constructors when determining if two types are interCoercible. For instance, both of these statements hold:637

2018-04-01 14:57. Page 6 of 1–12.

657
658
659
660

Deriving Via
661
662
663
664
665
666
667
668
669
670

Suppose we first quantified the variables in the derived
classes and made them scope over the via type. Because
each derived class has its own type variable scope, the a in C1
a would be bound independently from the a in C2 a. In otherwords, we would have something like this (using a hypothetical forall syntax):

enumFrom =coerce @(T -> [T]) @(Age -> [Age]) enumFrom

This time, GHC coerces from an enumFrom implementation
for T (the via type) to an implementation for Age. (Recall
from Section 3.2.2 that this is possible since we can coerce
transitively from T to Int to Age).
Now we can see why the instances that Deriving Via cangenerate are a strict superset of those that GND can generate.
For instance, our earlier GND example

671

newtype Age = MkAge Int deriving Enum672

could equivalently have been written using Deriving Via
like so:

673

Submitted to Haskell, 09/2018, St. Louis, MO, USA

deriving (forall a . C1 a, forall a . C2 a) via (T a)newtype Age = MkAge Int deriving Enum via Int

676
677

3.3

678

In the remainder of this section, we will present an overview
of how type variables are bound in Deriving Via clauses, andover what types they scope. Deriving Via introduces a newplace where types can go, and more importantly, it introduces a new place where type variables can be quantified,
so it takes some amount of care to devise a consistent treatment for it.

679
680
681
682
683
684

Type variable scoping

deriving (C1 a, C2 a) via (forall a . T a)

Now, there is no ambiguity regarding a, as both a variablesin the list of derived classes were bound in the same place.
It might feel strange visually to see a variable being used
before its binding site (assuming one reads code from left toright). However, this is not unprecedented within Haskell,as this is also legal:

685
686

3.3.1

687

Consider the following example:

Binding sites

688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712

data Foo a = . . .
deriving (Baz a b c) via (Bar a b)

Where is each type variable quantified?
• a is bound by Foo itself in the declaration data Foo a.
Such a variable scopes over both the derived class, Baza b c, as well as the via type, Bar a b.
• b is bound by the via type, Bar a b. Note that b isbound here but a is not, as it was bound earlier by
the data declaration. b scopes over the derived classtype, Baz a b c, as well.
• c is bound by the derived class, Baz a b c, as it was
not bound elsewhere. (a and b were bound earlier.)
In other words, the order of scoping starts at the data declaration, then the via type, and then the derived classes associated with that via type.
3.3.2

715

2018-04-01 14:57. Page 7 of 1–12.

719
720
721
722
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740

In this example, we have another scenario where things arebound (g and h) after their use sites. In this sense, the via
keyword is continuing a rich tradition pioneered by whereclauses.
One alternative idea (which was briefly considered) was
to put the via type before the derived classes so as to avoid
this “zigzagging” scoping. However, this would introduce
additional ambiguities. Imagine one were to take this example:

741
742
743
744
745
746
747
748
749
750

And convert it to a form in which the via type came first:
deriving via X Y Z

Should this be parsed as (X Y) Z, or X (Y Z)? It’s not clear
visually, so this choice would force programmers to write
additional parentheses.

751
752
753
754
755
756
757
758

4

More use cases

We have already seen in Section 2 how Deriving Via facilitates greater code reuse in the context of QuickCheck. Thisis far from the only domain where Deriving Via proves tobe a natural fit, however. In fact, there are so many of these
domains, there would be enough to fill pages upon pages!
Unfortunately, we do not have enough space to document
all of these use cases, so in this section, we present a crosssection of scenarios in which Deriving Via can capture interesting patterns and allow programmers to abstract overthem in a convenient way.

This scoping order may seem somewhat surprising, as one
might expect the type variables bound by the derived classes
to scope over the via type instead. However, this choice introduces additional complications that are tricky to resolve.
For instance, consider a scenario where one attempts to derive multiple classes at once with a single via type:

714

718

f = g + h where g = 1; h = 2

deriving Z via X Y

Establishing orderdata D
deriving (C1 a, C2 a) via (T a)

713

717

723

Now we are faced with a thorny question: which a is used in
the via type, T a? There are multiple choices here, since thea variables in C1 a and C2 a are distinct! This is an important
decision, since the kinds of C1 and C2 might differ, so the
choice of a could affect whether T a kind-checks or not.
On the other hand, if one binds the a in T a first and has
it scope over the derived classes, then this becomes a nonissue. We would instead have this:

674
675

716

7

759
760
761
762
763
764
765
766
767
768
769
770

Submitted to Haskell, 09/2018, St. Louis, MO, USA
771

4.1

772

A widely used feature of type classes is their ability to givedefault implementations for their methods if a programmer
leaves them off. One example of this can be found in the
Applicative class. The main workhorse of Applicative isthe (<*>) method, but on occasion, it is more convenient to
use the (<*) or (*>) methods, which sequence their actions
but discard the result of one of their arguments:

773
774
775
776
777
778

Baldur Blöndal, Andres Löh, and Ryan Scott

Asymptotic improvements with ease

781
782
783
784
785
786
787
788
789
790
791
792
793

796
797
798
799
800
801
802
803
804
805
806
807
808
809

812
813
814
815
816
817
818
819
820
821
822
823
824
825

827
828

newtype IntConsumer a = IntConsumer (Int -> a)deriving (Functor, Representable)
deriving Applicative via (WrapRep IntConsumer)class Functor f => Applicative f wherepure :: a -> f a(<*>) :: f (a -> b) -> f a -> f b

830
831
832
833
834
835
836

Not only does this save code in the long run, but it also gives
a name to the optimization being used, which allows it to be
documented, exported from a library, and thereby easier tospot “in the wild” for other programmers.

(<*) :: f a -> f b -> f a(<*) = liftA2 (\ a _ -> a)(*>) :: f a -> f b -> f b(*>) = liftA2 (\ _ b -> b)

4.2

As shown here, (<*) and (*>) have default implementationsin terms of liftA2. This works for any Applicative, but is
not as efficient as it could be in some cases. For some instances of Applicative, we can actually implement thesemethods in O(1) time instead of using liftA2, which canoften run in superlinear time. One such Applicative is thefunction type (->):

Making defaults more flexible

In the previous section, we saw an example of how relying too much on a type class’s default implementations canbackfire. This is an unfortunately common trend with typeclasses in general: Many classes try to pick one-size-fits-alldefaults that don’t work well in certain scenarios, but because Haskell allows specifying only one default per method,
if the provided default doesn’t work for a programmer’s use
case, then she is forced to implement her own implementations by hand.
In this section, we continue the trend of generalizing defaults by looking at another language extension that Deriving Via can substitute for: default signatures. Default signatures (a slight generalization of default implementations)can eliminate large classes of boilerplate, but they too arelimited by the one-default-per-method restriction. Here, we
demonstrate how one can scrap uses of default signaturesin favor of Deriving Via, and show how Deriving Via canovercome the limitations of default signatures.
The typical use case for default signatures is when one
has a type class method that has a frequently used default
implementation at a constrained type. For instance, consider
a Pretty class with a method pPrint for pretty-printing data:

instance Applicative ((->) r) wherepure = const(<*>) f g x = f x (g x)f <* _ = f_ *> g = g

Note that we had to explicitly define (<*) and (*>), as thedefault implementations would not have been as efficient.
But (->) is not the only type for which this trick works—
it also works for any data type that is isomorphic to (->)
r (for some r). These function-like types are characterized
by the Representable type class:class Functor f => Representable f wheretype Rep f
index :: f a -> (Rep f -> a)tabulate :: (Rep f -> a) -> f a

810
811

f <* _ = f_ *> g = g

829

794
795

826

Now, instead of having to manually override (<*) and (*>)
to get the desired performance, one can accomplish this ina more straightforward fashion by using Deriving Via:

779
780

f <*> g = tabulate (index f <*> index g)

838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865

class Pretty a where
pPrint :: a -> Doc

This is a good deal more abstract than (->) r, so it can be
helpful to see how Representable works for (->) r itself:

837

866
867

Coming up with Pretty instances for the vast majority ofdata types is repetitive and tedious, so a common pattern isto abstract away this tedium using generic programming libraries, such as those found in GHC.Generics [10] or genericssop [4]. For example, using GHC.Generics, we can define

instance Representable ((->) r) wheretype Rep ((->) r) = r
index f = ftabulate f = f

With Representable, we can codify the Applicative shortcutfor (<*) and (*>) with a suitable newtype:genericPPrint ::(Generic a, GPretty (Rep a)) => a -> Docnewtype WrapRep f a = WrapRep (f a)deriving (Functor, Representable)

The details of how Generic, GPretty, and Rep work are notimportant to understanding the example. What is importantis to note that we cannot just add

instance Representable f=> Applicative (WrapRep f) wherepure = tabulate . pure

pPrint = genericPPrint
8

868
869
870
871
872
873
874
875
876
877
878
879

2018-04-01 14:57. Page 8 of 1–12.

880

Deriving Via
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930

as a conventional default implementation to the Pretty class,because it does not typecheck due to the extra constraints.
Before the advent of default signatures, one had to workaround this by defining pPrint to be genericPPrint in every
Pretty instance, as in the examples below:

class Pretty a where
pPrint :: a -> Doc
default pPrint ::(Generic a, GPretty (Rep a)) => a -> Doc
pPrint = genericPPrint

Now, if any instances of Pretty are given without an explicit
definition of pPrint, the default implementation is used. For
this to typecheck, the data type a used in the instance mustsatisfy the constraints (Generic a, GPretty (Rep a)). Thus,
we can reduce the instances above to just

4.3

935

2018-04-01 14:57. Page 9 of 1–12.

Deriving via isomorphisms

All of the examples presented thus far in the paper rely onderiving through data types that have the same runtime representation as the original data type. In the following, however, we point out that—perhaps surprisingly—we can also
derive through data types that are isomorphic, not just representationally equal. To accomplish this feat, we rely ontechniques from generic programming.
Let us go back to QuickCheck (as in Section 2) once moreand consider the data type

Although default signatures remove the need for manyoccurrences of boilerplate code, it also imposes a significant
limitation: every type class method can have at most one
default implementation. As a result, default signatures effectively endorse one default implementation as the canonicalone. But in many scenarios, there is far more than just one
way to do something. Our pPrint example is no exception.
Instead of genericPPrint, one might one to:• Leverage a Show-based default implementation insteadof a Generic-based one,
• Use a different generic programming library, such as
generics-sop, instead of GHC.Generics, or• Use a tweaked version of genericPPrint which displays extra debugging information.
All of these are perfectly reasonable choices a programmer might want to make, but alas, GHC only lets type classes
bless each method with one default.
Fortunately, Deriving Via provides a convenient way of
encoding default implementations with the ability to toggle
between different choices: newtypes! For instance, we cancodify two different approaches to implementing pPrint asfollows:

934

937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960

instance Pretty Bool
instance Pretty a => Pretty (Maybe a)

instance (Generic a, GPretty (Rep a))=> Pretty (GenericPPrint a) where
pPrint (GenericPPrint x) = genericPPrint x

instance Show a => Pretty (ShowPPrint a) where
pPrint (ShowPPrint x) = stringToDoc (show x)

We have seen how Deriving Via makes it quite simple to givenames to particular defaults, and how toggling between defaults is a matter of choosing a name. In light of this, we
believe that many current uses of default signatures ought
to be removed entirely and replaced with the Deriving Viabased idiom presented in this section. This avoids the needto bless one particular default, and forces programmers toconsider which default is best suited to their use case, instead of blindly trusting the type class’s blessed default toalways do the right thing.
An additional advantage is that it allows decoupling thedefinition of such defaults from the site of the class definition. Hence, if a package author is hesitant to add a default because that might incur an unwanted additional dependency, nothing is lost, and the default can simply be
added in a separate package.

To avoid this repetition, default signatures allow one to provide a default implementation of a type class method using
additional constraints on the method’s type. For example:

932

936

deriving Pretty via (GenericPPrint DataType1)
deriving Pretty via (ShowPPrint
DataType2)

instance Pretty a => Pretty (Maybe a) where
pPrint = genericPPrint

newtype GenericPPrint a = GenericPPrint a

newtype ShowPPrint a = ShowPPrint aWith these newtypes in hand, choosing between them is assimple as changing a single type:instance Pretty Bool where
pPrint = genericPPrint

931
933

Submitted to Haskell, 09/2018, St. Louis, MO, USA

data Track = Track Title Duration

962
963
964
965
966
967
968
969
970
971
972

for which we would like to define an Arbitrary instance. Let
us further assume that we already have Arbitrary instances
for both Title and Duration.
The QuickCheck library defines an instance for pairs, so
we could generate values of type (Title, Duration), and in
essence, this is exactly what we want. But unfortunately, thetwo types are not inter-Coercible, even though they are isomorphic4 .
However, we can exploit the isomorphism and still get an
instance for free, and the technique we apply is quite widely
applicable in similar situations. As a first step, we declare anewtype to capture that one type is isomorphic to another:973

newtype SameRepAs a b = SameRepAs a

985

4 Isomorphic in the sense that we can define a function from Track to(Title, Duration) and vice versa. Depending on the class we want to derive,

sometimes an even weaker relationship between the types is sufficient, but
we will focus on the case of isomorphism here for reasons of space.
9

961

974
975
976
977
978
979
980
981
982
983
984
986
987
988
989
990

Submitted to Haskell, 09/2018, St. Louis, MO, USA
991
992
993
994
995
996
997
998
999

Baldur Blöndal, Andres Löh, and Ryan Scott

Note that the idea here is that a and b are isomorphic insome sense, but only a is used as the value of the type. So
SameRepAs a b is inter-Coercible with a.
We choose to witness an isomorphism between the two
types via their generic representations: if two types haveinter-Coercible generic representations, we can transformback and forth using the from and to methods of the Genericclass from GHC.Generics [10]. We can use this to define asuitable Arbitrary instance for SameRepAs:

Now, if we have a data type with a Monad instance, we cansimply derive the corresponding Functor and Applicative
instances by referring to FromMonad:data Stream a b = Done b | Yield a (Stream a b)
deriving (Functor, Applicative)
via (FromMonad (Stream a))
instance Monad (Stream a) wherereturn = Done

1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018

Yield a k >>= f = Yield a (k >>= f)Done b >>= f= f b

instance( Generic a, Generic b, Arbitrary b, Coercible (Rep a ()) (Rep b ()), Arbitrary b) => Arbitrary (a ‘SameRepAs‘ b) where
arbitrary = SameRepAs . coerceViaRep <$> arbitrary
where
coerceViaRep :: b -> acoerceViaRep =to . (coerce :: Rep b () -> Rep a ()) . from

One potentially problematic aspect remains. Another proposal [11] has been put forth (but has not been implemented,
as of now) to remove the return method from the Monad classand make it a synonym for pure from Applicative. The argument is that return is redundant, given that pure does thesame thing with a more general type signature. All otherprior discussion about the proposal aside, it should be notedthat removing return from the Monad class would prevent
FromMonad from working, as then Monad instances would nothave any way to define pure. 5

Here, we first use arbitrary to give us a generator of typeGen b, then coerce this via the generic representations into
an arbitrary value of type Gen a.
Finally, we can use the following deriving declarations
for Track to obtain the desired Arbitrary instance:

4.5

Not only can Deriving Via quickly procure type class instances, in some cases, it can eliminate the need for certain instances altogether. Haskell programmers often wantto avoid orphan instances: instances defined in a separate
module from both the type class and data types being used.
Sometimes, however, it’s quite tempting to reach for orphan
instances, as in the following example adapted from a blog
post by Gonzalez [9]:deriving Genericderiving Arbitrary
via (Track ‘SameRepAs‘ (String, Duration))

1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045

With this technique, we can significantly expand the “equivalence classes” of data types that can be used when picking
suitable types to derive through.
4.4

Avoiding orphan instances

newtype Plugin = Plugin (IO (String -> IO ()))
deriving SemigroupRetrofitting superclasses

On occasion, the need arises to retrofit an existing type classwith a superclass, such as when Monad was changed to have
Applicative as a superclass (which in turn has Functor as asuperclass).
One disadvantage of such a change is that if the primary
goal is to define the Monad instance for a type, one now has towrite two additional instances, for Functor and Applicative,
even though these instances are actually determined by the
Monad instance.
With Deriving Via, we can capture this fact as a newtype,thereby making the process of defining such instances muchless tedious:

1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080

In order for this derived Semigroup instance to typecheck,there must be a Semigroup instance for IO available. Suppose
for a moment that there was no such instance for IO. How
could one work around this issue?

newtype FromMonad m a = FromMonad (m a)deriving Monadinstance Monad m => Functor (FromMonad m) wherefmap = liftM
instance Monad m => Applicative (FromMonad m) wherepure = return(<*>) = ap
10

1081
1082
1083
1084

• One could patch the base library to add the instancefor IO. But given base’s slow release cycle, it would bea while before one could actually use this instance.
• Write an orphan instance for IO. This works, but is undesirable, as now anyone who uses Plugin must incur
a possibly unwanted orphan instance.

1085

Luckily, Deriving Via presents a more convenient third
option: re-use a Semigroup instance from another data type.
Recall the App data type from Section 1.2 that lets us definea Semigroup instance by lifting through an Applicative instance. As luck would have it, IO already has an Applicative

1091

5 A similar, yet somewhat weaker, argument applies to suggested changes

1097

to relax the constraints of liftM and ap to merely Applicative and to change
their definitions to be identical to fmap and (<*>), respectively.

1098

2018-04-01 14:57. Page 10 of 1–12.

1100

1086
1087
1088
1089
1090
1092
1093
1094
1095
1096

1099

Deriving Via
1101
1102

Submitted to Haskell, 09/2018, St. Louis, MO, USA

instance, so we can derive the desired Monoid instance for
Plugin like so:

it much simpler to describe and implement. Finally, the problem which explicit dictionaries aims to solve—resolving ambiguity in implicit arguments—almost never arises in Deriving Via, as the programmer must specify all the types involved in the process.

1103
1104
1105
1106
1107
1108
1109
1110
1111

newtype Plugin = Plugin (IO (String -> IO ()))
deriving Semigroupvia (App IO (String -> App IO ()))

1114

Note that we have to use App twice in the via type, corresponding to the two occurences of IO in the Plugin type.
This is ok, because App IO has the same representation as IO.
As desired, we completely bypass the need for a Semigroup
instance for IO.

1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130

5

Related Ideas

ML functors

Explicit dictionary passing

1154

The power and flexibility of Deriving Via is largely due to
GHC’s ability to take a class method of a particular type andmassage it into a method of a different type. This process isalmost completely abstracted away from the user, however.
A user only needs to specify the types involved, and GHC
will handle the rest behind the scenes.
An alternative approach, which would put more power
into the hands of the programmer, is to permit the ability
to explicitly construct and pass the normally implicit dictionary arguments corresponding to type class instances [5].
Unlike in Deriving Via, where going between class instances
is a process that is carefully guided by the compiler, permitting explicit dictionary arguments would allow users to actually coerce concrete instance values and pass them aroundas first-class values. In this sense, explicit dictionary arguments could be thought of as a further generalization of thetechnique that Deriving Via uses.
However, explicit dictionary arguments are a considerable extension of the language and its type system, and wefeel that to be too large a hammer for the nail we are trying
to hit. Deriving Via works by means of a simple desugaringof code with some light typechecking on top, which makes

1155

2018-04-01 14:57. Page 11 of 1–12.

1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153

Quality of error messages

The nice thing about deriving is that when it works, it tends
to work extremely well. When it doesn’t work, however, it
can be challenging to formulate an error message that adequately explains what went wrong. The fundamental issue
is that error messages resulting from uses of deriving areusually rooted in generated code, and pointing to code thatthe user didn’t write in error messages can sometimes leadto a confusing debugging experience.
Fortunately, we have found in our experience that thequality of Deriving Via-related error messages is overall on
the positive side. GHC has already invested significant effort into making type errors involving Coercible to be easily digestible by programmers, so Deriving Via benefits fromthis work. For instance, if one inadvertently tries to derivethrough a type that is not inter-Coercible with the original
data type, such as in the following example:

Languages in the ML family, such as Standard ML or OCaml,
provide functors, which are a feature of the module systemthat allows writing functions from modules of one signature
to modules of another signature. In terms of functionality,
functors somewhat closely resemble Deriving Via, as functors allow “lifting” of code into the module language much
like Deriving Via allows lifting of code into GHC’s deriving
construct.
5.2

1158
1159
1160
1162
1163
1164
1165
1166
1167
1168
1169

6.1

1131
1132

Current status

We have implemented Deriving Via within GHC. Our implementation also interacts well with other GHC featuresthat were not covered in this paper, such as kind polymorphism [12], StandaloneDeriving, and type classes with associated type families [2]. However, there are still challenges
remaining, which we will describe in this section.

We have demonstrated in the previous section that Deriving
Via is an extremely versatile technique, and can be used totackle a wide variety of problems. Deriving Via also bears aresemblance to other distinct language features, such as ML
functors and explicit dictionary passing, so in this section,
we present an overview of their similarities and differences.
5.1

1157

1161

6

1112
1113

1156

newtype UhOh = UhOh Char deriving Ord via Int

1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188

Then GHC will tell you exactly that, in plain language:

1189• Couldn’t match representation of type Char with that of Int
arising from the coercion of the method compare
from type ‘Int -> Int -> Ordering’to type ‘UhOh -> UhOh -> Ordering’

1190

That is not to say that every error message is this straightforward. There is are some scenarios that produce less-thanideal errors, such as this:newtype Foo a = Foo (Maybe a) deriving Ord via a

1191
1192
1193
1194
1195
1196
1197
1198
1199

• Occurs check: cannot construct the infinite type: a ~ Maybe aarising from the coercion of the method ‘compare’
from type ‘a -> a -> Ordering’to type ‘Foo a -> Foo a -> Ordering’

The real problem is that a and Maybe a do not have thesame representation at runtime, but the error does not makethis obvious. It is possible that one could add an ad hoc checkfor this class of programs, but there are likely many more
tricky corner cases lurking around the corner, given that one
can put anything after via.
11

1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210

Submitted to Haskell, 09/2018, St. Louis, MO, USA
1211
1212
1213
1214
1215
1216
1217

Baldur Blöndal, Andres Löh, and Ryan Scott

We do not propose a solution to this problem here, butinstead note that issues with Deriving Via error quality are
ultimately issues with coerce error quality, given that theerror messages are a result of coerce failing to typecheck.
It is likely that investing more effort into making coerce’s
error messages easier to understand would benefit Deriving
Via as well.

instance declarations that occur in the wild can actually bederived by using a pattern that deserves to be known andnamed, and that instances defined manually should become
an anti-pattern in all but some rare situations.

1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238

6.2

Multi-Parameter Type Classes

GHC extends Haskell by permitting type classes with morethan one parameter. Multi-parameter type classes are extremely common in modern Haskell, to the point where weassumed the existence of them in Section 3.1.1 without further mention. However, multi-parameter type classes pose
an intriguing design question when combined with Deriving Via and StandaloneDeriving, another GHC feature whichallows one to write deriving declarations independently ofa data type.
For example, one can write the following instance using
StandaloneDeriving:

1240

However, the code it generates is somewhat surprising. Instead of reusing the Triple () () () instance in the derived
instance, it will attempt to reuse an instance for Triple A B(). This is because, by convention, StandaloneDeriving will
only ever coerce through the last argument of a class. Thatis because the standalone instance above would be the sameas if a user had written:

1241
1242
1243
1244
1245
1246
1247

newtype C = C () deriving (Triple A B) via ()

1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265

This consistency is perhaps a bit limiting in this context,
where we have multiple arguments to C that one could “derive through”. But it is not clear how GHC would figure out
which of these arguments to C should be derived through,as there seven different combinations to choose from! It is
possible that another syntax would need to be devised to allow users to specify which arguments should be coerced to
avoid this ambiguity.

7

1269

Acknowledgements

1271

We would like to thank Richard Eisenberg for his feedback
on Section 3.3, as well as the first author’s former colleagues
at Standard Chartered Bank for their feedback.

1272

References

newtype A = A ()newtype B = B ()newtype C = C ()deriving via () instance Triple A B C

1268

1273
1274
1275

class Triple a b c where
triple :: (a, b, c)
instance Triple () () () where
triple = ((), (), ())

1239

1267

1270

1218
1219

1266

Conclusions

In this paper, we have introduced the Deriving Via language
extension, explained how it is implemented, and shown awide variety of use cases. We believe that Deriving Via has
the potential to dramatically change the way we write instances, as it encourages giving names to recurring patterns
and reusing them where needed. It is our feeling that most
12[1] Joachim Breitner, Richard A. Eisenberg, Simon Peyton Jones, andStephanie Weirich. 2014. Safe Zero-cost Coercions for Haskell. In Proceedings of the 19th ACM SIGPLAN International Conference on Functional Programming (ICFP ’14). ACM, New York, NY, USA, 189–202.
https://doi.org/10.1145/2628136.2628141[2] Manuel M. T. Chakravarty, Gabriele Keller, and Simon Peyton Jones.
2005. Associated Type Synonyms. In Proceedings of the Tenth ACM
SIGPLAN International Conference on Functional Programming (ICFP’05). ACM, New York, NY, USA, 241–253. https://doi.org/10.1145/
1086365.1086397[3] Koen Claessen and John Hughes. 2000. QuickCheck: A Lightweight
Tool for Random Testing of Haskell Programs. In Proceedings of theFifth ACM SIGPLAN International Conference on Functional Programming (ICFP ’00). ACM, New York, NY, USA, 268–279. https://doi.org/
10.1145/351240.351266[4] Edsko de Vries and Andres Löh. 2014. True Sums of Products. In Proceedings of the 10th ACM SIGPLAN Workshop on Generic Programming(WGP ’14). ACM, New York, NY, USA, 83–94. https://doi.org/10.1145/
2633628.2633634[5] Atze Dijkstra and S. Doaitse Swierstra. 2005. Making implicit parameters explicit. Technical Report UU-CS-2005-032. Department
of Information and Computing Sciences, Utrecht University. http://www.cs.uu.nl/research/techreps/repo/CS-2005/2005-032.pdf[6] Richard A. Eisenberg, Stephanie Weirich, and Hamidhasan G. Ahmed.
2016. Visible Type Application. In Proceedings of the 25th European
Symposium on Programming Languages and Systems - Volume 9632.
Springer-Verlag New York, Inc., New York, NY, USA, 229–254. https://doi.org/10.1007/978-3-662-49498-1_10[7] Conal Elliott. 2009. applicative-numbers: Applicative-based nuhttps://hackage.haskell.org/package/
meric instances.
(2009).
applicative-numbers[8] Jeremy Gibbons and Bruno c. d. s. Oliveira. 2009. The Essence of
the Iterator Pattern. J. Funct. Program. 19, 3-4 (July 2009), 377–402.
https://doi.org/10.1017/S0956796809007291[9] Gabriel Gonzalez. 2014. Equational reasoning at scale. (Jul 2014). http://www.haskellforall.com/2014/07/equational-reasoning-at-scale.
html[10] José Pedro Magalhães, Atze Dijkstra, Johan Jeuring, and Andres Löh.
2010. A Generic Deriving Mechanism for Haskell. In Proceedings of
the Third ACM Haskell Symposium on Haskell (Haskell ’10). ACM, New
York, NY, USA, 37–48. https://doi.org/10.1145/1863523.1863529[11] Herbert V. Riedel and David Luposchainsky. 2015.
Monad ofno return Proposal (MRP): Moving return out of Monad. (Sep2015). https://mail.haskell.org/pipermail/libraries/2015-September/
026121.html[12] Brent A. Yorgey, Stephanie Weirich, Julien Cretin, Simon Peyton Jones, Dimitrios Vytiniotis, and José Pedro Magalhães. 2012. Giving Haskell a Promotion. In Proceedings of the 8th ACM SIGPLAN
Workshop on Types in Language Design and Implementation (TLDI ’12).
ACM, New York, NY, USA, 53–66. https://doi.org/10.1145/2103786.
2103795
2018-04-01 14:57. Page 12 of 1–12.

1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320