Hierarchy in Generic Programming Libraries

Conference paper

DOI: 10.1007/978-3-319-19797-5_5

Part of the Lecture Notes in Computer Science book series (LNCS, volume 9129)
Cite this paper as:
Magalhães J.P., Löh A. (2015) Hierarchy in Generic Programming Libraries. In: Hinze R., Voigtländer J. (eds) Mathematics of Program Construction. MPC 2015. Lecture Notes in Computer Science, vol 9129. Springer, Cham

Abstract

Generic programming (GP) is a form of abstraction in programming languages that serves to reduce code duplication by exploiting the regular structure of algebraic datatypes. Several different approaches to GP in Haskell have surfaced, giving rise to the problem of code duplication across GP libraries. Given the original goals of GP, this is a rather unfortunate turn of events. Fortunately, we can convert between the different representations of each approach, which allows us to “borrow” generic functions from different approaches, avoiding the need to reimplement every generic function in every single GP library.

In previous work we have shown how existing GP libraries relate to each other. In this paper we go one step further and advocate “hierarchical GP”: through proper design of different GP approaches, each library can fit neatly in a hierarchy, greatly minimizing the amount of supporting infrastructure necessary for each approach, and allowing each library to be specific and concise, while eliminating code duplication overall. We introduce a new library for GP in Haskell intended to sit at the top of the “GP hierarchy”. This library contains a lot of structural information, and is not intended to be used directly. Instead, it is a good starting point for generating generic representations for other libraries. This approach is also suitable for being the only library with native compiler support; all other approaches can be obtained from this one by simple conversion of representations in plain Haskell code.

1 Introduction

Generic programs are concise, abstract, and reusable. They allow one single definition to be used for many kinds of data, existing and to come. For example, parsing and pretty-printing, (de-)serialisation, test data generation, and traversals can all be implemented generically, freeing the programmer to implement only datatype-specific functionality.

Given its power, it’s no surprise that GP approaches abound: including pre-processors, template-based approaches, language extensions, and libraries, there are well over 15 different approaches to GP in Haskell (Magalhães 2012, Chap. 8). This abundance is partly caused by the lack of a clearly superior approach; each approach has its strengths and weaknesses, uses different implementation mechanisms, a different generic view (Holdermans et al. 2006) (i.e. a different representation of datatypes), or focuses on solving a particular task. Their number and variety makes comparisons difficult, and can make prospective GP users struggle even before actually writing a generic program, since first they have to choose a library that is appropriate for their needs.

We have previously investigated how to model and formally relate some Haskell GP libraries using Agda (Magalhães and Löh 2012), and concluded that some approaches clearly subsume others. Afterwards, we have shown how to reduce code duplication in GP libraries in Haskell by converting between the representations of different approaches, in what we dubbed “generic programming” (Magalhães and Löh 2014).

To help understand the benefits of our work, it is important to distinguish three kinds of users of GP:
  • Compiler writer. As far as GP goes, the compiler writer is concerned with which approach(es) are natively supported by the compiler. At the moment, in the main Haskell compiler GHC, both syb (Lämmel and Peyton Jones 2003, 2004) and generic-deriving (Magalhães et al. 2010) are natively supported. This means that the compiler can automatically generate the necessary generic representations to enable using these approaches. A quick analysis reveals that in GHC there are about 226 lines of code for supporting syb, and 884 for generic-deriving.

  • GP library author. The library author maintains one or more GP libraries, and possibly creates new ones. Since most approaches are not natively supported, the library author has to deal with generating generic representations for their library (or accept that no end user will use this library, given the amount of boilerplate code they would have to write). Typically, the library author will rely on Template Haskell (TH, Sheard and Peyton Jones 2002) for this task. Given that TH handles code generation at the AST level (thus syntactic), this is not a pleasant task. Furthermore, the TH API changes as frequently as the compiler, so the library author currently has to update its supporting code frequently.

  • End users. The end users know nothing about compiler support, and ideally not even about library-specific detail. They simply want to use a particular generic function on their data, with minimum overhead.

