packages feed

generics-eot 0.1 → 0.2

raw patch · 6 files changed

+602/−527 lines, 6 files

Files

README.md view
@@ -2,6 +2,4 @@ very simple to understand and use. It's heavily inspired by the awesome `generics-sop` package (http://hackage.haskell.org/package/generics-sop). -There's documentation in the source code, there's a tutorial in-`Generics.Eot.Tutorial` and there's also some examples in `examples` (and-descriptive tests for them in `test/Examples`).+Documentation can be found here: http://generics-eot.readthedocs.org/en/latest/
generics-eot.cabal view
@@ -3,7 +3,7 @@ -- see: https://github.com/sol/hpack  name:           generics-eot-version:        0.1+version:        0.2 synopsis:       A library for generic programming that aims to be easy to understand category:       Generics homepage:       https://github.com/soenkehahn/generics-eot#readme@@ -24,7 +24,7 @@ library   hs-source-dirs:       src-  ghc-options: -Wall -fno-warn-name-shadowing+  ghc-options: -Wall -fno-warn-name-shadowing -pgmL markdown-unlit   build-depends:       base == 4.*   exposed-modules:@@ -42,7 +42,7 @@       test     , src     , examples-  ghc-options: -Wall -fno-warn-name-shadowing+  ghc-options: -Wall -fno-warn-name-shadowing -pgmL markdown-unlit   build-depends:       base == 4.*     , hspec@@ -73,7 +73,7 @@   hs-source-dirs:       test/quickcheck     , src-  ghc-options: -Wall -fno-warn-name-shadowing+  ghc-options: -Wall -fno-warn-name-shadowing -pgmL markdown-unlit   build-depends:       base == 4.*     , hspec
src/Generics/Eot.hs view
@@ -8,7 +8,7 @@ -- that is easy to understand. "eot" stands for "eithers of tuples". -- -- A tutorial on how to use @generics-eot@ can be found here:--- "Generics.Eot.Tutorial".+-- http://generics-eot.readthedocs.org/en/latest/. module Generics.Eot (   HasEot(..), 
− src/Generics/Eot/Tutorial.hs
@@ -1,518 +0,0 @@-{-# LANGUAGE DefaultSignatures #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeSynonymInstances #-}-{-# LANGUAGE UndecidableInstances #-}---- | This tutorial is meant to be read alongside with the haddock comments in--- "Generics.Eot".------ @generics-eot@ allows roughly three different kinds of operations:------ 1. Accessing meta information about ADTs ('datatype' for names, 'Proxy' and---    'Eot' for field types). Example: Generation of database schemas for ADTs.--- 2. Deconstructing values generically ('toEot'). Example: Serialization to a---    binary format.--- 3. Constructing values of an ADT generically ('fromEot').---    Example: Deserialization from a binary format.------ Sometimes only one of the three forms is used but often multiple have to--- be combined. For example serialization to JSON usually--- requires both 'datatype' and--- 'toEot'.--module Generics.Eot.Tutorial where--import           Data.Char-import           Data.List-import           Data.Typeable--import           Generics.Eot---- * #1stExample# 1st Example: Meta Information Without Types: Field Names---- | This simple function extracts the names of all field selectors and returns--- them as a list:------ >>> namesOfFields (Proxy :: Proxy A)--- ["foo","bar","baz"]------ (You're encouraged to look at the source code of the examples in this--- tutorial to understand how they work. If you're looking at a web page--- generated by haddock, you'll hopefully find @Source@ links to the right.)-namesOfFields :: HasEot a => Proxy a -> [String]-namesOfFields proxy =-  nub $-  concatMap (fieldNames . fields) $-  constructors $ datatype proxy-  where-    fieldNames :: Fields -> [String]-    fieldNames fields = case fields of-      Selectors names -> names-      _ -> []--data A = A1 {-    foo :: String,-    bar :: Int-  }-  | A2 {-    bar :: Int,-    baz :: Bool-  }-  deriving (Generic, Show)---- * The 'Generic' instance: Don't forget!!!---- $ To be able to use generic functions that are written with @generics-eot@--- you need to derive an instance for 'GHC.Generics.Generic' (using--- @DeriveGeneric@) for your ADTs. This will automatically give you an instance--- for 'HasEot'.------ When the instance for 'GHC.Generics.Generic' is missing the type error--- messages are unfortunately very confusing and unhelpful. They go something--- like this:------ >    Couldn't match type ‘GHC.Generics.Rep WithoutGeneric’--- >                   with ‘GHC.Generics.D1 c f’--- >    The type variables ‘c’, ‘f’ are ambiguous--- >    In the expression: namesOfFields (Proxy :: Proxy WithoutGeneric)------ So don't forget: you need a 'Generic' instance.---- ** 'Eot': Isomorphic representations---- $ Part of the type class 'HasEot' is the type-level function 'Eot' that maps--- ADTs to isomorphic types.--- These isomorphic types are always a combination of 'Either's, tuples and--- the uninhabited type 'Void'. For example this type:--data B = B1 Int | B2 String Bool | B3-  deriving (Generic)---- $ would be mapped to--- @'Either' ('Int', ()) ('Either' ('String', ('Bool', ())) ('Either' () 'Void'))@.--- (For the exact rules of this mapping see here: 'Eot'.)------ If we have an ADT @a@ then we can convert values of type @a@ to this--- isomorphic representation--- @'Eot' a@ with 'toEot' and we can convert in the other direction with--- 'fromEot'. Generic functions always operate on these isomorphic--- representations and then convert from or to the real ADTs with 'fromEot' and--- 'toEot'.------ These generic isomorphic types are referred to as "eot" -- short for--- "'Either's of tuples".---- * #2ndExample# 2nd Example: Deconstructing Values: Serialization---- $ We start by writing a function that operates on the eot representations.--- The eot representations follow simple patterns and always look similar, but--- they don't look exactly the same for different ADTs.--- For this reason we have to use a type class:--class EotSerialize eot where-  eotSerialize :: Int -- ^ The number of the constructor being passed in-    -> eot -- ^ The eot representation-    -> [Int] -- ^ A simple serialization format---- $ Now we need to write instances for the types that occur in eot types.--- (Please, look at the source code to see the instance implementations.)--- Usually these are:------ - @'Either' this next@:------     - If as eot value we get @'Left' this@ it means that the original value---     was constructed with the constructor that corresponds to @this@. In this---     case we put the number of the constructor into the output and continue---     with serializing the fields of type @this@.---     - If we get @'Right' rest@ it means that one of the following---     constructors was the one that the original value was built with. We---     continue by increasing the constructor counter and serializing the value---     of type @rest@.------     Note that this results in 'EotSerialize' class constraints for both---     @this@ and @rest@. If we write the correct instances for all eot types---     these constraints should always be fulfilled.--instance (EotSerialize this, EotSerialize next) =>-  EotSerialize (Either this next) where--  eotSerialize n (Left fields) = n : eotSerialize n fields-  eotSerialize n (Right next) = eotSerialize (succ n) next---- $--- - 'Void':---     We need this instance to make the compiler happy, but it'll never be---     used. If you look at the type you can also see that: an argument of type---     'Void' cannot be constructed.--instance EotSerialize Void where-  eotSerialize _ void = seq void $ error "impossible"---- $--- - @(x, xs)@:---     Right-nested 2-tuples are used to encode all the fields for one specific---     constructor. So @x@ is the current field and @xs@ are the remaining---     fields. To serialize this we serialize @x@ (using 'serialize')---     and also write the length of the---     resulting list into the output. This will allow deserialization.------     Note: We could use 'EotSerialize' to serialize the fields. But that would---     be a bit untrue to the spirit, since the fields are not eot types. Apart---     from that we might want to encode a field of e.g. type @'Either' a b@---     differently than the eot type @'Either' a b@. So we use a very similar---     but distinct type class called 'Serialize'.------     The value of type @xs@ contains the remaining fields and will be encoded---     recursively with 'eotSerialize'.--instance (Serialize x, EotSerialize xs) => EotSerialize (x, xs) where-  eotSerialize n (x, xs) =-    let xInts = serialize x-    in length xInts : xInts ++ eotSerialize n xs---- $--- - @()@:---     Finally we need an instance for the unit type that marks the end of the---     fields encoded in 2-tuples. Since @()@ doesn't carry any information, we---     can encode it as the empty list.--instance EotSerialize () where-  eotSerialize _ () = []---- | This is the class 'Serialize'. It's used to serialize every field of the--- used ADTs, so we need instances for all of them.-class Serialize a where-  serialize :: a -> [Int]-  default serialize :: (HasEot a, EotSerialize (Eot a)) => a -> [Int]-  serialize = genericSerialize--instance Serialize Int where-  serialize i = [i]--instance Serialize String where-  serialize = map ord--instance Serialize Bool where-  serialize True = [1]-  serialize False = [0]--instance Serialize () where-  serialize () = []---- | To tie everything together we provide a function 'genericSerialize' that--- converts a value of some ADT into an eot value using 'toEot' and then uses--- 'eotSerialize' to convert that eot value into a list of 'Int's.-genericSerialize :: (HasEot a, EotSerialize (Eot a)) => a -> [Int]-genericSerialize = eotSerialize 0 . toEot---- $ And it works too:------ >>> genericSerialize (A1 "foo" 42)--- [0,3,102,111,111,1,42]--- >>> genericSerialize (A2 23 True)--- [1,1,23,1,1]---- * #3rdExample# 3rd Example: Constructing Values: Deserialization---- $ Deserialization works very similarly. It differs in that the functions turn--- lists of 'Int's into eot values.------ Here's the 'EotDeserialize' class with instances for:------  - @'Either' this next@---  - @'Void'@---  - @(x, xs)@---  - @()@--class EotDeserialize eot where-  eotDeserialize :: [Int] -> eot--instance (EotDeserialize this, EotDeserialize next) =>-  EotDeserialize (Either this next) where--  eotDeserialize (0 : r) = Left $ eotDeserialize r-  eotDeserialize (n : r) = Right $ eotDeserialize (pred n : r)-  eotDeserialize [] = error "invalid input"--instance EotDeserialize Void where-  eotDeserialize _ = error "invalid input"--instance (Deserialize x, EotDeserialize xs) =>-  EotDeserialize (x, xs) where--  eotDeserialize (len : r) =-    let (this, rest) = splitAt len r-    in (deserialize this, eotDeserialize rest)-  eotDeserialize [] = error "invalid input"--instance EotDeserialize () where-  eotDeserialize [] = ()-  eotDeserialize (_ : _) = error "invalid input"---- $ And here's the 'Deserialize' class plus all instances to deserialize the--- fields:--class Deserialize a where-  deserialize :: [Int] -> a--instance Deserialize Int where-  deserialize [n] = n-  deserialize _ = error "invalid input"--instance Deserialize String where-  deserialize = map chr--instance Deserialize () where-  deserialize [] = ()-  deserialize (_ : _) = error "invalid input"--instance Deserialize Bool where-  deserialize [0] = False-  deserialize [1] = True-  deserialize _ = error "invalid input"---- | And here's 'genericDeserialize' to tie it together. It uses--- 'eotDeserialize' to convert a list of 'Int's into an eot value and then--- 'fromEot' to construct a value of the wanted ADT.-genericDeserialize :: (HasEot a, EotDeserialize (Eot a)) => [Int] -> a-genericDeserialize = fromEot . eotDeserialize---- $ Here you can see it in action:------ >>> genericDeserialize [0,3,102,111,111,1,42] :: A--- A1 {foo = "foo", bar = 42}--- >>> genericDeserialize [1,1,23,1,1] :: A--- A2 {bar = 23, baz = True}------ And it is the inverse of 'genericSerialize':------ >>> (genericDeserialize $ genericSerialize $ A1 "foo" 42) :: A--- A1 {foo = "foo", bar = 42}---- * 4th Example: Meta Information with types: generating SQL schemas---- $ Accessing meta information __including__ the types works very--- similarly to deconstructing or constructing values. It uses the same--- structure of type classes and instances for the eot-types. The difference is:--- since we don't want actual values of our ADT as input or output we operate on--- 'Proxy's of our eot-types.------ As an example we're going to implement a function that generates SQL--- statements that create tables that our ADTs would fit into. To be able to--- use nice names for the table and columns we're going to traverse the--- type-less meta information (see <#1stExample 1st example>) at the same time.------ (Note that the generated SQL statements are targeted at a fictional--- database implementation that magically understands Haskell types like--- 'Int' and 'String', or rather @['Char']@.)------ Again we start off by writing a class that operates on the eot-types. Besides--- the eot-type the class has an additional parameter, @meta@, that will be--- instantiated by the corresponding types used for untyped meta information.--class EotCreateTableStatement meta eot where-  eotCreateTableStatement :: meta -> Proxy eot -> [String]---- $ Our first instance is for the complete datatype. @eot@ is instantiated to--- @'Either' fields 'Void'@. Note that this instance only works for ADTs with--- exactly one constructor as we don't support types with multiple constructors.--- @meta@ is instantiated to 'Datatype' which is the type for meta information--- for ADTs.--instance EotCreateTableStatement [String] fields =>-  EotCreateTableStatement Datatype (Either fields Void) where--  eotCreateTableStatement datatype Proxy = case datatype of-    Datatype name [Constructor _ (Selectors fields)] ->-      "CREATE TABLE " :-      name :-      " COLUMNS " :-      "(" :-      intercalate ", " (eotCreateTableStatement fields (Proxy :: Proxy fields)) :-      ");" :-      []-    Datatype _ [Constructor name (NoSelectors _)] ->-      error ("constructor " ++ name ++ " has no selectors, this is not supported")-    Datatype name _ ->-      error ("type " ++ name ++ " must have exactly one constructor")---- $ The second instance is responsible for creating the parts of the SQL--- statements that declare the columns. As such it has to traverse the fields--- of our ADT. @eot@ is instantiated to the usual @(x, xs)@. @meta@ is--- instantiated to @['String']@, representing the field names. The name of the--- field type is obtained using 'typeRep', therefore we need a @'Typeable' x@--- constraint.--instance (Typeable x, EotCreateTableStatement [String] xs) =>-  EotCreateTableStatement [String] (x, xs) where--  eotCreateTableStatement (field : fields) Proxy =-    (field ++ " " ++ show (typeRep (Proxy :: Proxy x))) :-    eotCreateTableStatement fields (Proxy :: Proxy xs)-  eotCreateTableStatement [] Proxy = error "impossible"---- $ The last instances is for @()@. It's needed as the base case for--- traversing the fields and as such returns just an empty list.--instance EotCreateTableStatement [String] () where-  eotCreateTableStatement [] Proxy = []-  eotCreateTableStatement (_ : _) Proxy = error "impossible"---- | 'createTableStatement' ties everything together. It obtaines the meta--- information through 'datatype' passing a 'Proxy' for @a@. And it creates a--- 'Proxy' for the eot-type:------ > Proxy :: Proxy (Eot a)------ Then it calls 'eotCreateTableStatement' and just 'concat's the resulting--- snippets.-createTableStatement :: forall a . (HasEot a, EotCreateTableStatement Datatype (Eot a)) =>-  Proxy a -> String-createTableStatement proxy =-  concat $ eotCreateTableStatement (datatype proxy) (Proxy :: Proxy (Eot a))---- $ As an example, we're going to use 'Person':--data Person-  = Person {-    name :: String,-    age :: Int-  }-  deriving (Generic)---- $ And here's the created SQL statement:------ >>> putStrLn $ createTableStatement (Proxy :: Proxy Person)--- CREATE TABLE Person COLUMNS (name [Char], age Int);------ If we try to use an ADT with multiple constructors, we get a type error--- due to a missing instance:------ >>> putStrLn $ createTableStatement (Proxy :: Proxy A)--- <BLANKLINE>--- ...---     No instance for (EotCreateTableStatement---                        Datatype---                        (Either ([Char], (Int, ())) (Either (Int, (Bool, ())) Void)))---       arising from a use of ‘createTableStatement’--- ...------ If we try to use it with an ADT with a single constructor but no selectors,--- we get a runtime error:--data NoSelectors-  = NotSupported Int Bool-  deriving (Generic)---- $ >>> putStrLn $ createTableStatement (Proxy :: Proxy NoSelectors)--- *** Exception: constructor NotSupported has no selectors, this is not supported---- * DefaultSignatures---- $ There is a GHC language extension called @<https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/type-class-extensions.html#class-default-signatures DefaultSignatures>@.--- In itself it has little to do with generic programming, but it makes a good--- companion.---- ** How DefaultSignatures work:---- $ Imagine you have a type class called @ToString@ which allows to convert--- values to 'String's:--class ToString a where-  toString :: a -> String-  default toString :: Show a => a -> String-  toString = show---- $ You can write instances manually, but you might be tempted to give the--- following default implementation for 'toString':------ > toString = show------ The idea is that then you can just write down an empty 'ToString' instance:------ > instance ToString A------ and you get to use 'toString' on values of type 'A' for free.------ But that default implementation doesn't work, because in the class declaration--- we don't have an instance for @Show a@. ghc says:------ > Could not deduce (Show a) arising from a use of ‘show’--- > from the context (ToString a)------ One solution would be to make 'ToString' a subclass of 'Show', but then we--- cannot implement 'ToString' instances manually anymore for types that don't--- have a 'Show' instance. @DefaultSignatures@ provide a better solution. The--- extension allows you to further narrow down the type for your default--- implementation for class methods:------ > class ToString a where--- >   toString :: a -> String--- >   default toString :: Show a => a -> String--- >   toString = show------ Then writing down empty instances work for types that have a 'Show' instance:------ > instance ToString Int--instance ToString Int---- $ >>> toString (42 :: Int)--- "42"---- $ Note: if you write down an empty @ToString@ instances for a type that--- does not have a 'Show' instance, the error message looks like this:------ > No instance for (Show NoShow)------ This might be confusing especially since haddock docs don't list the default--- signatures or implementations and users of the class might be wondering why--- 'Show' comes into play at all.---- ** How to use @DefaultSignatures@ for generic programming:---- $ @DefaultSignatures@ are especially handy when doing generic programming.--- Remember the type class 'Serialize' from the second example? Initially we--- used it to serialize the fields of our ADTs in the generic serialization--- through 'genericSerialize' and 'EotSerialize'. We just assumed that we would--- have a manual implementation for all field types. But with--- @DefaultSignatures@ we can now give a default implementation that uses--- 'genericSerialize':------ > class Serialize a where--- >   serialize :: a -> [Int]--- >   default serialize :: (HasEot a, EotSerialize (Eot a)) => a -> [Int]--- >   serialize = genericSerialize------ Note that the default implementation is given by 'genericSerialize' and has--- the same constraints.------ Now we can write empty instances for custom ADTs:--data C-  = C1 Int String-  deriving (Generic)---- $--- > instance Serialize C--instance Serialize C---- $ You could say that by giving this empty instance we give our blessing to--- use 'genericSerialize' for this type, but we don't have to actually implement--- anything. And it works:------ >>> serialize (C1 42 "yay!")--- [0,1,42,4,121,97,121,33]---- $ Important is that we still have the option to implement instances manually--- by overwriting the default implementation. This is needed for basic types--- like 'Int' and 'Char' that don't have useful generic representations. But it--- also allows us to overwrite instances for ADTs manually. For example you may--- want a certain type to be serialized in a special way that deviates from the--- generic implementation or you may implement an instance manually for--- performance gain.
+ src/Generics/Eot/Tutorial.lhs view
@@ -0,0 +1,595 @@+# `generics-eot` tutorial++This tutorial is meant to be read alongside with the haddock comments in+[Generics.Eot](http://hackage.haskell.org/package/generics-eot-0.1/docs/Generics-Eot.html).+Its source is a compiled haskell file, so we have to get some language pragmas+and imports out of the way first:++``` haskell+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeSynonymInstances #-}+{-# LANGUAGE UndecidableInstances #-}++module Generics.Eot.Tutorial where++import           Data.Char+import           Data.List+import           Data.Typeable++import           Generics.Eot+```++`generics-eot` allows roughly three different kinds of operations:++1. Accessing meta information about ADTs (`datatype` for names, `Proxy` and+   `Eot` for field types). Example: Generation of database schemas for ADTs.+2. Deconstructing values generically (`toEot`). Example: Serialization to a+   binary format.+3. Constructing values of an ADT generically (`fromEot`).+   Example: Deserialization from a binary format.++Sometimes only one of the three forms is used but often multiple have to be+combined. For example serialization to JSON usually requires both `datatype`+and `toEot`.++## 1st Example: Meta Information Without Types: Field Names++This simple function extracts the names of all field selectors and returns them+as a list:++``` haskell+namesOfFields :: HasEot a => Proxy a -> [String]+namesOfFields proxy =+  nub $+  concatMap (fieldNames . fields) $+  constructors $ datatype proxy+  where+    fieldNames :: Fields -> [String]+    fieldNames fields = case fields of+      Selectors names -> names+      _ -> []+```++And here's proof that it works (using+[doctest](https://github.com/sol/doctest)):++``` haskell+data A = A1 {+    foo :: String,+    bar :: Int+  }+  | A2 {+    bar :: Int,+    baz :: Bool+  }+  deriving (Generic, Show)++-- $ >>> namesOfFields (Proxy :: Proxy A)+-- ["foo","bar","baz"]+```++## The `Generic` instance: Don't forget!!!++To be able to use generic functions that are written with `generics-eot` you+need to derive an instance for `GHC.Generics.Generic` (using `DeriveGeneric`)+for your ADTs. This will automatically give you an instance for `HasEot`.++When the instance for `GHC.Generics.Generic` is missing the type error messages+are unfortunately very confusing and unhelpful. They go something like this:++    Couldn't match type ‘GHC.Generics.Rep WithoutGeneric’+                   with ‘GHC.Generics.D1 c f’+    The type variables ‘c’, ‘f’ are ambiguous+    In the expression: namesOfFields (Proxy :: Proxy WithoutGeneric)++So don't forget: you need a `Generic` instance.++## `Eot`: Isomorphic representations++Part of the type class `HasEot` is the type-level function `Eot` that maps ADTs+to isomorphic types.  These isomorphic types are always a combination of+`Either`s, tuples and the uninhabited type `Void`. For example this type:++``` haskell+data B = B1 Int | B2 String Bool | B3+  deriving (Generic)+```++would be mapped to:++```+Either (Int, ()) (Either (String, (Bool, ())) (Either () Void))+```++Tip: Here's how you can execute the type-level function `Eot` in `ghci`:++``` haskell+-- $ >>> :kind! Eot B+-- Eot B :: *+-- = Either (Int, ()) (Either ([Char], (Bool, ())) (Either () Void))+```++For the exact rules of this mapping see here:+[Eot](http://hackage.haskell.org/package/generics-eot-0.1/docs/Generics-Eot.html#t:Eot).++If we have an ADT `a` then we can convert values of type `a` to this isomorphic+representation `Eot a` with `toEot` and we can convert in the other direction+with `fromEot`. Generic functions always operate on these isomorphic+representations and then convert from or to the real ADTs with `fromEot` and+`toEot`.++These generic isomorphic types are referred to as "eot" -- short for+"`Either`s of tuples".++## 2nd Example: Deconstructing Values: Serialization++We start by writing a function that operates on the eot representations.  The+eot representations follow simple patterns and always look similar, but they+don't look exactly the same for different ADTs.  For this reason we have to use+a type class:++``` haskell+class EotSerialize eot where+  eotSerialize :: Int -- ^ The number of the constructor being passed in+    -> eot -- ^ The eot representation+    -> [Int] -- ^ A simple serialization format+```++Now we need to write instances for the types that occur in eot types.  Usually+these are:++- `Either this next`:++  - If as eot value we get `Left this` it means that the original value+    was constructed with the constructor that corresponds to `this`. In this+    case we put the number of the constructor into the output and continue+    with serializing the fields of type `this`.+  - If we get `Right rest` it means that one of the following constructors was+    the one that the original value was built with. We+    continue by increasing the constructor counter and serializing the value+    of type `rest`.++  Note that this results in `EotSerialize` class constraints for both+  `this` and `rest`. If we write the correct instances for all eot types+  these constraints should always be fulfilled.++``` haskell+instance (EotSerialize this, EotSerialize next) =>+  EotSerialize (Either this next) where++  eotSerialize n (Left fields) = n : eotSerialize n fields+  eotSerialize n (Right next) = eotSerialize (succ n) next+```++- `Void`:+  We need this instance to make the compiler happy, but it'll never be+  used. If you look at the type you can also see that: an argument of type+  `Void` cannot be constructed.++``` haskell+instance EotSerialize Void where+  eotSerialize _ void = seq void $ error "impossible"+```++- `(x, xs)`:+  Right-nested 2-tuples are used to encode all the fields for one specific+  constructor. So `x` is the current field and `xs` are the remaining+  fields. To serialize this we serialize `x` (using `serialize`)+  and also write the length of the+  resulting list into the output. This will allow deserialization.++  Note: We could use `EotSerialize` to serialize the fields. But that would+  be a bit untrue to the spirit, since the fields are not eot types. Apart+  from that we might want to encode a field of e.g. type `Either a b`+  differently than the eot type `Either a b`. So we use a very similar+  but distinct type class called `Serialize`.++  The value of type `xs` contains the remaining fields and will be encoded+  recursively with `eotSerialize`:++``` haskell+instance (Serialize x, EotSerialize xs) => EotSerialize (x, xs) where+  eotSerialize n (x, xs) =+    let xInts = serialize x+    in length xInts : xInts ++ eotSerialize n xs+```++- `()`:+  Finally we need an instance for the unit type that marks the end of the+  fields encoded in 2-tuples. Since `()` doesn't carry any information, we+  can encode it as the empty list:++``` haskell+instance EotSerialize () where+  eotSerialize _ () = []+```++This is the class `Serialize`:++``` haskell+class Serialize a where+  serialize :: a -> [Int]+```++We give `serialize` a default implementation, but please ignore that for now.+It'll be explained later in the section about+[DefaultSignatures](#defaultsignatures):++``` haskell+  default serialize :: (HasEot a, EotSerialize (Eot a)) => a -> [Int]+  serialize = genericSerialize+```++`Serialize` is used to serialize every field of the used ADTs, so we need+instances for all of them:++``` haskell+instance Serialize Int where+  serialize i = [i]++instance Serialize String where+  serialize = map ord++instance Serialize Bool where+  serialize True = [1]+  serialize False = [0]++instance Serialize () where+  serialize () = []+```++To tie everything together we provide a function `genericSerialize` that+converts a value of some ADT into an eot value using `toEot` and then uses+`eotSerialize` to convert that eot value into a list of `Int`s.++``` haskell+genericSerialize :: (HasEot a, EotSerialize (Eot a)) => a -> [Int]+genericSerialize = eotSerialize 0 . toEot+```++And it works too:++``` haskell+-- $ >>> genericSerialize (A1 "foo" 42)+-- [0,3,102,111,111,1,42]+-- >>> genericSerialize (A2 23 True)+-- [1,1,23,1,1]+```++## 3rd Example: Constructing Values: Deserialization++Deserialization works very similarly. It differs in that the functions turn+lists of `Int`s into eot values.++Here's the `EotDeserialize` class with instances for:++- `Either this next`+- `Void`+- `(x, xs)`+- `()`++``` haskell+class EotDeserialize eot where+  eotDeserialize :: [Int] -> eot++instance (EotDeserialize this, EotDeserialize next) =>+  EotDeserialize (Either this next) where++  eotDeserialize (0 : r) = Left $ eotDeserialize r+  eotDeserialize (n : r) = Right $ eotDeserialize (pred n : r)+  eotDeserialize [] = error "invalid input"++instance EotDeserialize Void where+  eotDeserialize _ = error "invalid input"++instance (Deserialize x, EotDeserialize xs) =>+  EotDeserialize (x, xs) where++  eotDeserialize (len : r) =+    let (this, rest) = splitAt len r+    in (deserialize this, eotDeserialize rest)+  eotDeserialize [] = error "invalid input"++instance EotDeserialize () where+  eotDeserialize [] = ()+  eotDeserialize (_ : _) = error "invalid input"+```++And here's the `Deserialize` class plus all instances to deserialize the+fields:++``` haskell+class Deserialize a where+  deserialize :: [Int] -> a++instance Deserialize Int where+  deserialize [n] = n+  deserialize _ = error "invalid input"++instance Deserialize String where+  deserialize = map chr++instance Deserialize () where+  deserialize [] = ()+  deserialize (_ : _) = error "invalid input"++instance Deserialize Bool where+  deserialize [0] = False+  deserialize [1] = True+  deserialize _ = error "invalid input"+```++And here's `genericDeserialize` to tie it together. It uses+`eotDeserialize` to convert a list of `Int`s into an eot value and then+`fromEot` to construct a value of the wanted ADT.++``` haskell+genericDeserialize :: (HasEot a, EotDeserialize (Eot a)) => [Int] -> a+genericDeserialize = fromEot . eotDeserialize+```++Here you can see it in action:++``` haskell+-- $ >>> genericDeserialize [0,3,102,111,111,1,42] :: A+-- A1 {foo = "foo", bar = 42}+-- >>> genericDeserialize [1,1,23,1,1] :: A+-- A2 {bar = 23, baz = True}+```++And it is the inverse of `genericSerialize`:++``` haskell+-- $ >>> (genericDeserialize $ genericSerialize $ A1 "foo" 42) :: A+-- A1 {foo = "foo", bar = 42}+```++## 4th Example: Meta Information with types: generating SQL schemas++Accessing meta information __including__ the types works very+similarly to deconstructing or constructing values. It uses the same+structure of type classes and instances for the eot-types. The difference is:+since we don't want actual values of our ADT as input or output we operate on+`Proxy`s of our eot-types.++As an example we're going to implement a function that generates SQL statements+that create tables that our ADTs would fit into. To be able to use nice names+for the table and columns we're going to traverse the type-less meta+information (see+[1st Example](#st-example-meta-information-without-types-field-names)) at the+same time.++(Note that the generated SQL statements are targeted at a fictional+database implementation that magically understands Haskell types like+`Int` and `String`, or rather `[Char]`.)++Again we start off by writing a class that operates on the eot-types. Besides+the eot-type the class has an additional parameter, `meta`, that will be+instantiated by the corresponding types used for untyped meta information.++``` haskell+class EotCreateTableStatement meta eot where+  eotCreateTableStatement :: meta -> Proxy eot -> [String]+```++Our first instance is for the complete datatype. `eot` is instantiated to+`Either fields Void`. Note that this instance only works for ADTs with+exactly one constructor as we don't support types with multiple constructors.+`meta` is instantiated to `Datatype` which is the type for meta information+for ADTs.++``` haskell+instance EotCreateTableStatement [String] fields =>+  EotCreateTableStatement Datatype (Either fields Void) where++  eotCreateTableStatement datatype Proxy = case datatype of+    Datatype name [Constructor _ (Selectors fields)] ->+      "CREATE TABLE " :+      name :+      " COLUMNS " :+      "(" :+      intercalate ", " (eotCreateTableStatement fields (Proxy :: Proxy fields)) :+      ");" :+      []+    Datatype _ [Constructor name (NoSelectors _)] ->+      error ("constructor " ++ name ++ " has no selectors, this is not supported")+    Datatype name _ ->+      error ("type " ++ name ++ " must have exactly one constructor")+```++The second instance is responsible for creating the parts of the SQL+statements that declare the columns. As such it has to traverse the fields+of our ADT. `eot` is instantiated to the usual `(x, xs)`. `meta` is+instantiated to `[String]`, representing the field names. The name of the+field type is obtained using `typeRep`, therefore we need a `Typeable x`+constraint.++``` haskell+instance (Typeable x, EotCreateTableStatement [String] xs) =>+  EotCreateTableStatement [String] (x, xs) where++  eotCreateTableStatement (field : fields) Proxy =+    (field ++ " " ++ show (typeRep (Proxy :: Proxy x))) :+    eotCreateTableStatement fields (Proxy :: Proxy xs)+  eotCreateTableStatement [] Proxy = error "impossible"+```++The last instances is for `()`. It's needed as the base case for+traversing the fields and as such returns just an empty list.++``` haskell+instance EotCreateTableStatement [String] () where+  eotCreateTableStatement [] Proxy = []+  eotCreateTableStatement (_ : _) Proxy = error "impossible"+```++`createTableStatement` ties everything together. It obtaines the meta+information through `datatype` passing a `Proxy` for `a`. And it creates a+`Proxy` for the eot-type `Proxy :: Proxy (Eot a)`. Then it calls+`eotCreateTableStatement` and just `concat`s the resulting snippets.++``` haskell+createTableStatement :: forall a . (HasEot a, EotCreateTableStatement Datatype (Eot a)) =>+  Proxy a -> String+createTableStatement proxy =+  concat $ eotCreateTableStatement (datatype proxy) (Proxy :: Proxy (Eot a))+```++As an example, we're going to use `Person`:++``` haskell+data Person+  = Person {+    name :: String,+    age :: Int+  }+  deriving (Generic)+```++And here's the created SQL statement:++``` haskell+-- $ >>> putStrLn $ createTableStatement (Proxy :: Proxy Person)+-- CREATE TABLE Person COLUMNS (name [Char], age Int);+```++If we try to use an ADT with multiple constructors, we get a type error+due to a missing instance:++``` haskell+-- $ >>> putStrLn $ createTableStatement (Proxy :: Proxy A)+-- <BLANKLINE>+-- ...+--     No instance for (EotCreateTableStatement+--                        Datatype+--                        (Either ([Char], (Int, ())) (Either (Int, (Bool, ())) Void)))+--       arising from a use of ‘createTableStatement’+-- ...+```++If we try to use it with an ADT with a single constructor but no selectors,+we get a runtime error:++``` haskell+data NoSelectors+  = NotSupported Int Bool+  deriving (Generic)++-- $ >>> putStrLn $ createTableStatement (Proxy :: Proxy NoSelectors)+-- *** Exception: constructor NotSupported has no selectors, this is not supported+```++## DefaultSignatures++There is a GHC language extension called+[DefaultSignatures](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/type-class-extensions.html#class-default-signatures).+In itself it has little to do with generic programming, but it makes a good+companion.++### How DefaultSignatures work++Imagine you have a type class called `ToString` which allows to convert+values to `String`s:++``` haskell+class ToString a where+  toString :: a -> String+```++You can write instances manually, but you might be tempted to give the+following default implementation for `toString`:++    toString = show++The idea is that then you can just write down an empty `ToString` instance:++    instance ToString A++and you get to use `toString` on values of type `A` for free, because `A` has+a `Show` instance.++But that default implementation doesn't work, because in the class declaration+we don't have an instance for `Show a`. `ghc` says:++    Could not deduce (Show a) arising from a use of ‘show’+     from the context (ToString a)++One solution would be to make `ToString` a subclass of `Show`, but then we+cannot implement `ToString` instances manually anymore for types that don't+have a `Show` instance. `DefaultSignatures` provide a better solution. The+extension allows you to further narrow down the type for your default+implementation for class methods:++``` haskell+class ToString2 a where+  toString2 :: a -> String+  default toString2 :: Show a => a -> String+  toString2 = show+```++Then writing down empty instances works for types that have a `Show` instance:++``` haskell+instance ToString2 Int++-- $ >>> toString2 (42 :: Int)+-- "42"+```++Note: if you write down an empty `ToString2` instances for a type that+does not have a `Show` instance, the error message looks like this:++    No instance for (Show NoShow)++This might be confusing especially since haddock docs don't list the default+signatures or implementations and users of the class might be wondering why+`Show` comes into play at all.++### How to use `DefaultSignatures` for generic programming++`DefaultSignatures` are especially handy when doing generic programming.+Remember the type class `Serialize` from the+[second example](#nd-example-deconstructing-values-serialization)? In that+example we used it to serialize the fields of our ADTs in the generic+serialization through `genericSerialize` and `EotSerialize`. We just assumed+that we would have a manual implementation for all field types. But we also+gave it a default implementation for `serialize` in terms of+`genericSerialize`:++    default serialize :: (HasEot a, EotSerialize (Eot a)) => a -> [Int]+    serialize = genericSerialize++Note that the default implementation has the same class constraints as+`genericSerialize`.++Now we can write empty instances for custom ADTs:++``` haskell+data C+  = C1 Int String+  deriving (Generic)++instance Serialize C+```++You could say that by giving this empty instance we give our blessing to+use `genericSerialize` for this type, but we don't have to actually implement+anything. And it works:++``` haskell+-- $ >>> serialize (C1 42 "yay!")+-- [0,1,42,4,121,97,121,33]+```++Important is that we still have the option to implement instances manually+by overwriting the default implementation. This is needed for basic types+like `Int` and `Char` that don't have useful generic representations. But it+also allows us to overwrite instances for ADTs manually. For example you may+want a certain type to be serialized in a special way that deviates from the+generic implementation or you may implement an instance manually for+performance gain.
test/Generics/Eot/TutorialSpec.hs view
@@ -9,4 +9,4 @@ spec :: Spec spec = describe "tutorial" $ do   it "doctests" $ do-    doctest (words "src/Generics/Eot/Tutorial.hs -isrc")+    doctest (words "src/Generics/Eot/Tutorial.lhs -isrc -pgmL markdown-unlit")