{-# 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