While Magalhães and Löh (2014) focused mostly on improving the life of the end user, the work we describe in this paper brings more advantages to the compiler writer and GP library author. We elaborate further on the idea of generic generic programming by highlighting the importance of hierarchy in GP approaches. Each new GP library should only be a piece of the puzzle, specialising in one task, or exploring a new generic representation, but obtaining most of its supporting infrastructure (such as example generic functions) from already existing approaches. To facilitate this, we introduce a new GP library, structured, which we use as a core to derive representations for other GP libraries. Defining a new library does not mean introducing a lot of new supporting code. In fact, we do not even think many generic functions will ever be defined in our new library, as its representation is verbose (albeit precise). Instead, we use it to guide conversion efforts, as a highly structured approach provides a good foundation to build upon.

From the compiler writer’s perspective, this library would be the only one needing compiler support; support for other libraries follows automatically from conversions that are defined in plain Haskell, not through more compiler extensions. Since structured has only one representation, as opposed to generic-deriving ’s two representations, we believe that supporting it in GHC would require fewer lines of code than the existing support for generic-deriving. The code for supporting generic-deriving and syb could then be removed, as those representations can be obtained from structured.

Should we ever find that we need more information in structured to support converting to other libraries, we can extend it without changing any of the other libraries. This obviates the need to change the compiler for supporting or adapting GP approaches. It also simplifies the life of the GP library author, who no longer needs to rely on meta-programming tools.

Specifically, our contributions are the following:
  • A new library for GP, structured, which properly encodes the nesting of the different structures within a datatype representation (Sect. 2). We propose this library as a foundation for GP in Haskell, from which many other approaches can be derived. It is designed to be highly expressive and easily extensible, serving as a back-end for more stable and established GP libraries.

  • We show how structured can provide GP library authors with different views of the nesting of constructors and fields (Sect. 3). Different generic functions prefer different balancings, which we provide through automatic conversion (instead of duplicated encodings differing only in the balancing).

  • We position structured at the top of GP hierarchy by showing how to derive generic-deriving (Magalhães et al. 2010) representations from it (Sect. 4). This also shows how structured unifies the two generic representations of generic-deriving. Representations for other libraries (regular (Van Noort et al. 2008), multirec (Rodriguez Yakushev et al. 2009), and syb (Lämmel and Peyton Jones 2003, 2004)) can then be obtained from generic-deriving.

Figure 1 shows an overview of the hierarchical relationships between different libraries for GP in Haskell. In this paper, we introduce structured and its conversion to generic-deriving. We refer the reader to Magalhães and Löh (2014) for the conversions from generic-deriving.
Fig. 1.

Hierarchical relationship between GP approaches.

1.1 Notation

In order to avoid syntactic clutter and to help the reader, we adopt a liberal Haskell notation in this paper. We assume the existence of a kind keyword, which allows us to define kinds directly. These kinds behave as if they had arisen from datatype promotion (Yorgey et al. 2012), except that they do not define a datatype and constructors. We omit the keywords \(\mathbf {type}\;\mathbf{family }\) and \(\mathbf {type}\;\mathbf {instance}\) entirely, making type-level functions look like their value-level counterparts. We colour constructors in Open image in new window, types in Open image in new window, and kinds in Open image in new window. In case the colours cannot be seen, the “level” of an expression is clear from the context. Additionally, we use Greek letters for type variables, apart from Open image in new window, which is reserved for kind variables.

This syntactic sugar is only for presentation purposes. An executable version of the code, which compiles with GHC 7.8.3, is available at http://dreixel.net/research/code/hgp.zip. We rely on many GHC-specific extensions to Haskell, which are essential for our development. Due to space constraints we cannot explain them all in detail, but we try to point out relevant features as we use them.

1.2 Structure of the Paper

