bound-simple-0.2.0.0: src/Bound/Simple.hs
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE DerivingStrategies #-}
{-# language DeriveAnyClass #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# language CPP #-}
{-# options_ghc -Wno-unused-top-binds #-}
{-|
Module : Bound.Simple
Description : Lightweight implementation of 'bound'
Copyright : (c) 2013 Edward Kmett, 2021 Marco Zocca
License : BSD
Maintainer : github.com/ocramz
Stability : experimental
Portability : POSIX
= Example
The 'whnf' function in this example shows how to beta-reduce a term of the untyped lambda calculus.
Note : the Show instance of Exp depends on its Show1 instance (since Exp has one type parameter), which can be derived 'Generically'. This works on most recent versions of GHC (>= 8.6.1).
Note 2 : the example below requires language extensions `DeriveFunctor`, `DeriveFoldable`, `DeriveTraversable` and `DerivingVia`.
@
import Bound.Simple (Scope, Bound(..), abstract1, instantiate1)
import Data.Functor.Classes (Show1)
import Data.Functor.Classes.Generic (Generically(..))
import GHC.Generics (Generic1)
infixl 9 :\@
data Exp a = V a | Exp a :@ Exp a | Lam (Scope () Exp a)
deriving (Show, Functor, Foldable, Traversable, Generic1)
deriving (Show1) via Generically Exp
instance Applicative Exp where pure = V; k \<*\> m = ap k m
instance Monad Exp where
return = V
V a >>= f = f a
(x :\@ y) >>= f = (x >>= f) :\@ (y >>= f)
Lam e >>= f = Lam (e '>>>=' f)
lam :: Eq a => a -> Exp a -> Exp a
lam v b = Lam ('abstract1' v b)
whnf :: Exp a -> Exp a
whnf (e1 \:\@ e2) = case whnf e1 of
Lam b -> whnf ('instantiate1' e2 b)
f' -> f' :\@ e2
whnf e = e
main :: IO ()
main = do
let term = lam 'x' (V 'x') :\@ V 'y'
print term -- Lam (Scope (V (B ()))) :\@ V 'y'
print $ whnf term -- V 'y'
@
-}
module Bound.Simple (Bound(..)
, Scope, toScope, fromScope
, Var
-- * Abstraction
, abstract, abstract1
-- * Instantiation
, instantiate, instantiate1
, bindings
, hoistScope
, closed
, substitute
, substituteVar
-- ** Predicates
, isClosed
-- * Utils
, Generically(..)
) where
import Control.Monad (ap, liftM)
import Control.Monad.Trans.Class (MonadTrans(..))
import Data.Functor.Classes (Show2(..), Show1(..), showsUnaryWith, showsPrec1, liftShowsPrec2, Eq2(..), Eq1(..), eq1, liftEq, liftEq2)
import GHC.Generics (Generic1)
import Data.Functor.Classes.Generic (Generically(..))
infixl 9 :@
data Exp a = V a | Exp a :@ Exp a | Lam (Scope () Exp a)
deriving (Show, Eq, Functor,Foldable,Traversable, Generic1)
deriving (Show1, Eq1) via Generically Exp
-- instance Applicative Exp where pure = V; (<*>) = ap
-- instance Monad Exp where
-- return = V
-- V a >>= f = f a
-- (x :@ y) >>= f = (x >>= f) :@ (y >>= f)
-- Lam e >>= f = Lam (e >>>= f)
-- lam :: Eq a => a -> Exp a -> Exp a
-- lam v b = Lam (abstract1 v b)
-- whnf :: Exp a -> Exp a
-- whnf (f :@ a) = case whnf f of
-- Lam b -> whnf (instantiate1 a b)
-- f' -> f' :@ a
-- whnf e = e
-- test :: IO ()
-- test = do
-- let term = lam 'x' (V 'x') :@ V 'y'
-- print $ term
-- print $ whnf term
data Var b a = B b -- ^ bound variables
| F a -- ^ free variables
deriving (Eq, Show, Functor, Foldable, Traversable)
instance Eq2 Var where
liftEq2 f _ (B a) (B c) = f a c
liftEq2 _ g (F b) (F d) = g b d
liftEq2 _ _ _ _ = False
instance Eq b => Eq1 (Var b) where liftEq = liftEq2 (==)
instance Show2 Var where
liftShowsPrec2 f _ _ _ d (B a) = showsUnaryWith f "B" d a
liftShowsPrec2 _ _ h _ d (F a) = showsUnaryWith h "F" d a
instance Show b => Show1 (Var b) where
liftShowsPrec = liftShowsPrec2 showsPrec showList
-- | @'Scope' b f a@ is an @f@ expression with bound variables in @b@,
-- and free variables in @a@
newtype Scope b f a = Scope { unscope :: f (Var b a) }
deriving (Generic1)
deriving (Show1, Eq1) via (Generically (Scope b f))
-- instance (Eq b, Eq1 f) => Eq1 (Scope b f) where
-- liftEq f m n = liftEq (liftEq f) (unscope m) (unscope n)
-- instance (Show b, Show1 f) => Show1 (Scope b f) where
-- liftShowsPrec f g d m = showParen (d > 10) $
-- showString "Scope " . liftShowsPrec (liftShowsPrec f g) (liftShowList f g) 11 (unscope m)
instance (Eq e, Functor m, Eq1 m, Eq a) => Eq (Scope e m a) where (==) = eq1
instance (Show e, Functor m, Show1 m, Show a) => Show (Scope e m a) where showsPrec = showsPrec1
-- | @'fromScope'@ is just another name for 'unscope'
fromScope :: Scope b f a -> f (Var b a)
fromScope = unscope
{-# INLINE fromScope #-}
-- | @'toScope'@ is just another name for 'Scope'
toScope :: f (Var b a) -> Scope b f a
toScope = Scope
{-# INLINE toScope #-}
class Bound t where
-- | Perform substitution
--
-- If @t@ is an instance of @MonadTrans@ and you are compiling on GHC >= 7.4, then this
-- gets the default definition:
--
-- @m '>>>=' f = m '>>=' 'lift' '.' f@
(>>>=) :: Monad f => t f a -> (a -> f c) -> t f c
#if defined(__GLASGOW_HASKELL__)
default (>>>=) :: (MonadTrans t, Monad f, Monad (t f)) =>
t f a -> (a -> f c) -> t f c
m >>>= f = m >>= lift . f
{-# INLINE (>>>=) #-}
#endif
instance Bound (Scope b) where
Scope m >>>= f = Scope $ m >>= \v -> case v of
B b -> return (B b)
F a -> liftM F (f a)
{-# INLINE (>>>=) #-}
instance Functor f => Functor (Scope b f) where
fmap f (Scope a) = Scope (fmap (fmap f) a)
{-# INLINE fmap #-}
-- | @'toList'@ is provides a list (with duplicates) of the free variables
instance Foldable f => Foldable (Scope b f) where
foldMap f (Scope a) = foldMap (foldMap f) a
{-# INLINE foldMap #-}
instance Traversable f => Traversable (Scope b f) where
traverse f (Scope a) = Scope <$> traverse (traverse f) a
{-# INLINE traverse #-}
#if !MIN_VERSION_base(4,8,0)
instance (Functor f, Monad f) => Applicative (Scope b f) where
#else
instance Monad f => Applicative (Scope b f) where
#endif
pure a = Scope (return (F a))
{-# INLINE pure #-}
(<*>) = ap
{-# INLINE (<*>) #-}
-- | The monad permits substitution on free variables, while preserving
-- bound variables
instance Monad f => Monad (Scope b f) where
#if __GLASGOW_HASKELL__ < 710
return a = Scope (return (F a))
{-# INLINE return #-}
#endif
Scope e >>= f = Scope $ e >>= \v -> case v of
B b -> return (B b)
F a -> unscope (f a)
{-# INLINE (>>=) #-}
-- | @'substitute' a p w@ replaces the free variable @a@ with @p@ in @w@.
--
-- >>> substitute "hello" ["goodnight","Gracie"] ["hello","!!!"]
-- ["goodnight","Gracie","!!!"]
substitute :: (Monad f, Eq a) => a -> f a -> f a -> f a
substitute a p w = w >>= \b -> if a == b then p else return b
{-# INLINE substitute #-}
-- | @'substituteVar' a b w@ replaces a free variable @a@ with another free variable @b@ in @w@.
--
-- >>> substituteVar "Alice" "Bob" ["Alice","Bob","Charlie"]
-- ["Bob","Bob","Charlie"]
substituteVar :: (Functor f, Eq a) => a -> a -> f a -> f a
substituteVar a p = fmap (\b -> if a == b then p else b)
{-# INLINE substituteVar #-}
-- | Capture some free variables in an expression to yield
-- a 'Scope' with bound variables in @b@
--
-- >>> :m + Data.List
-- >>> abstract (`elemIndex` "bar") "barry"
-- Scope [B 0,B 1,B 2,B 2,F 'y']
abstract :: Functor f => (a -> Maybe b) -> f a -> Scope b f a
abstract f e = Scope (fmap k e) where
k y = case f y of
Just z -> B z
Nothing -> F y
{-# INLINE abstract #-}
-- | Abstract over a single variable
--
-- >>> abstract1 'x' "xyz"
-- Scope [B (),F 'y',F 'z']
abstract1 :: (Functor f, Eq a) => a -> f a -> Scope () f a
abstract1 a = abstract (\b -> if a == b then Just () else Nothing)
-- | Enter a scope, instantiating all bound variables
--
-- >>> :m + Data.List
-- >>> instantiate (\x -> [toEnum (97 + x)]) $ abstract (`elemIndex` "bar") "barry"
-- "abccy"
instantiate :: Monad f => (b -> f a) -> Scope b f a -> f a
instantiate k e = unscope e >>= \v -> case v of
B b -> k b
F a -> return a
{-# INLINE instantiate #-}
-- | Enter a 'Scope' that binds one variable, instantiating it
--
-- >>> instantiate1 "x" $ Scope [B (),F 'y',F 'z']
-- "xyz"
instantiate1 :: Monad f => f a -> Scope n f a -> f a
instantiate1 e = instantiate (const e)
{-# INLINE instantiate1 #-}
hoistScope :: (f (Var b a) -> g (Var b a)) -> Scope b f a -> Scope b g a
hoistScope f = Scope . f . unscope
{-# INLINE hoistScope #-}
-- | Perform a change of variables on bound variables.
mapBound :: Functor f => (b -> b') -> Scope b f a -> Scope b' f a
mapBound f (Scope s) = Scope (fmap f' s) where
f' (B b) = B (f b)
f' (F a) = F a
{-# INLINE mapBound #-}
-- | Return a list of occurences of the variables bound by this 'Scope'.
bindings :: Foldable f => Scope b f a -> [b]
bindings (Scope s) = foldr f [] s where
f (B v) vs = v : vs
f _ vs = vs
{-# INLINE bindings #-}
-- | If a term has no free variables, you can freely change the type of
-- free variables it is parameterized on.
--
-- >>> closed [12]
-- Nothing
--
-- >>> closed ""
-- Just []
--
-- >>> :t closed ""
-- closed "" :: Maybe [b]
closed :: Traversable f => f a -> Maybe (f b)
closed = traverse (const Nothing)
{-# INLINE closed #-}
-- | A closed term has no free variables.
--
-- >>> isClosed []
-- True
--
-- >>> isClosed [1,2,3]
-- False
isClosed :: Foldable f => f a -> Bool
isClosed = all (const False)
{-# INLINE isClosed #-}