alloy-1.2.2: Generic programming library

Safe HaskellNone



A module containing code to generate instances of the Alloy class for you.

Generating Alloy instances by hand would be laborious, complex and error-prone. This module provides instance generation, based on the Scrap Your Boilerplate (Data.Generics) generics that have built-in support in GHC. So you should just need to add

deriving (Data, Typeable)

after all your data-types, then use the functions in this module to generate some Haskell code with instances of the Alloy classes. The simplest functions for doing this are writeInstances and writeInstancesTo. The tutorial has examples of using this module.

You do not even have to modify the definitions of your data-types if you are using GHC 6.8.2 or later, you can simply add these lines in your module for generating the instances (assuming the data-type is not hidden during import):

deriving instance Typeable Foo
deriving instance Data Foo

That technique, and in fact this module as a whole generates orphan instances. This is generally advised against in Haskell, but it should not cause any problems here.

The primary drawback of Alloy's approach is that it can generate a lot of type-class instances (generally, the square of the number of types). There are two ways to control this explosion. Using GenWithOverlapped tends to halve the number of instances, at the cost of using a GHC extension. If you need instances for more than one of Alloy, AlloyA and AlloyARoute, it is possible to define one based on another, and thus avoid an entire set of instances altogether. See the alloy-proxy-fd package on Hackage for more details.



writeInstances :: GenInstanceConfig -> [GenInstance] -> [String] -> IO () Source

Generates the instances according to the options and writes it to stdout with the given header (the header is a list of lines without newline characters).

The configuration can be obtained from justPure (for example) or constructing the configuration yourself. The list of GenInstance can be obtained through genInstance. The header will generally be something like:

"module FooInstances where" : "import qualified Foo" : instanceImports

writeInstancesTo :: GenInstanceConfig -> [GenInstance] -> [String] -> FilePath -> IO () Source

Generates the instances according to the options and writes it to the given filename with the given header (the header is a list of lines without newline characters).

justPure :: GenOverlappedOption -> GenInstanceConfig Source

Constructs a configuration that just generates instances for the Alloy type-class (not AlloyA or AlloyARoute).

allInstances :: GenOverlappedOption -> GenInstanceConfig Source

Constructs instances for all the type-classes: Alloy, AlloyA and AlloyARoute. This may be quite a lot, see the documentation at the top of this file.

instanceImports :: [String] Source

The lines in the header that form the import statements necessary for the Alloy instances.

instanceImportsMapSet :: [String] Source

Like instanceImports but also adds the lines needed for maps and sets. If you use genMapInstance or genSetInstance, use this function, otherwise instanceImports will suffice.

data GenInstance Source

A type that represents a generator for instances of a set of types.

genInstance :: Data t => t -> GenInstance Source

Generates instances for all types within the given type. Therefore you should only need one or two of these calls to cover all of a complex data structure, by calling this on the largest types in your structure. The function is non-strict in its argument, so the easiest way to call it is:

genInstance (undefined :: MyType)

genMapInstance :: forall k v. (Ord k, Data k, Data v) => k -> v -> GenInstance Source

Generates an instance for the Map type. Map is a difficult type because its instance of Data hides its implementation, so we can't actually use the Data instance to work out what the children are (as we can do for other algebraic data types). So for every different Map that you want to process (or that you have inside other types you want to process), you must also call this function to effectively notify the generation-functions of the existence of your map. We wish there was an easier, non-hacky way but we can't see one.

genSetInstance :: forall a. (Ord a, Data a) => a -> GenInstance Source

Generates an instance for the Set type. See genMapInstance for an explanation.

genInstances :: GenInstanceConfig -> [GenInstance] -> IO [String] Source

Generates all the given instances (eliminating any duplicates) with the given options. The return is a list of lines of code. This should then be written to a Haskell module with the appropriate header.

languageExtras :: GenOverlappedOption -> String Source

The line with a LANGUAGE pragma detailed all the extensions needed. This is automatically written by writeInstances and writeInstancesTo at the top of the file, but you may want to use it if you are using genInstances.

genVectorInstance :: forall v. Data v => v -> GenInstance Source

Generates an instance for the Vector type. Like Map, the Data instance for Vector hides its representation.

data GenOverlappedOption Source

The option controlling whether the generated instances can be overlapped. If you choose GenWithOverlapped many less instances (around half, in our experience) will be generated, but you must enable the overlapping-instances flag in GHC (-XOverlappingInstances in GHC 6.8 and 6.10) when compiling the instances.

data GenClassOption Source

For now, this option has only one setting.