The remainder of this paper is structured as follows. We first introduce the structured library (Sect. 2). We then see how to obtain views with different balancings of the constructors and constructor arguments (Sect. 3). Afterwards, we see how to obtain generic-deriving from structured (Sect. 4). We conclude with a discussion in Sect. 5.

2 A Highly Structured GP Library

Our efforts of modularising a hierarchy of GP libraries stem from a structured library intended to sit at the top of the hierarchy. Our goal is to define a library that is highly expressive, without having to worry about convenience of use. Users requiring the level of detail given by structured can use it directly, but we expect most to prefer using any of the other, already existing GP libraries. Usability is not our concern here; expressiveness is. Stability is also not guaranteed; we might extend our library as needed to support converting to more approaches. Previous approaches had to find a careful balance between having too little information in the generic representation, resulting in a library with poor expressiveness, and having too much information, resulting in a verbose and hard to use approach. Given our modular approach, we are free from these concerns.

The design of structured we give here is preliminary; we plan to extend it in the future in order to support representing more datatypes. In fact, as the type language of GHC grows with new extensions, we expect to keep changing structured frequently. However, the simple fact that we introduce structured, and show how to use it for decoupling generic-deriving from the compiler, improves the current status quo. In particular, if structured is supported through automatic deriving in GHC, no more compiler support is required for the other libraries. Using this library also improves modularity; it can be updated or extended more freely, since supporting the other libraries requires only updating the conversions, not the compiler itself (for the automatic derivation of instances).

In our previous work (Magalhães and Löh 2014) we have shown how to obtain the representation of many libraries from generic-deriving. Given that structured, at the time of writing, serves only to provide a conversion to generic-deriving, the reader might think that it is unnecessary. We have several reasons justifying structured, however:
  • The generic-deriving library has been around for some time now, and lots of code using it has been written. Sticking to generic-deriving as a foundational approach would force us to break lots of code whenever we would need to update it in order to support new approaches, or to add functionality.

  • By introducing structured, we can decouple most of generic-deriving from the compiler. In particular, the mechanism for deriving generic-deriving instances can be simplified, because generic-deriving has two representations (which need to be derived separately), while structured has only one (from which we can derive both generic-deriving representations).

  • Being a new approach designed to be an internal representation, structured can be changed without worrying too much about breaking existing code; the only code that would need to be adapted is that for the conversion to generic-deriving. This is plain Haskell code, not compiler code or TH, so it’s easier to update and maintain.

We now proceed to describe the representation types in structured, their interpretation as values, and the conversion between user datatypes and their generic representations, together with example encodings.

2.1 Universe

The structure used to encode datatypes in a GP approach is called its universe (Morris 2007). The universe of structured, for now, is similar to that of generic-deriving (Magalhães 2012, Chap. 11), as it supports abstraction over at most one datatype parameter. We choose to restrict this parameter to be the last of the datatype, and only if its kind is Open image in new window. This is a pragmatic decision: many generic functions, such as \({ map }\), require abstraction over one parameter, but comparatively few require abstraction over more than one parameter. For example, in the type Open image in new window, the parameter is Open image in new window, and in Open image in new window, it is Open image in new window. The differences to generic-deriving lay in the explicit hierarchy of data, constructor, and field, and the absence of two separate ways of encoding constructor arguments. It might seem unsatisfactory that we do not improve on the limitations of generic-deriving with regards to datatype parameters, but that is secondary to our goal in this paper (and it would be easy to implement support for multiple parameters in structured following the strategy of Magalhães (2014)). Furthermore, structured can easily be improved later, keeping the other libraries unchanged, and adapting only the conversions if necessary.

Datatypes are represented as types of kindOpen image in new window. We define new kinds, whose types are not inhabited by values: only types of kind Open image in new window are inhabited by values. These kinds can be thought of as datatypes, but their “constructors” will be used as indices of a GADT (Schrijvers et al. 2009) to construct values with a specific structure.

