packages feed

code-conjure-0.5.2: src/Conjure/Conjurable/Derive.hs

{-# LANGUAGE TemplateHaskell, CPP #-}
-- |
-- Module      : Conjure.Conjurable.Derive
-- Copyright   : (c) 2019-2021 Rudy Matela
-- License     : 3-Clause BSD  (see the file LICENSE)
-- Maintainer  : Rudy Matela <rudy@matela.com.br>
--
-- Allows automatic derivation of 'Conjurable' typeclass instances.
module Conjure.Conjurable.Derive
  ( deriveConjurable
  , deriveConjurableCascading
  , deriveConjurableIfNeeded
  )
where

import Test.LeanCheck
import Test.LeanCheck.Derive
import Test.LeanCheck.Utils
import Conjure.Expr hiding (mkName, Name, isInstanceOf)
import Conjure.Conjurable hiding (Name)
import Data.Express.Utils (primeCycle)
import Data.Express.Utils.TH

import Control.Monad
import Data.Char
import Data.List
import Language.Haskell.TH.Lib

#if __GLASGOW_HASKELL__ < 710
import Data.Functor ((<$>))
#endif

-- | Derives an 'Conjurable' instance for the given type 'Name'.
--
-- This function needs the @TemplateHaskell@ extension.
--
-- If '-:', '->:', '->>:', '->>>:', ... are not in scope,
-- this will derive them as well.
--
-- For now,
-- this function only derives
-- 'conjureEquality',
-- 'conjureTiers' and
-- 'conjureExpress'
-- and does not derive
-- 'conjureSubTypes',
-- 'conjureArgumentCases' and
-- 'conjureSize'.
-- These will be added in future versions.
-- If you plan to use features that depend on these functionalities,
-- please define your instances manually.
deriveConjurable :: Name -> DecsQ
deriveConjurable  =  deriveWhenNeededOrWarn ''Conjurable reallyDerive
  where
  reallyDerive  =  reallyDeriveConjurableWithRequisites

-- | Same as 'deriveConjurable' but does not warn when instance already exists
--   ('deriveConjurable' is preferable).
--
-- For now,
-- this function only derives
-- 'conjureEquality',
-- 'conjureTiers' and
-- 'conjureExpress'
-- and does not derive
-- 'conjureSubTypes',
-- 'conjureArgumentCases' and
-- 'conjureSize'.
-- These will be added in future versions.
-- If you plan to use features that depend on these functionalities,
-- please define your instances manually.
deriveConjurableIfNeeded :: Name -> DecsQ
deriveConjurableIfNeeded  =  deriveWhenNeeded ''Conjurable reallyDerive
  where
  reallyDerive  =  reallyDeriveConjurableWithRequisites

-- | Derives a 'Conjurable' instance for a given type 'Name'
--   cascading derivation of type arguments as well.
--
-- For now,
-- this function only derives
-- 'conjureEquality',
-- 'conjureTiers' and
-- 'conjureExpress'
-- and does not derive
-- 'conjureSubTypes',
-- 'conjureArgumentCases' and
-- 'conjureSize'.
-- These will be added in future versions.
-- If you plan to use features that depend on these functionalities,
-- please define your instances manually.
deriveConjurableCascading :: Name -> DecsQ
deriveConjurableCascading  =  deriveWhenNeeded ''Conjurable reallyDerive
  where
  reallyDerive t  =  concat
                 <$> sequence [ deriveListableCascading t
                              , deriveNameCascading t
                              , deriveExpressCascading t
                              , reallyDeriveConjurableCascading t ]

reallyDeriveConjurableWithRequisites :: Name -> DecsQ
reallyDeriveConjurableWithRequisites t  =  concat <$>
  sequence [ deriveListableIfNeeded t
           , deriveNameIfNeeded t
           , deriveExpressIfNeeded t
           , reallyDeriveConjurable t ]

reallyDeriveConjurable :: Name -> DecsQ
reallyDeriveConjurable t  =  do
  isEq <- t `isInstanceOf` ''Eq
  isOrd <- t `isInstanceOf` ''Ord
  (nt,vs) <- normalizeType t
#if __GLASGOW_HASKELL__ >= 710
  cxt <- sequence [ [t| $(conT c) $(return v) |]
#else
  -- template-haskell <= 2.9.0.0:
  cxt <- sequence [ classP c [return v]
#endif
                  | c <- [''Conjurable, ''Listable, ''Express] ++ [''Eq | isEq] ++ [''Ord | isOrd]
                  , v <- vs]
  cs <- typeConstructorsArgNames t
  asName <- newName "x"
  let withTheReturnTypeOfs = deriveWithTheReturnTypeOfs $ [length ns | (_,ns) <- cs]
  let inst = [d| instance Conjurable $(return nt) where
                   conjureExpress   =  reifyExpress
                   conjureEquality  =  reifyEquality
                   conjureTiers     =  reifyTiers |]
  -- withTheReturnTypeOfs |++| (cxt |=>| inst)
  cxt |=>| inst `addFun` deriveSize t `mergeI` deriveSubTypes t `mergeI` deriveCases t
-- TODO: derive conjureCases, e.g.:
-- conjureCases mx  =  [ value "Nothing" (Nothing -: mx)
--                     , value "Just" (Just ->: mx) :$ hole x
--                     ]

deriveCases :: Name -> DecsQ
deriveCases t  =  do
  n <- newName "x"
  (nt,vs) <- normalizeType t
  cs <- typeConstructorsArgNames t
  let lets = [letin n c ns | (c,ns) <- cs]
  let rhs = foldr (\e1 e2 -> [| $e1 : $e2 |]) [|[]|] lets
  [d| instance Conjurable $(return nt) where
        conjureCases $(varP n) = $rhs |]
  where
  letin :: Name -> Name -> [Name] -> ExpQ
  letin x c ns = do
    und <- VarE <$> lookupValN "undefined"
    let lhs = conP c (map varP ns)
    let rhs = return $ foldl AppE (ConE c) [und | _ <- ns]
    let retTypeOf = varE $ mkName $ "-" ++ replicate (length ns) '>' ++ ":"
    let ins = foldl (\e1 e2 -> [| $e1 :$ $e2 |])
                [| value $(stringE $ nameBase c) ($retTypeOf $(conE c) $(varE x)) |]
                [ [| hole $(varE n) |] | n <- ns ]
    [| let $lhs = $rhs `asTypeOf` $(varE x) in $ins |]

deriveSubTypes :: Name -> DecsQ
deriveSubTypes t  =  do
  n <- newName "x"
  (nt,vs) <- normalizeType t
  cs <- typeConstructorsArgNames t
  let lets = [letin n c ns | (c,ns) <- cs, not (null ns)]
  let rhs = foldr0 (\e1 e2 -> [| $e1 . $e2 |]) [|id|] lets
  [d| instance Conjurable $(return nt) where
        conjureSubTypes $(varP n) = $rhs |]
  where
  letin :: Name -> Name -> [Name] -> ExpQ
  letin x c ns = do
    und <- VarE <$> lookupValN "undefined"
    let lhs = conP c (map varP ns)
    let rhs = return $ foldl AppE (ConE c) [und | _ <- ns]
    let bot = foldl1 (\e1 e2 -> [| $e1 . $e2 |])
                     [ [| conjureType $(varE n) |] | n <- ns ]
    [| let $lhs = $rhs `asTypeOf` $(varE x) in $bot |]

deriveSize :: Name -> DecsQ
deriveSize t  =  ((:[]) . FunD (mkName "conjureSize")) <$> deriveSizeClauses t

deriveSizeClauses :: Name -> Q [Clause]
deriveSizeClauses t  =  mapM (uncurry mkClause) =<< typeConstructors t
  where
  mkClause :: Name -> [Type] -> Q Clause
  mkClause n as  =  clause pat body []
    where
    ns  =  take (length as) $ map mkName (variableNamesFromTemplate "x")
    pat  =  [conP n [varP n | n <- ns]]
    body  =  normalB $ foldl (\e n -> [| $e + conjureSize $(varE n) |]) [| 1 |] ns

-- Not only really derive Conjurable instances,
-- but cascade through argument types.
reallyDeriveConjurableCascading :: Name -> DecsQ
reallyDeriveConjurableCascading  =  reallyDeriveCascading ''Conjurable reallyDeriveConjurable

deriveWithTheReturnTypeOfs :: [Int] -> DecsQ
deriveWithTheReturnTypeOfs  =
  fmap concat . mapM deriveWithTheReturnTypeOf . nubSort

deriveWithTheReturnTypeOf :: Int -> DecsQ
deriveWithTheReturnTypeOf n  =  do
  mf <- lookupValueName name
  case mf of
    Nothing -> reallyDeriveWithTheReturnTypeOf n
    Just _  -> return []
  where
  name  =  "-" ++ replicate n '>' ++ ":"

reallyDeriveWithTheReturnTypeOf :: Int -> DecsQ
reallyDeriveWithTheReturnTypeOf n  =  do
  td <- sigD name theT
  vd <- [d| $(varP name) = const |]
  return $ td:vd
  where
  theT  =  bind [t| $(theFunT) -> $(last vars) -> $(theFunT) |]
  theFunT  =  foldr1 funT vars
  funT t1 t2  =  [t| $(t1) -> $(t2) |]
  vars  =  map (varT . mkName) . take (n+1) . primeCycle $ map (:"") ['a'..'z']
  name  =  mkName $ "-" ++ replicate n '>' ++ ":"
#if __GLASGOW_HASKELL__ >= 800
  bind  =  id -- unbound variables are automatically bound
#else
  bind  =  toBoundedQ
#endif

addFun :: DecsQ -> DecsQ -> DecsQ
qds1 `addFun` qds2 = do ds1 <- qds1
                        ds2 <- qds2
                        return $ ds1 `m` ds2
  where
#if __GLASGOW_HASKELL__ < 800
  [InstanceD   c ts ds1] `m` ds2 = [InstanceD   c ts (ds1 ++ ds2)]
#else
  [InstanceD o c ts ds1] `m` ds2 = [InstanceD o c ts (ds1 ++ ds2)]
#endif