VKHS-0.1.2: src/Data/Label/Derive.hs
{-
Copyright (c) Erik Hesselink & Sebastiaan Visser 2008
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the author nor the names of his contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
-}
{-# OPTIONS -fno-warn-orphans #-}
{-# LANGUAGE
TemplateHaskell
, OverloadedStrings
, FlexibleContexts
, FlexibleInstances
, TypeOperators
#-}
module Data.Label.Derive
( mkLabels
, mkLabel
, mkLabelsWith
, mkLabelsMono
, mkLabelsNoTypes
) where
import Control.Arrow
import Control.Category
import Control.Monad
import Data.Char
import Data.Function (on)
import Data.Label.Abstract
import Data.Label.Pure ((:->))
import Data.Label.Maybe ((:~>))
import Data.List
import Data.Ord
import Data.String
import Language.Haskell.TH
import Language.Haskell.TH.Syntax
import Prelude hiding ((.), id)
-- Throw a fclabels specific error.
fclError :: String -> a
fclError err = error ("Data.Label.Derive: " ++ err)
-- | Derive lenses including type signatures for all the record selectors for a
-- collection of datatypes. The types will be polymorphic and can be used in an
-- arbitrary context.
mkLabels :: [Name] -> Q [Dec]
mkLabels = mkLabelsWith defaultMakeLabel
-- | Derive lenses including type signatures for all the record selectors in a
-- single datatype. The types will be polymorphic and can be used in an
-- arbitrary context.
mkLabel :: Name -> Q [Dec]
mkLabel = mkLabels . return
-- | Generate the label name from the record field name.
-- For instance, @drop 1 . dropWhile (/='_')@ creates a label @val@ from a
-- record @Rec { rec_val :: X }@.
mkLabelsWith :: (String -> String) -> [Name] -> Q [Dec]
mkLabelsWith makeLabel = liftM concat . mapM (derive1 makeLabel True False)
-- | Derive lenses including type signatures for all the record selectors in a
-- datatype. The signatures will be concrete and can only be used in the
-- appropriate context.
mkLabelsMono :: [Name] -> Q [Dec]
mkLabelsMono = liftM concat . mapM (derive1 defaultMakeLabel True True)
-- | Derive lenses without type signatures for all the record selectors in a
-- datatype.
mkLabelsNoTypes :: [Name] -> Q [Dec]
mkLabelsNoTypes = liftM concat . mapM (derive1 defaultMakeLabel False False)
-- Helpers to generate all labels for one datatype.
derive1 :: (String -> String) -> Bool -> Bool -> Name -> Q [Dec]
derive1 makeLabel signatures concrete datatype =
do i <- reify datatype
let -- Only process data and newtype declarations, filter out all
-- constructors and the type variables.
(tyname, cons, vars) =
case i of
TyConI (DataD _ n vs cs _) -> (n, cs, vs)
TyConI (NewtypeD _ n vs c _) -> (n, [c], vs)
_ -> fclError "Can only derive labels for datatypes and newtypes."
-- We are only interested in lenses of record constructors.
recordOnly = groupByCtor [ (f, n) | RecC n fs <- cons, f <- fs ]
concat `liftM`
mapM (derive makeLabel signatures concrete tyname vars (length cons))
recordOnly
where groupByCtor = map (\xs -> (fst (head xs), map snd xs))
. groupBy ((==) `on` (fst3 . fst))
. sortBy (comparing (fst3 . fst))
where fst3 (a, _, _) = a
-- Generate the code for the labels.
-- | Generate a name for the label. If the original selector starts with an
-- underscore, remove it and make the next character lowercase. Otherwise,
-- add 'l', and make the next character uppercase.
defaultMakeLabel :: String -> String
defaultMakeLabel field =
case field of
'_' : c : rest -> toLower c : rest
f : rest -> 'l' : toUpper f : rest
n -> fclError ("Cannot derive label for record selector with name: " ++ n)
derive :: (String -> String)
-> Bool -> Bool -> Name -> [TyVarBndr] -> Int
-> (VarStrictType, [Name]) -> Q [Dec]
derive makeLabel signatures concrete tyname vars total ((field, _, fieldtyp), ctors) =
do (sign, body) <-
if length ctors == total
then function derivePureLabel
else function deriveMaybeLabel
return $
if signatures
then [sign, inline, body]
else [inline, body]
where
-- Generate an inline declaration for the label.
inline = PragmaD (InlineP labelName (InlineSpec True True (Just (True, 0))))
labelName = mkName (makeLabel (nameBase field))
-- Build a single record label definition for labels that might fail.
deriveMaybeLabel = (if concrete then mono else poly, body)
where
mono = forallT prettyVars (return []) [t| $(inputType) :~> $(return prettyFieldtyp) |]
poly = forallT forallVars (return [])
[t| (ArrowChoice $(arrow), ArrowZero $(arrow))
=> Lens $(arrow) $(inputType) $(return prettyFieldtyp) |]
body = [| lens (fromRight . $(getter)) (fromRight . $(setter)) |]
where
getter = [| arr (\ p -> $(caseE [|p|] (cases (bodyG [|p|] ) ++ wild))) |]
setter = [| arr (\(v, p) -> $(caseE [|p|] (cases (bodyS [|p|] [|v|]) ++ wild))) |]
cases b = map (\ctor -> match (recP ctor []) (normalB b) []) ctors
wild = [match wildP (normalB [| Left () |]) []]
bodyS p v = [| Right $( record p field v ) |]
bodyG p = [| Right $( varE field `appE` p ) |]
-- Build a single record label definition for labels that cannot fail.
derivePureLabel = (if concrete then mono else poly, body)
where
mono = forallT prettyVars (return []) [t| $(inputType) :-> $(return prettyFieldtyp) |]
poly = forallT forallVars (return [])
[t| Arrow $(arrow) => Lens $(arrow) $(inputType) $(return prettyFieldtyp) |]
body = [| lens $(getter) $(setter) |]
where
getter = [| arr $(varE field) |]
setter = [| arr (\(v, p) -> $(record [| p |] field [| v |])) |]
-- Compute the type (including type variables of the record datatype.
inputType = return $ foldr (flip AppT) (ConT tyname) (map tvToVarT (reverse prettyVars))
-- Convert a type variable binder to a regular type variable.
tvToVarT (PlainTV tv) = VarT tv
tvToVarT _ = fclError "No support for special-kinded type variables."
-- Prettify type variables.
arrow = varT (mkName "~>")
prettyVars = map prettyTyVar vars
forallVars = PlainTV (mkName "~>") : prettyVars
prettyFieldtyp = prettyType fieldtyp
-- Q style record updating.
record rec fld val = val >>= \v -> recUpdE rec [return (fld, v)]
-- Build a function declaration with both a type signature and body.
function (s, b) = liftM2 (,)
(sigD labelName s)
(funD labelName [ clause [] (normalB b) [] ])
fromRight :: (ArrowChoice a, ArrowZero a) => a (Either b d) d
fromRight = zeroArrow ||| returnA
-------------------------------------------------------------------------------
-- Helper functions to prettify type variables.
prettyName :: Name -> Name
prettyName tv = mkName (takeWhile (/='_') (show tv))
prettyTyVar :: TyVarBndr -> TyVarBndr
prettyTyVar (PlainTV tv ) = PlainTV (prettyName tv)
prettyTyVar (KindedTV tv ki) = KindedTV (prettyName tv) ki
prettyType :: Type -> Type
prettyType (ForallT xs cx ty) = ForallT (map prettyTyVar xs) (map prettyPred cx) (prettyType ty)
prettyType (VarT nm ) = VarT (prettyName nm)
prettyType (AppT ty tx ) = AppT (prettyType ty) (prettyType tx)
prettyType (SigT ty ki ) = SigT (prettyType ty) ki
prettyType ty = ty
prettyPred :: Pred -> Pred
prettyPred (ClassP nm tys) = ClassP (prettyName nm) (map prettyType tys)
prettyPred (EqualP ty tx ) = EqualP (prettyType ty) (prettyType tx)
-- IsString instances for TH types.
instance IsString Exp where
fromString = VarE . mkName
instance IsString (Q Pat) where
fromString = varP . mkName
instance IsString (Q Exp) where
fromString = varE . mkName