Datatypes have some metadata, such as their name, and contain constructors. Constructors have their own metadata, and contain fields. Finally, each field can have metadata, and contain a value of some structure:

We use a binary leaf tree to encode the structure of the constructors in a datatype, and the fields in a constructor. Typically lists are used, but we will see in Sect. 3 that it is convenient to encode the structure as a tree, as we can change the way it is balanced for good effect.

The metadata we store is unsurprising:

It is important to note that this metadata is encoded at the type level. In particular, we have type-level strings and natural numbers. We make use of the current (in GHC 7.8.3) implementation of type-level strings, whose kind is Open image in new window.

Finally, Open image in new window describes the structure of constructor arguments:

A field can either be a datatype parameter other than the last Open image in new window, an occurrence of a different datatype of kind Open image in new windowOpen image in new window, some other type (such as an application of a type variable, encoded with Open image in new window), a datatype of kind (at least) Open image in new window (Open image in new window), which can be either the same type we’re encoding (Open image in new window) or a different one (Open image in new window), the (last) parameter of the datatype (Open image in new window), or a composition of a type constructor with another argument (Open image in new window).

The representation is best understood in terms of an example. Consider the following datatype:
We first show the encoding of each of the four constructor arguments: Open image in new window is a datatype of kind Open image in new window, so it’s encoded with Open image in new window; Open image in new window depends on the instantiation of Open image in new window, so it’s encoded with Open image in new window; Open image in new window is a composition between the list functor and the datatype we’re defining, so it’s encoded with Open image in new window; finally, Open image in new window is the parameter we abstract over, so it’s encoded with Open image in new window:
The entire representation consists of wrapping of appropriate meta-data around the representation for constructor arguments:

2.2 Interpretation

The interpretation of the universe defines the structure of the values that inhabit the datatype representation. Datatype representations will be types of kind Open image in new window. We use a data family (Schrijvers et al. 2008) Open image in new window to encode the interpretation of the universe of structured:

Its first argument is written infix, and the second postfix. Its kind, Open image in new window, is overly general in Open image in new window; we will only instantiate Open image in new window to the types of the universe shown before, and prevent further instantiation by not exporting the family Open image in new window (effectively making it a closed data family). The second argument of Open image in new window, of kind Open image in new window, is the parameter of the datatype which we abstract over.

The top-level inhabitant of a datatype representation is a constructor Open image in new window, which serves only as a proxy to store the datatype metadata in its type:
Constructors, on the other hand, are part of a Open image in new window structure, so they can be on the left (Open image in new window) or right (Open image in new window) side of a branch, or be a leaf. As a leaf, they contain the meta-information for the constructor that follows (Open image in new window):
Constructor fields are similar, except that they might be empty (Open image in new window, as some constructors have no arguments), leaves contain fields (Open image in new window), and branches are inhabited by the arguments of both sides (Open image in new window):
We’re left with constructor arguments. We encode base types with Open image in new window, datatype occurrences with Open image in new window, the parameter with Open image in new window, and composition with Open image in new window:

2.3 Conversion to and from User Datatypes

Having seen the generic universe and its interpretation, we need to provide a mechanism to mediate between user datatypes and our generic representation. We use a type class for this purpose:

In the Open image in new window class, the type family Open image in new window encodes the generic representation associated with user datatype Open image in new window, and Open image in new window extracts the last parameter from the datatype. In case the datatype is of kind Open image in new window, we use Open image in new window; a type family default allows us to leave the type instance empty for types of kind Open image in new window. The conversion functions \({ from }\) and \({ to }\) perform the conversion between the user datatype values and the interpretation of its generic representation.

2.4 Example Datatype Encodings

We now show two complete examples of how user datatypes are encoded in structured. Naturally, users should never have to define these manually; a release version of structured would be incorporated in the compiler, allowing automatic derivation of Open image in new window instances.

Choice. The first datatype we encode represents a choice between four options:
Open image in new window is a datatype of kind Open image in new window, so we do not need to provide a type instance for Open image in new window. The encoding, albeit verbose, is straightforward:

