lol-calculus-1.20160822: Language/LOL/Calculus/Abstraction.hs
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# OPTIONS_GHC -fno-warn-tabs #-}
-- | Generalized DeBruijn abstraction
module Language.LOL.Calculus.Abstraction where
import Control.Applicative (Applicative(..))
import Control.Monad
import Control.Monad.Trans.Class (MonadTrans(..))
import Data.Bool (Bool(..))
import Data.Eq (Eq(..))
import Data.Foldable (Foldable(..))
import Data.Function (($), (.), on)
import Data.Maybe (Maybe(..))
import Data.Ord (Ord(..), Ordering(..))
import Data.String (IsString(..), String)
import Data.Text (Text)
import Data.Text.Buildable (Buildable(..))
import Data.Traversable (Traversable(..))
import Prelude (Int)
import Text.Show (Show(..), ShowS, showChar, showParen, showString)
-- * Type 'Abstraction'
-- | 'Abstraction' @bound@ @expr@ @var@:
-- encodes an 'abstract'-ion
-- over an expression of type @expr@,
-- by segretating its variables between:
--
-- * /bound variables/ of type: @bound@,
-- * and /unbound variables/ (aka. /free variables/) of type: @var@.
--
-- Note that /unbound variables/ may later themselves be made /bound variables/
-- of an enclosing 'Abstraction', effectively encoding
-- /DeBruijn indices/ using Haskell’s /data constructors/,
-- that is, not like in a /traditional DeBruijn indexing/:
-- where an integer is used in each /bound variable/
-- to indicate which one of its enclosing 'Abstraction's is bounding it,
-- but by the nesting of 'Var_Free' data constructors.
-- As a side note, this is also different from the /DeBruijn indexing/
-- encoded at Haskell’s /type level/ by using @GADTs@
-- (done for instance in https://hackage.haskell.org/package/glambda ).
--
-- Moreover, /unbound variables/ are wrapped within a second level of expression
-- in order to improve the time complexity of /traditional DeBruijn indexing/
-- when 'unabstract'-ing (aka. /instantiating/)
-- (see 'Var' and instance 'MonadTrans' of 'Abstraction').
--
-- 'Abstraction' enables:
--
-- * /locally-nameless/ variables (nameless in 'Var_Bound', named in the deepest 'Var_Free');
-- * substitution of /bound variables/ in an expression using /DeBruijn indices/
-- (hence enabling capture-avoiding /β-reduction/
-- and reducing /α-equivalence/ to a structural equality (==));
-- * shifting /DeBruijn indices/ within an expression without traversing it
-- (hence generalizing and speeding up /traditional DeBruijn indices/);
-- * simultaneous substitution of several /bound variables/
-- (hence enabling expressions implementing recursive-@let@).
--
-- __Ressources:__
--
-- * /Bound/, Edward Kmett, 19 August 2013,
-- https://www.schoolofhaskell.com/user/edwardk/bound
newtype Abstraction bound expr var
= Abstraction (expr (Var bound (expr var)))
deriving (Foldable, Functor, Traversable)
instance (Monad expr, Eq bound, Eq1 expr, Show bound)
=> Eq1 (Abstraction bound expr) where
(==#) = (==#) `on` abstract_normalize
instance (Monad expr, Eq bound, Eq1 expr, Eq var, Show var, Show bound)
=> Eq (Abstraction bound expr var) where
(==) = (==#)
instance (Monad expr, Ord bound, Ord1 expr, Show bound)
=> Ord1 (Abstraction bound expr) where
compare1 = compare1 `on` abstract_normalize
instance (Monad expr, Ord bound, Ord1 expr, Ord var, Show var, Show bound)
=> Ord (Abstraction bound expr var) where
compare = compare1
instance (Functor expr, Show bound, Show1 expr)
=> Show1 (Abstraction bound expr) where
showsPrec1 d (Abstraction e) =
showsUnaryWith "Abstraction" d $
(Lift1 `fmap`) `fmap` e
instance (Functor expr, Show bound, Show1 expr, Show var)
=> Show (Abstraction bound expr var) where
showsPrec = showsPrec1
instance Monad expr => Applicative (Abstraction bound expr) where
pure = return
(<*>) = ap
-- | A 'Monad' instance capturing the notion of /variable substitution/,
-- used by 'unabstract' to decrement the /DeBruijn indices/.
instance Monad expr => Monad (Abstraction bound expr) where
return = Abstraction . return . Var_Free . return
Abstraction expr >>= f = Abstraction $ expr >>= \var ->
case var of
Var_Bound bound -> return (Var_Bound bound)
Var_Free e -> e >>= (\(Abstraction ex) -> ex) . f
instance MonadTrans (Abstraction bound) where
lift = Abstraction . return . Var_Free
-- | 'Monad_Module_Left' instance capturing the notion
-- of /variable substitution/ with /capture-avoiding/.
instance Monad_Module_Left (Abstraction bound) where
l >>>= f = l >>= lift . f
-- | WARNING: 'abstract' 'fmap'-s the given expression,
-- thus repetitive 'abstract'-ings have a quadratic time-complexity.
abstract
:: Monad expr
=> (var -> Maybe bound)
-> expr var
-> Abstraction bound expr var
abstract f = Abstraction . fmap (\var ->
case f var of
Nothing -> Var_Free (return var)
Just b -> Var_Bound b)
-- | Aka. /instantiating/.
unabstract
:: Monad expr
=> (bound -> expr var)
-> Abstraction bound expr var
-> expr var
unabstract unbound (Abstraction ex) = ex >>= \var ->
case var of
Var_Bound b -> unbound b
Var_Free v -> v
-- | @'abstract_normalize'@ normalize
-- the possible placements of 'Var_Free' in 'Abstraction'
-- by moving them all to the leaves of the 'abstract'-ed expression.
--
-- This gives /traditional DeBruijn indices/ for /bound variables/.
abstract_normalize
:: Monad expr
=> Abstraction bound expr var
-> expr (Var bound var)
abstract_normalize (Abstraction expr) = expr >>= \var ->
case var of
Var_Bound bound -> return $ Var_Bound bound
Var_Free e -> Var_Free `fmap`{-on var of expr-} e
-- | Convert from /traditional DeBruijn indices/
-- to /generalized DeBruijn indices/,
-- by wrapping all the leaves within the 'Monad'
-- of the given expression.
--
-- This requires a full traversal of the given expression.
abstract_generalize
:: Monad expr
=> expr (Var bound var)
-> Abstraction bound expr var
abstract_generalize = Abstraction .
((return{-of expr-}
`fmap`{-on var of Var-})
`fmap`{-on var of expr-})
-- ** Class 'Monad_Module_Left'
-- | Like ('>>=') but whose 'Monad' is within a wrapping type @left@
-- (aka. /left module over a monad/).
--
-- __Laws:__
--
-- ('>>>=') should satisfy the following equations
-- in order to be used within a 'Monad' instance:
--
-- @
-- ('>>>=' 'return') = id
-- ('>>>=' (('>>=' g) . f)) = ('>>>=' f) . ('>>>=' g)
-- @
--
-- If @left@ has a 'MonadTrans' instance, then:
--
-- @
-- ('>>>=' f) = ('>>=' ('lift' . f))
-- @
--
-- which implies the above equations,
-- see 'MonadTrans' instance of ('Abstraction' @bound@).
--
-- __Uses:__
--
-- * Useful for expression constructors containing 'Abstraction' data.
--
-- __Ressources:__
--
-- * André Hirschowitz, Marco Maggesi, /Modules over monads and initial semantics/.
-- Information and Computation 208 (2010), pp. 545-564,
-- http://www.sciencedirect.com/science/article/pii/S0890540109002405
class Monad_Module_Left left where
(>>>=) :: Monad expr
=> left expr var
-> (var -> expr bound)
-> left expr bound
infixl 1 >>>=
-- ** Type 'Var'
-- | 'Var' @bound@ @var@: a variable segregating between:
--
-- * 'Var_Bound', containing data of type @bound@,
-- considered /bound/ by the first enclosing 'Abstraction',
-- hence playing the role of a @Zero@ in /DeBruijn indexing/ terminology.
--
-- Note that the presence of this @bound@ enables the substitution
-- of a 'Var_Bound' by different values,
-- which is used to keep the 'Var_Name' given in the source code,
-- (note that it could also be used to implement a @recursive-let@).
--
-- * 'Var_Free', containing data of type @var@,
-- considered /free/ with respect to the first enclosing 'Abstraction',
-- hence playing the role of a @Succ@ in /DeBruijn indexing/ terminology.
--
-- Note that @var@ is not constrained to be itself a 'Var',
-- this in order to make it possible in 'Abstraction'
-- to insert @expr@ in between the @Succ@ nesting,
-- which optimizes the /DeBruijn indexing/ when 'unabstract'-ing,
-- by avoiding to traverse ('fmap') an @expr@ to @Succ@ its variables
-- (see instance 'MonadTrans' for 'Abstraction').
data Var bound var
= Var_Bound bound -- ^ @Zero@
| Var_Free var -- ^ @Succ@
deriving (Eq, Foldable, Functor, Ord, Show, Traversable)
instance (Buildable bound, Buildable var) => Buildable (Var bound var) where
build var =
case var of
Var_Bound b -> build b
Var_Free f -> build f
-- | A convenient operator for 'abstract'-ing.
(=?) :: Eq a => a -> a -> Maybe (Suggest a)
(=?) x y = if x == y then Just (Suggest x) else Nothing
-- | A convenient type synonym for clarity.
type Var_Name = Text
-- | A convenient class synonym for brievety.
class (Show var, Buildable var) => Variable var
instance Variable Var_Name
instance (Variable bound, Variable var) => Variable (Var bound var)
instance Variable var => Variable (Suggest var)
-- * Higher-order @Prelude@ classes
-- ** Class 'Eq1'
-- | Lift the 'Eq' class to unary type constructors,
-- to avoid the @UndecidableInstances@ language extension.
--
-- __Ressources:__
--
-- * /Simulating Quantified Class Constraints/, Valery Trifonov, 2003,
-- http://flint.cs.yale.edu/trifonov/papers/sqcc.pdf
-- * /prelude-extras/, Edward Kmett, 2011,
-- https://hackage.haskell.org/package/prelude-extras
-- * /base/ 'Data.Functor.Classes', Ross Paterson, 2013,
-- https://hackage.haskell.org/package/base/docs/Data-Functor-Classes.html
class Eq1 f where
(==#) :: (Eq a, Show a) => f a -> f a -> Bool
class Eq1 f => Ord1 f where
compare1 :: Ord a => f a -> f a -> Ordering
-- ** Class 'Show1'
-- | Lift the 'Show' class to unary type constructors,
-- to avoid the @UndecidableInstances@ language extension.
class Show1 f where
showsPrec1 :: Show a => Int -> f a -> ShowS
showsUnaryWith :: (Show1 f, Show a) => String -> Int -> f a -> ShowS
showsUnaryWith name d x =
showParen (d > 10) $
showString name . showChar ' ' . showsPrec1 11 x
-- ** Type 'Lift1'
-- | Lift the 'Lift' class to unary type constructors,
-- to avoid the @UndecidableInstances@ language extension.
newtype Lift1 f a = Lift1 { lower1 :: f a }
deriving (Functor, Foldable, Traversable, Eq1, Ord1, Show1)
instance (Eq1 f, Eq a, Show a) => Eq (Lift1 f a) where (==) = (==#)
instance (Ord1 f, Ord a, Show a) => Ord (Lift1 f a) where compare = compare1
instance (Show1 f, Show a) => Show (Lift1 f a) where showsPrec = showsPrec1
-- ** Type 'Suggest'
-- | A convenient wrapper to include data ignored by /α-equivalence/.
newtype Suggest n
= Suggest n
deriving (Functor, Show)
-- | Always return 'True', in order to be transparent for 'alpha_equiv'.
instance Eq (Suggest n) where
_ == _ = True
instance Ord (Suggest n) where
_ `compare` _ = EQ
instance Buildable var
=> Buildable (Suggest var) where
build (Suggest var) = build var
instance IsString x => IsString (Suggest x) where
fromString = Suggest . fromString