one-liner-0.3.1: src/Generics/OneLiner.hs
-----------------------------------------------------------------------------
-- |
-- Module : Generics.OneLiner
-- License : BSD-style (see the file LICENSE)
--
-- Maintainer : sjoerd@w3future.com
-- Stability : experimental
-- Portability : non-portable
--
-- This module is for writing generic functions on algebraic data types
-- of kind @*@. These data types must be an instance of the `Generic` type
-- class, which can be derived.
--
-----------------------------------------------------------------------------
{-# LANGUAGE
RankNTypes
, TypeFamilies
, TypeOperators
, ConstraintKinds
, FlexibleContexts
, FlexibleInstances
, ScopedTypeVariables
#-}
module Generics.OneLiner (
-- * Producing values
create, createA, ctorIndex,
-- * Traversing values
gmap, gfoldMap, gtraverse,
-- * Combining values
gzipWith, mzipWith, zipWithA,
-- * Single constructor functions
op0, op1, op2,
-- * Types
ADT, ADTRecord, Constraints, For(..)
) where
import GHC.Generics
import GHC.Prim (Constraint)
import Control.Applicative
import Data.Functor.Identity
import Data.Monoid
type family Constraints' (t :: * -> *) (c :: * -> Constraint) :: Constraint
type instance Constraints' V1 c = ()
type instance Constraints' U1 c = ()
type instance Constraints' (f :+: g) c = (Constraints' f c, Constraints' g c)
type instance Constraints' (f :*: g) c = (Constraints' f c, Constraints' g c)
type instance Constraints' (K1 i v) c = c v
type instance Constraints' (M1 i t f) c = Constraints' f c
class ADT' (t :: * -> *) where
ctorIndex' :: t x -> Int
ctorIndex' _ = 0
ctorCount :: proxy (t x) -> Int
ctorCount _ = 1
f0 :: (Constraints' t c, Applicative f)
=> for c -> (forall s. c s => f s) -> [f (t ())]
f1 :: (Constraints' t c, Applicative f)
=> for c -> (forall s. c s => s -> f s) -> t x -> f (t x)
f2 :: (Constraints' t c, Applicative f)
=> for c -> (forall s. c s => s -> s -> f s) -> t x -> t x -> Maybe (f (t x))
instance ADT' V1 where
ctorCount _ = 0
f0 _ _ = []
f1 _ _ = pure
f2 _ _ _ = Just . pure
instance (ADT' f, ADT' g) => ADT' (f :+: g) where
ctorIndex' (L1 l) = ctorIndex' l
ctorIndex' (R1 r) = ctorCount (undefined :: [f ()]) + ctorIndex' r
ctorCount _ = ctorCount (undefined :: [f ()]) + ctorCount (undefined :: [g ()])
f0 for f = map (fmap L1) (f0 for f) ++ map (fmap R1) (f0 for f)
f1 for f (L1 l) = L1 <$> f1 for f l
f1 for f (R1 r) = R1 <$> f1 for f r
f2 for f (L1 a) (L1 b) = fmap (fmap L1) (f2 for f a b)
f2 for f (R1 a) (R1 b) = fmap (fmap R1) (f2 for f a b)
f2 _ _ _ _ = Nothing
instance ADT' U1 where
f0 _ _ = [pure U1]
f1 _ _ = pure
f2 _ _ _ = Just . pure
instance (ADT' f, ADT' g) => ADT' (f :*: g) where
f0 for f = [(:*:) <$> head (f0 for f) <*> head (f0 for f)]
f1 for f (l :*: r) = (:*:) <$> f1 for f l <*> f1 for f r
f2 for f (al :*: ar) (bl :*: br) = liftA2 (:*:) <$> f2 for f al bl <*> f2 for f ar br
instance ADT' (K1 i v) where
f0 _ f = [K1 <$> f]
f1 _ f (K1 v) = K1 <$> f v
f2 _ f (K1 l) (K1 r) = Just $ K1 <$> f l r
instance ADT' f => ADT' (M1 i t f) where
ctorIndex' = ctorIndex' . unM1
ctorCount _ = ctorCount (undefined :: [M1 i t f ()])
f0 for f = map (fmap M1) (f0 for f)
f1 for f = fmap M1 . f1 for f . unM1
f2 for f (M1 l) (M1 r) = fmap (fmap M1) (f2 for f l r)
class ADTRecord' (f :: * -> *) where
instance ADTRecord' U1
instance ADTRecord' (f :*: g)
instance ADTRecord' (K1 i v)
instance ADTRecord' f => ADTRecord' (M1 i t f)
instance ADTRecord' f => ADTRecord' (V1 :+: f)
instance ADTRecord' f => ADTRecord' (f :+: V1)
-- | `Constraints` is a constraint type synonym, containing the constraint requirements for an instance for `t` of class `c`.
-- It requires an instance of class `c` for each component of `t`.
type Constraints t c = Constraints' (Rep t) c
-- | `ADT` is a constraint type synonym. The `Generic` instance can be derived,
-- and any generic representation will be an instance of `ADT'`.
type ADT t = (Generic t, ADT' (Rep t))
-- | `ADTRecord` is a constraint type synonym. An instance is an `ADT` with exactly one constructor.
type ADTRecord t = (ADT t, ADTRecord' (Rep t))
-- | Tell the compiler which class we want to use in the traversal. Should be used like this:
--
-- > (For :: For Show)
--
-- Where @Show@ can be any class.
data For (c :: * -> Constraint) = For
-- | Create a value (one for each constructor), given how to construct the components.
--
-- @
-- `minBound` = `head` `$` `create` (`For` :: `For` `Bounded`) `minBound`
-- `maxBound` = `last` `$` `create` (`For` :: `For` `Bounded`) `maxBound`
-- @
create :: (ADT t, Constraints t c)
=> for c -> (forall s. c s => s) -> [t]
create for f = map runIdentity (createA for (Identity f))
-- | Create a value (one for each constructor), given how to construct the components, under an applicative effect.
--
-- Here's how to implement `get` from the `binary` package:
--
-- @
-- get = getWord8 `>>=` \\ix -> `createA` (`For` :: `For` Binary) get `!!` `fromEnum` ix
-- @
createA :: (ADT t, Constraints t c, Applicative f)
=> for c -> (forall s. c s => f s) -> [f t]
createA for f = map (fmap to) (f0 for f)
-- | Get the index in the lists returned by `create` and `createA` of the constructor of the given value.
--
-- For example, this is the implementation of `put` that generates the binary data that
-- the above implentation of `get` expects:
--
-- @
-- `put` t = `putWord8` (`toEnum` (`ctorIndex` t)) `<>` `gfoldMap` (`For` :: `For` `Binary`) `put` t
-- @
--
-- /Note that this assumes a straightforward `Monoid` instance of `Put` which `binary` unfortunately does not provide./
ctorIndex :: ADT t => t -> Int
ctorIndex = ctorIndex' . from
-- | Map over a structure, updating each component.
gmap :: (ADT t, Constraints t c)
=> for c -> (forall s. c s => s -> s) -> t -> t
gmap for f = runIdentity . gtraverse for (Identity . f)
-- | Map each component of a structure to a monoid, and combine the results.
--
-- If you have a class `Size`, which measures the size of a structure, then this could be the default implementation:
--
-- @
-- size = `succ` `.` `getSum` `.` `gfoldMap` (`For` :: `For` Size) (`Sum` `.` size)
-- @
gfoldMap :: (ADT t, Constraints t c, Monoid m)
=> for c -> (forall s. c s => s -> m) -> t -> m
gfoldMap for f = getConst . gtraverse for (Const . f)
-- | Map each component of a structure to an action, evaluate these actions from left to right, and collect the results.
gtraverse :: (ADT t, Constraints t c, Applicative f)
=> for c -> (forall s. c s => s -> f s) -> t -> f t
gtraverse for f = fmap to . f1 for f . from
-- | Combine two values by combining each component of the structures with the given function.
-- Returns `Nothing` if the constructors don't match.
gzipWith :: (ADT t, Constraints t c)
=> for c -> (forall s. c s => s -> s -> s) -> t -> t -> Maybe t
gzipWith for f l r = runIdentity <$> zipWithA for (\x y -> Identity (f x y)) l r
-- | Combine two values by combining each component of the structures to a monoid, and combine the results.
-- Returns `mempty` if the constructors don't match.
--
-- @
-- `compare` s t = `compare` (`ctorIndex` s) (`ctorIndex` t) `<>` `mzipWith` (`For` :: `For` `Ord`) `compare` s t
-- @
mzipWith :: (ADT t, Constraints t c, Monoid m)
=> for c -> (forall s. c s => s -> s -> m) -> t -> t -> m
mzipWith for f l r = maybe mempty getConst $ zipWithA for (\x y -> Const (f x y)) l r
-- | Combine two values by combining each component of the structures with the given function, under an applicative effect.
-- Returns `Nothing` if the constructors don't match.
zipWithA :: (ADT t, Constraints t c, Applicative f)
=> for c -> (forall s. c s => s -> s -> f s) -> t -> t -> Maybe (f t)
zipWithA for f l r = fmap (fmap to) (f2 for f (from l) (from r))
-- | Implement a nullary operator by calling the operator for each component.
--
-- @
-- `mempty` = `op0` (`For` :: `For` `Monoid`) `mempty`
-- `fromInteger` i = `op0` (`For` :: `For` `Num`) (`fromInteger` i)
-- @
op0 :: (ADTRecord t, Constraints t c)
=> for c -> (forall s. c s => s) -> t
op0 for f = head $ create for f
-- | Implement a unary operator by calling the operator on the components.
-- This is here for consistency, it is the same as `gmap`.
--
-- @
-- `negate` = `op1` (`For` :: `For` `Num`) `negate`
-- @
op1 :: (ADTRecord t, Constraints t c)
=> for c -> (forall s. c s => s -> s) -> t -> t
op1 = gmap
-- | Implement a binary operator by calling the operator on the components.
--
-- @
-- `mappend` = `op2` (`For` :: `For` `Monoid`) `mappend`
-- (`+`) = `op2` (`For` :: `For` `Num`) (`+`)
-- @
op2 :: (ADTRecord t, Constraints t c)
=> for c -> (forall s. c s => s -> s -> s) -> t -> t -> t
op2 for f l r = case gzipWith for f l r of
Just t -> t
Nothing -> error "op2: constructor mismatch should not be possible for ADTRecord"