We use a balanced tree structure for the constructors; in Sect. 3 we will see how this can be changed without any user effort.

Lists. Standard Haskell lists are a type of kind Open image in new window. We break down its type representation into smaller fragments using type synonyms, to ease comprehension. The encoding of the metadata of each constructor and the two arguments to Open image in new window follows:

The encoding of the first argument to Open image in new window, Open image in new window, states that there is no record selector, and that the argument is the parameter Open image in new window. The encoding of the second argument, Open image in new window, is a recursive occurrence of the same datatype being defined (Open image in new window).

With these synonyms in place, we can show the complete Open image in new window instance for lists:

The type function Open image in new window extracts the parameter Open image in new window from Open image in new window; the \({ from }\) and \({ to }\) conversion functions are unsurprising.

3 Left- and Right-Biased Encodings

The structured library uses trees to store the constructors inside a datatype, as well as the fields inside a constructor. So far we have kept these trees balanced, but other choices would be acceptable too. In fact, the balancing choice determines a generic view (Holdermans et al. 2006). Different balancings might be more convenient for certain generic functions. For example, if we are defining a binary encoding function, it is convenient to use the balanced encoding, as then we can easily minimise the number of bits used to encode a constructor. On the other hand, if we are defining a generic function that extracts the first argument to a constructor (if it exists), we would prefer using a right-nested view, as then we can simply pick the first argument on the left. Fortunately, we do not have to provide multiple representations to support this; we can automatically convert between different balancings. As an example, we see in this section how to convert from the (default) balanced encoding to a right-nested one.

This is the first conversion shown in this paper, and as such serves as an introduction to our conversions. Following the style of Magalhães and Löh (2014), we use a type family to adapt the representation, and a type-class to adapt the values. Since this conversion works at the top of the hierarchy (on structured), the new balancing persists in future conversions, so a generic function in generic-deriving could make use of a right-biased encoding.

3.1 Type Conversion

The essential part of the type conversion is a type function that performs one rotation to the right on a tree:
We then apply this rotation repeatedly at the top level until the tree contains a Open image in new window on the left subtree, and then proceed to rotate the right subtree:

The conversion for constructors (Open image in new window) and selectors (Open image in new window) differs only in the treatment for leaves, as the leaf of a selector is the stopping point of this transformation.

3.2 Value Conversion

The value-level conversion is witnessed by a type class:

We skip the definition of the instances, as they are mostly unsurprising and can be found in our code bundle.

3.3 Example

To test the conversion, we define a generic function that computes the depth of the encoding of a constructor:
We now have two ways of calling this function; one using the standard encoding, and other using the right-nested encoding obtained using Open image in new window:
Applying these two functions to the constructors of the Open image in new window datatype should give different results:

Indeed, \({ testCountSums }\) evaluates to \(([\mathrm {2},\mathrm {2},\mathrm {2},\mathrm {2}],[\mathrm {1},\mathrm {2},\mathrm {3},\mathrm {3}])\) as expected. As we’ve seen, not only can we obtain a different balancing without having to duplicate the representation, but we can also effortlessly apply the same generic function to differently-balanced encodings. Furthermore, the conversions shown in the coming sections automatically “inherit” the balancing chosen in structured, allowing us to provide representations with different balancings to the other GP libraries as well.

4 From structured to generic-deriving

In this section we show how to obtain generic-deriving representations from structured.

4.1 Encoding generic-deriving

The first step is to define generic-deriving. We could use its definition as implemented in the GHC.Generics module, but it seems more appropriate to at least make use of proper kinds. We thus redefine generic-deriving in this paper to bring it up to date with the most recent compiler functionality1. This is not essential for our conversions, and should be seen only as a small improvement. The type representation is similar to a collapsed version of structured, where all types inhabit a single kind Open image in new window:

