{-# LANGUAGE PolyKinds, UndecidableInstances #-} {-# OPTIONS_GHC -fno-warn-unused-binds #-} -- | Main module of @generics-sop@ -- -- In most cases, you will probably want to import just this module, -- and possibly "Generics.SOP.TH" if you want to use Template Haskell -- to generate 'Generic' instances for you. -- -- = Generic programming with sums of products -- -- You need this library if you want to define your own generic functions -- in the sum-of-products SOP style. Generic programming in the SOP style -- follows the following idea: -- -- 1. A large class of datatypes can be viewed in a uniform, structured -- way: the choice between constructors is represented using an n-ary -- sum (called 'NS'), and the arguments of each constructor are -- represented using an n-ary product (called 'NP'). -- -- 2. The library captures the notion of a datatype being representable -- in the following way. There is a class 'Generic', which for a given -- datatype @A@, associates the isomorphic SOP representation with -- the original type under the name @'Rep' A@. The class also provides -- functions 'from' and 'to' that convert between @A@ and @'Rep' A@ and -- witness the isomorphism. -- -- 3. Since all 'Rep' types are sums of products, you can define -- functions over them by performing induction on the structure, of -- by using predefined combinators that the library provides. Such -- functions then work for all 'Rep' types. -- -- 4. By combining the conversion functions 'from' and 'to' with the -- function that works on 'Rep' types, we obtain a function that works -- on all types that are in the 'Generic' class. -- -- 5. Most types can very easily be made an instance of 'Generic'. For -- example, if the datatype can be represented using GHC's built-in -- approach to generic programming and has an instance for the -- 'GHC.Generics.Generic' class from module "GHC.Generics", then an -- instance of the SOP 'Generic' can automatically be derived. There -- is also Template Haskell code in "Generics.SOP.TH" that allows to -- auto-generate an instance of 'Generic' for most types. -- -- = Example -- -- == Instantiating a datatype for use with SOP generics -- -- Let's assume we have the datatypes: -- -- > data A = C Bool | D A Int | E (B ()) -- > data B a = F | G a Char Bool -- -- To create 'Generic' instances for @A@ and @B@ via "GHC.Generics", we say -- -- > {-# LANGUAGE DeriveGeneric #-} -- > -- > import qualified GHC.Generics as GHC -- > import Generics.SOP -- > -- > data A = C Bool | D A Int | E (B ()) -- > deriving (Show, GHC.Generic) -- > data B a = F | G a Char Bool -- > deriving (Show, GHC.Generic) -- > -- > instance Generic A -- empty -- > instance Generic (B a) -- empty -- -- Now we can convert between @A@ and @'Rep' A@ (and between @B@ and @'Rep' B@). -- For example, -- -- >>> from (D (C True) 3) :: Rep A -- SOP (S (Z (I (C True) :* I 3 :* Nil))) -- >>> to it :: A -- D (C True) 3 -- -- Note that the transformation is shallow: In @D (C True) 3@, the -- inner value @C True@ of type @A@ is not affected by the -- transformation. -- -- For more details about @'Rep' A@, have a look at the -- "Generics.SOP.Universe" module. -- -- == Defining a generic function -- -- As an example of a generic function, let us define a generic -- version of 'Control.DeepSeq.rnf' from the @deepseq@ package. -- -- The type of 'Control.DeepSeq.rnf' is -- -- @ -- NFData a => a -> () -- @ -- -- and the idea is that for a term @x@ of type @a@ in the -- 'Control.DeepSeq.NFData' class, @rnf x@ forces complete evaluation -- of @x@ (i.e., evaluation to /normal form/), and returns @()@. -- -- We call the generic version of this function @grnf@. A direct -- definition in SOP style, making use of structural recursion on the -- sums and products, looks as follows: -- -- @ -- grnf :: ('Generic' a, 'All2' NFData ('Code' a)) => a -> () -- grnf x = grnfS ('from' x) -- -- grnfS :: ('All2' NFData xss) => 'SOP' 'I' xss -> () -- grnfS ('SOP' ('Z' xs)) = grnfP xs -- grnfS ('SOP' ('S' xss)) = grnfS ('SOP' xss) -- -- grnfP :: ('All' NFData xs) => 'NP' 'I' xs -> () -- grnfP 'Nil' = () -- grnfP ('I' x ':*' xs) = x \`deepseq\` (grnfP xs) -- @ -- -- The @grnf@ function performs the conversion between @a@ and @'Rep' a@ -- by applying 'from' and then applies @grnfS@. The type of @grnf@ -- indicates that @a@ must be in the 'Generic' class so that we can -- apply 'from', and that all the components of @a@ (i.e., all the types -- that occur as constructor arguments) must be in the 'NFData' class -- ('All2'). -- -- The function @grnfS@ traverses the outer sum structure of the -- sum of products (note that @'Rep' a = 'SOP' 'I' ('Code' a)@). It -- encodes which constructor was used to construct the original -- argument of type @a@. Once we've found the constructor in question -- ('Z'), we traverse the arguments of that constructor using @grnfP@. -- -- The function @grnfP@ traverses the product structure of the -- constructor arguments. Each argument is evaluated using the -- 'Control.DeepSeq.deepseq' function from the 'Control.DeepSeq.NFData' -- class. This requires that all components of the product must be -- in the 'NFData' class ('All') and triggers the corresponding -- constraints on the other functions. Once the end of the product -- is reached ('Nil'), we return @()@. -- -- == Defining a generic function using combinators -- -- In many cases, generic functions can be written in a much more -- concise way by avoiding the explicit structural recursion and -- resorting to the powerful combinators provided by this library -- instead. -- -- For example, the @grnf@ function can also be defined as a one-liner -- as follows: -- -- @ -- grnf :: ('Generic' a, 'All2' NFData ('Code' a)) => a -> () -- grnf = 'rnf' . 'hcollapse' . 'hcmap' ('Proxy' :: 'Proxy' NFData) ('mapIK' rnf) . 'from' -- @ -- -- 'mapIK' and friends ('mapII', 'mapKI', etc.) are small helpers for working -- with 'I' and 'K' functors, for example 'mapIK' is defined as -- @'mapIK' f = \\ ('I' x) -> 'K' (f x)@ -- -- The following interaction should provide an idea of the individual -- transformation steps: -- -- >>> let x = G 2.5 'A' False :: B Double -- >>> from x -- SOP (S (Z (I 2.5 :* I 'A' :* I False :* Nil))) -- >>> hcmap (Proxy :: Proxy NFData) (mapIK rnf) it -- SOP (S (Z (K () :* K () :* K () :* Nil))) -- >>> hcollapse it -- [(),(),()] -- >>> rnf it -- () -- -- The 'from' call converts into the structural representation. -- Via 'hcmap', we apply 'rnf' to all the components. The result -- is a sum of products of the same shape, but the components are -- no longer heterogeneous ('I'), but homogeneous (@'K' ()@). A -- homogeneous structure can be collapsed ('hcollapse') into a -- normal Haskell list. Finally, 'rnf' actually forces evaluation -- of this list (and thereby actually drives the evaluation of all -- the previous steps) and produces the final result. -- -- == Using a generic function -- -- We can directly invoke 'grnf' on any type that is an instance of -- class 'Generic'. -- -- >>> grnf (G 2.5 'A' False) -- () -- >>> grnf (G 2.5 undefined False) -- *** Exception: Prelude.undefined -- ... -- -- Note that the type of 'grnf' requires that all components of the -- type are in the 'Control.DeepSeq.NFData' class. For a recursive -- datatype such as @B@, this means that we have to make @A@ -- (and in this case, also @B@) an instance of 'Control.DeepSeq.NFData' -- in order to be able to use the 'grnf' function. But we can use 'grnf' -- to supply the instance definitions: -- -- > instance NFData A where rnf = grnf -- > instance NFData a => NFData (B a) where rnf = grnf -- -- = More examples -- -- The best way to learn about how to define generic functions in the SOP style -- is to look at a few simple examples. Examples are provided by the following -- packages: -- -- * @<http://hackage.haskell.org/package/basic-sop basic-sop>@ basic examples, -- * @<http://hackage.haskell.org/package/pretty-sop pretty-sop>@ generic pretty printing, -- * @<http://hackage.haskell.org/package/lens-sop lens-sop>@ generically computed lenses, -- * @<http://hackage.haskell.org/package/json-sop json-sop>@ generic JSON conversions. -- -- The generic functions in these packages use a wide variety of the combinators -- that are offered by the library. -- -- = Paper -- -- A detailed description of the ideas behind this library is provided by -- the paper: -- -- * Edsko de Vries and Andres Löh. -- <http://www.andres-loeh.de/TrueSumsOfProducts True Sums of Products>. -- Workshop on Generic Programming (WGP) 2014. -- -- module Generics.SOP ( -- * Codes and interpretations Generic(..) , Rep , IsProductType , IsEnumType , IsWrappedType , IsNewtype -- * n-ary datatypes , NP(..) , NS(..) , SOP(..) , unSOP , POP(..) , unPOP -- * Metadata , DatatypeInfo(..) , moduleName , datatypeName , constructorInfo , ConstructorInfo(..) , constructorName , FieldInfo(..) , fieldName , HasDatatypeInfo(..) , DatatypeName , ModuleName , ConstructorName , FieldName , Associativity(..) , Fixity -- * Combinators -- ** Constructing products , HPure(..) -- ** Destructing products , hd , tl , Projection , projections , shiftProjection -- ** Application , type (-.->)(..) , fn , fn_2 , fn_3 , fn_4 , Prod , HAp(..) -- ** Lifting / mapping , hliftA , hliftA2 , hliftA3 , hcliftA , hcliftA2 , hcliftA3 , hmap , hzipWith , hzipWith3 , hcmap , hczipWith , hczipWith3 -- ** Constructing sums , Injection , injections , shift , shiftInjection , UnProd , HApInjs(..) , apInjs_NP -- deprecated export , apInjs_POP -- deprecated export -- ** Destructing sums , unZ , HIndex(..) -- ** Dealing with @'All' c@ , hcliftA' , hcliftA2' , hcliftA3' -- ** Comparison , compare_NS , ccompare_NS , compare_SOP , ccompare_SOP -- ** Collapsing , CollapseTo , HCollapse(..) -- ** Folding and sequencing , HTraverse_(..) , hcfoldMap , hcfor_ , HSequence(..) , hsequence , hsequenceK , hctraverse , hcfor -- ** Expanding sums to products , HExpand(..) -- ** Transformation of index lists and coercions , HTrans(..) , hfromI , htoI -- ** Partial operations , fromList -- * Utilities -- ** Basic functors , K(..) , unK , I(..) , unI , (:.:)(..) , unComp -- *** Mapping functions , mapII , mapIK , mapKI , mapKK , mapIII , mapIIK , mapIKI , mapIKK , mapKII , mapKIK , mapKKI , mapKKK -- ** Mapping constraints , All , All2 , cpara_SList , ccase_SList , AllZip , AllZip2 , AllN , AllZipN -- ** Other constraints , Compose , And , Top , LiftedCoercible , SameShapeAs -- ** Singletons , SList(..) , SListI , SListI2 , sList , para_SList , case_SList -- *** Shape of type-level lists , Shape(..) , shape , lengthSList -- ** Re-exports -- Workaround for lack of MIN_TOOL_VERSION macro in Cabal 1.18, see: -- https://github.com/well-typed/generics-sop/issues/3 #ifndef MIN_TOOL_VERSION_haddock #define MIN_TOOL_VERSION_haddock(x,y,z) 0 #endif #if !(defined(__HADDOCK_VERSION__)) || MIN_TOOL_VERSION_haddock(2,14,0) , Proxy(..) -- hidden from old Haddock versions, because it triggers an internal error #endif ) where import Data.Proxy (Proxy(..)) import Generics.SOP.BasicFunctors import Generics.SOP.Classes import Generics.SOP.Constraint import Generics.SOP.Instances () import Generics.SOP.Metadata import Generics.SOP.NP import Generics.SOP.NS import Generics.SOP.Universe import Generics.SOP.Sing -- $setup -- -- >>> :set -XDeriveGeneric -- >>> import qualified GHC.Generics as GHC -- >>> import Generics.SOP -- >>> import Control.DeepSeq -- >>> data B a = F | G a Char Bool deriving (Show, GHC.Generic) -- >>> data A = C Bool | D A Int | E (B ()) deriving (Show, GHC.Generic) -- >>> instance Generic A -- empty -- >>> instance Generic (B a) -- empty -- -- >>> let grnf = rnf . hcollapse . hcmap (Proxy :: Proxy NFData) (\ (I x) -> K (rnf x)) . from