Since many names are the same as those in structured, we use the “D” subscript for generic-deriving names. Open image in new window, Open image in new window, Open image in new window, Open image in new window, Open image in new window, and Open image in new window behave very much like the structuredOpen image in new window, Open image in new window, Open image in new window, Open image in new window, Open image in new window, and Open image in new window, respectively. The binary operators Open image in new window and Open image in new window are equivalent to Open image in new window, and Open image in new window encompasses structured ’s Open image in new window, Open image in new window, and Open image in new window.

Having seen the interpretation of structured, the interpretation of the generic-deriving universe is unsurprising:

The significant difference from structured is the relative lack of structure. The types (and kinds) do not prevent an Open image in new window from showing up under a Open image in new window, for example.

User datatypes are converted to the generic representation using two type classes:

Class Open image in new window is used for all supported datatypes, and encodes a simple view on the constructor arguments. For datatypes that abstract over (at least) one type parameter, an instance for Open image in new window is also required. The type representation in this instance encodes the more general view of constructor arguments (i.e. using Open image in new window, Open image in new window, and Open image in new window). Note that Open image in new window doesn’t currently have Open image in new window in GHC, but we think this is a (minor) improvement. Furthermore, the presence of a type family default makes it backwards-compatible.

Since these two classes represent essentially two different universes in generic-deriving, we need to define two distinct conversions from structured to generic-deriving.

4.2 To Open image in new window

The universe of structured has a detailed encoding of constructor arguments. However, many generic functions do not need such detailed information, and are simpler to write by giving a single case for constructor arguments (imagine, for example, a function that counts the number of arguments). For this purpose, generic-deriving states that representations from Open image in new window contain only the Open image in new window type at the arguments (so no Open image in new window, Open image in new window, and Open image in new window).

To derive Open image in new window instances from Open image in new window, we use the following instance:

In the remainder of this section, we explain the definition of Open image in new window, a type family that converts a representation of structured into one of generic-deriving, and the class Open image in new window, whose methods \({ s_{\rightarrow }g_{0} }\) and \({ s_{\leftarrow }g_{0} }\) perform the value-level conversion.

Type Representation Conversion. To convert between the type representations, we use a type family:

The kind of Open image in new window is overly polymorphic; its input is not any Open image in new window, but only the kinds that make up the structured universe. We could encode this by using multiple type families, one at each “level”. For simplicity, however, we use a single type family, which we instantiate only for the structured representation types.

The encoding of datatype meta-information is left unchanged:
We then proceed with the conversion of the constructors:
Again, the structure of the constructors and their meta-information is left unchanged. We proceed similarly for constructor fields:
Finally, we arrive at individual fields, where the interesting part of the conversion takes place:
Basically, all the information kept about the field is condensed into the first argument of Open image in new window. Composition requires special care, but gets similarly collapsed into a Open image in new window:

The auxiliary type family Open image in new window takes care of unwrapping the composition, and re-applying the type to its arguments.

Value Conversion. Having performed the type-level conversion, we have to convert the values in an equally type-directed fashion. We begin with datatypes:

As in the type conversion, we simply traverse the representation, and convert the constructors with another function. From here on, we omit the \({ s_{\leftarrow }g_{0} }\) direction, as it is entirely symmetrical.

Constructors and selectors simply traverse the meta-information:
Finally, at the argument level, we collapse everything into Open image in new window:
Again, for composition we need to unwrap the representation, removing all representation types within:

With all these instances in place, the Open image in new window shown at the beginning of this section takes care of converting to the simpler representation of generic-deriving without syntactic overhead. In particular, all generic functions defined over the Open image in new window class, such as \({ gshow }\) and \({ genum }\) from the generic-deriving package, are now available to all types in structured, such as Open image in new window and Open image in new window.

Example: Length. To test the conversion, we define a generic function in generic-deriving that computes the number of elements in a structure:

We omit the instances of Open image in new window as they are unsurprising: we traverse the representation until we reach the arguments, which are recursively counted and added.

While the Open image in new window class works on the generic representation, a user-facing class Open image in new window takes care of handling user-defined datatypes. We define a generic default which implements \({ gLength }\) generically:
Because of the generic default, instantiating Open image in new window to datatypes with a Open image in new window instance is very simple:

Recall, however, that in Sect. 2.4 we have given only Open image in new window instances for Open image in new window and Open image in new window, not Open image in new window. However, due to the Open image in new window instance of the beginning of this section, Open image in new window and Open image in new window automatically get a Open image in new window instance, which is being used here.

We can test that this function behaves as expected: \({ gLength }\;[\mathrm {0},\mathrm {1},\mathrm {2},\mathrm {3}]\) returns \(\mathrm {4}\), and Open image in new window returns \(\mathrm {0}\). And further: using our previous work (Magalhães and Löh 2014), we also gain all the functionality from other libraries, such as syb traversals or a zipper, for example.

4.3 To Open image in new window

Converting to Open image in new window is very similar, only that we preserve more structure. The conversion is similarly performed by two components.

Type Representation Conversion. We define a type family to perform the conversion of the type representation:
The type instances for the datatype, constructors, and fields behave exactly like in Open image in new window, so we skip straight to the constructor arguments, which are simple to handle because they are in one-to-one correspondence:
Value Conversion. The value-level conversion is as trivial as the type-level conversion, so we omit it from the paper. It is witnessed by a poly-kinded type class:

Again, we only give instances of Open image in new window for the representation types of structured.

Using this class we can give instances for each user datatype that we want to convert. For example, the list datatype (instantiated in structured in Sect. 2.4) can be transported to generic-deriving with the following instance:

We use Open image in new window because we need to instantiate the list with some parameter. Any parameter will do, because we know that Open image in new window. However, this means that, unlike in Sect. 4.2, we cannot give a single instance of the form Open image in new window. The reason for this is the disparity between the kinds of the two classes involved; Open image in new window only mentions the parameter Open image in new window in the signature of its methods, where it’s impossible to state that said Open image in new window is the same as in the instance head (Open image in new window).

This is not a major issue, however, because Open image in new window instances are currently derived by the compiler. If these instances were to be replaced by conversions from Open image in new window, the behaviour of Open image in new window would change to mean “derive Open image in new window, and define a trivial Open image in new window instance”.

With the instance above, functionality defined in the generic-deriving package over the Open image in new window class, such as \({ gmap }\), is now available to Open image in new window.

5 Conclusion

Following the lines of generic generic programming, we’ve shown how to add another level of hierarchy to the current landscape of GP libraries in Haskell. Introducing structured allows us to unify the two generic views in generic-deriving, and brings the possibility of using different nestings in the constructor and constructor arguments encoding. These developments can help in simplifying the implementation of GP in the compiler, as less code has to be part of the compiler itself (only that for generating structured instances), and more code can be moved into the user domain. GP library writers also see their life simplified, by gaining access to multiple generic views without needing to duplicate code.

Should structured turn out to be not informative enough to cover a particular approach, then it can always be refined or extended. Since we do not advocate to use structured directly, this means that only the direct conversions from structured have to be extended, and everything else will just keep working. Our hierarchical approach facilitates a future where GP libraries themselves are as modular and duplication-free as the code they enable end users to write.

Footnotes
1

Along the lines of its proposed kind-polymorphic overhaul described in http://hackage.haskell.org/trac/ghc/wiki/Commentary/Compiler/GenericDeriving#Kindpolymorphicoverhaul.

 

Acknowledgements

The first author is funded by EPSRC grant number EP/J010995/1. We thank the anonymous reviewers for the helpful feedback.

Copyright information

© Springer International Publishing Switzerland 2015

Authors and Affiliations

  1. 1.Department of Computer ScienceUniversity of OxfordOxfordUK
  2. 2.Well-Typed LLPRegensburgGermany

Personalised recommendations