unbound-generics (empty) → 0.0.0.90
raw patch · 23 files changed
+2551/−0 lines, 23 filesdep +HUnitdep +basedep +containerssetup-changed
Dependencies added: HUnit, base, containers, contravariant, mtl, transformers, unbound-generics
Files
- Changelog.md +2/−0
- LICENSE +30/−0
- README.md +20/−0
- Setup.hs +2/−0
- examples/F.hs +170/−0
- src/Unbound/Generics/LocallyNameless.hs +30/−0
- src/Unbound/Generics/LocallyNameless/Alpha.hs +610/−0
- src/Unbound/Generics/LocallyNameless/Bind.hs +77/−0
- src/Unbound/Generics/LocallyNameless/Embed.hs +77/−0
- src/Unbound/Generics/LocallyNameless/Fresh.hs +140/−0
- src/Unbound/Generics/LocallyNameless/Internal/Fold.hs +44/−0
- src/Unbound/Generics/LocallyNameless/LFresh.hs +220/−0
- src/Unbound/Generics/LocallyNameless/Name.hs +96/−0
- src/Unbound/Generics/LocallyNameless/Operations.hs +162/−0
- src/Unbound/Generics/LocallyNameless/Rebind.hs +85/−0
- src/Unbound/Generics/LocallyNameless/Subst.hs +177/−0
- src/Unbound/Generics/LocallyNameless/Unsafe.hs +24/−0
- src/Unbound/Generics/PermM.hs +165/−0
- test/Calc.hs +165/−0
- test/ParallelReduction.hs +92/−0
- test/test-calc.hs +55/−0
- test/test-parallelreduction.hs +25/−0
- unbound-generics.cabal +83/−0
+ Changelog.md view
@@ -0,0 +1,2 @@+* 0.0.0.90+ Initial (re-)implementation effort.
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2014, Aleksey Kliger++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * 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.++ * Neither the name of Aleksey Kliger nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT+OWNER 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.
+ README.md view
@@ -0,0 +1,20 @@+# unbound-generics++[](https://travis-ci.org/lambdageek/unbound-generics)+++This is a reimplementation of (parts of) [unbound](http://hackage.haskell.org/package/unbound) but using [GHC generics](http://www.haskell.org/ghc/docs/latest/html/libraries/base-4.7.0.1/GHC-Generics.html) instead of [RepLib](https://hackage.haskell.org/package/RepLib).++## Differences from `unbound`++For the most part, I tried to keep the same methods with the same signatures. However there are a few differences.++1. `fv :: Alpha t => t -> Fold t (Name n)`++ The `fv` method returns a `Fold` (in the sense of the [lens](http://hackage.haskell.org/package/lens) library),+ rather than an `Unbound.Util.Collection` instance. That means you will generally have to write `toListOf fv t` or some other summary operation.++2. `isPat :: Alpha t => t -> DisjointSet AnyName`++ You should olnly notice this if you're implementing an instance of `Alpha` by hand (rather than by using the default+ generic instance). The original `unbound` returned a `Maybe [AnyName]` here with the same interpretation as `DisjointSet`: `Nothing` means an inconsistency was encountered, or `Just` the free variables of the pattern.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ examples/F.hs view
@@ -0,0 +1,170 @@+{-# LANGUAGE DeriveGeneric,+ DeriveDataTypeable,+ FlexibleInstances,+ FlexibleContexts,+ MultiParamTypeClasses,+ ScopedTypeVariables+ #-}++module F where++import Unbound.Generics.LocallyNameless++import GHC.Generics+import Data.Typeable (Typeable)++import Control.Monad.Identity+import Control.Monad.Writer hiding (All)+import Control.Monad.Trans.Error+import Data.List as List+++-- System F with type and term variables++type TyName = Name Ty+type TmName = Name Tm++data Ty = TyVar TyName+ | Arr Ty Ty+ | All (Bind TyName Ty)+ deriving (Show, Generic, Typeable)++data Tm = TmVar TmName+ | Lam (Bind (TmName, Embed Ty) Tm)+ | TLam (Bind TyName Tm)+ | App Tm Tm+ | TApp Tm Ty+ deriving (Show, Generic, Typeable)++------------------------------------------------------+instance Alpha Ty+instance Alpha Tm++instance Subst Tm Ty+instance Subst Tm Tm where+ isvar (TmVar v) = Just (SubstName v)+ isvar _ = Nothing++instance Subst Ty Ty where+ isvar (TyVar v) = Just (SubstName v)+ isvar _ = Nothing++------------------------------------------------------+-- Example terms+------------------------------------------------------++x :: Name Tm+y :: Name Tm+z :: Name Tm+(x,y,z) = (string2Name "x", string2Name "y", string2Name "z")++a :: Name Ty+b :: Name Ty+c :: Name Ty+(a,b,c) = (string2Name "a", string2Name "b", string2Name "c")++-- /\a. \x:a. x+polyid :: Tm+polyid = TLam (bind a (Lam (bind (x, Embed (TyVar a)) (TmVar x))))++-- All a. a -> a+polyidty :: Ty+polyidty = All (bind a (Arr (TyVar a) (TyVar a)))++-- /\b. \y:b. y+polyid2 :: Tm+polyid2 = TLam (bind b (Lam (bind (y, Embed (TyVar b)) (TmVar y))))++-- /\c. \y:b. y+bad_polyid2 :: Tm+bad_polyid2 = TLam (bind c (Lam (bind (y, Embed (TyVar b)) (TmVar y))))+++-----------------------------------------------------------------+-- Typechecker+-----------------------------------------------------------------+type Delta = [ TyName ]+type Gamma = [ (TmName, Ty) ]++data Ctx = Ctx { getDelta :: Delta , getGamma :: Gamma }+ deriving (Show)+ +emptyCtx :: Ctx+emptyCtx = Ctx { getDelta = [], getGamma = [] }++type M = ErrorT String (WriterT [String] (FreshMT Identity))++runM :: M a -> (a, [String])+runM m = case (runIdentity $ runFreshMT $ runWriterT (runErrorT m)) of+ (Left s, msgs) -> error $ s ++ "\nLog: " ++ show msgs+ (Right ans, msgs) -> (ans, msgs)++checkTyVar :: Ctx -> TyName -> M ()+checkTyVar g v = do+ if List.elem v (getDelta g) then+ return ()+ else+ throwError $ "NotFound: " ++ show v ++ " in " ++ show g++lookupTmVar :: Ctx -> TmName -> M Ty+lookupTmVar g v = do+ case lookup v (getGamma g) of+ Just s -> return s+ Nothing -> throwError "NotFound"++extendTy :: TyName -> Ctx -> Ctx+extendTy n ctx = ctx { getDelta = n : (getDelta ctx) }++extendTm :: TmName -> Ty -> Ctx -> Ctx+extendTm n ty ctx = ctx { getGamma = (n, ty) : (getGamma ctx) }++tcty :: Ctx -> Ty -> M ()+tcty g (TyVar alpha) = do+ trace $ "looking up tyvar " ++ show alpha+ checkTyVar g alpha+tcty g (All bnder) = do+ trace $ "checking " ++ show (All bnder)+ (alpha, ty') <- unbind bnder+ trace $ "unbinding All gave " ++ show alpha ++ " in " ++ show ty'+ tcty (extendTy alpha g) ty'+tcty g (Arr t1 t2) = do+ trace $ "checking " ++ show (Arr t1 t2)+ tcty g t1+ tcty g t2++trace :: String -> M ()+trace s = lift (tell [s])++ti :: Ctx -> Tm -> M Ty+ti g (TmVar v) = trace ("looking up " ++ show v) >> lookupTmVar g v+ti g (Lam bnd) = do+ trace $ "checking " ++ show (Lam bnd)+ ((v, Embed ty1), t) <- unbind bnd+ tcty g ty1+ ty2 <- ti (extendTm v ty1 g) t+ return (Arr ty1 ty2)+ti g (App t1 t2) = do+ trace $ "checking " ++ show (App t1 t2)+ ty1 <- ti g t1+ ty2 <- ti g t2+ case ty1 of+ Arr ty11 ty21 | ty2 `aeq` ty11 ->+ return ty21+ _ -> throwError "TypeError"+ti g (TLam bnd) = do+ trace $ "checking " ++ show (TLam bnd)+ (v, t) <- unbind bnd+ trace $ "unbinding TLam gave " ++ show v ++ " in " ++ show t+ ty <- ti (extendTy v g) t+ return (All (bind v ty))+ti g (TApp t ty) = do+ trace $ "checking " ++ show (TApp t ty)+ tyt <- ti g t+ case tyt of+ (All bnder) -> do+ tcty g ty+ (n1, ty1) <- unbind bnder+ return $ subst n1 ty ty1+ _ -> throwError $ "Expected a ForAll in a type application, got " ++ show tyt++
+ src/Unbound/Generics/LocallyNameless.hs view
@@ -0,0 +1,30 @@+-- |+-- Module : Unbound.Generics.LocallyNameless+-- Copyright : (c) 2014, Aleksey Kliger+-- License : BSD3 (See LICENSE)+-- Maintainer : Aleksey Kliger+-- Stability : experimental+--+--+-- See 'Alpha', 'Bind', "Unbound.Generics.LocallyNameless.Operations" to get started. +module Unbound.Generics.LocallyNameless (+ module Unbound.Generics.LocallyNameless.Alpha,+ module Unbound.Generics.LocallyNameless.Name,+ module Unbound.Generics.LocallyNameless.Operations,+ module Unbound.Generics.LocallyNameless.Bind,+ module Unbound.Generics.LocallyNameless.Embed,+ module Unbound.Generics.LocallyNameless.Rebind,+ module Unbound.Generics.LocallyNameless.Fresh,+ module Unbound.Generics.LocallyNameless.LFresh,+ module Unbound.Generics.LocallyNameless.Subst+ ) where++import Unbound.Generics.LocallyNameless.Alpha+import Unbound.Generics.LocallyNameless.Name hiding (Bn, Fn)+import Unbound.Generics.LocallyNameless.Bind hiding (B)+import Unbound.Generics.LocallyNameless.Embed+import Unbound.Generics.LocallyNameless.Rebind hiding (Rebnd)+import Unbound.Generics.LocallyNameless.Fresh+import Unbound.Generics.LocallyNameless.LFresh+import Unbound.Generics.LocallyNameless.Operations+import Unbound.Generics.LocallyNameless.Subst
+ src/Unbound/Generics/LocallyNameless/Alpha.hs view
@@ -0,0 +1,610 @@+{-# OPTIONS_HADDOCK show-extensions #-}+-- |+-- Module : Unbound.Generics.LocallyNameless.Alpha+-- Copyright : (c) 2014, Aleksey Kliger+-- License : BSD3 (See LICENSE)+-- Maintainer : Aleksey Kliger+-- Stability : experimental+--+-- Use the 'Alpha' typeclass to mark types that may contain 'Name's.+{-# LANGUAGE DefaultSignatures+ , FlexibleContexts+ , TypeOperators #-}+module Unbound.Generics.LocallyNameless.Alpha (+ -- * Name-aware opertions+ Alpha(..)+ -- * Binder variables+ , DisjointSet(..)+ , inconsistentDisjointSet+ , singletonDisjointSet+ , isConsistentDisjointSet+ -- * Implementation details+ , NthPatFind+ , NamePatFind+ , AlphaCtx+ , initialCtx+ , patternCtx+ , termCtx+ , isTermCtx+ , incrLevelCtx+ ) where++import Control.Applicative (Applicative(..), (<$>))+import Control.Arrow (first)+import Control.Monad (liftM)+import Data.Function (on)+import Data.Functor.Contravariant (Contravariant(..))+import Data.Foldable (Foldable(..))+import Data.List (intersect)+import Data.Monoid (Monoid(..), (<>))+import Data.Ratio (Ratio)+import Data.Typeable (Typeable, gcast)+import GHC.Generics++import Unbound.Generics.LocallyNameless.Name+import Unbound.Generics.LocallyNameless.Fresh+import Unbound.Generics.LocallyNameless.LFresh+import Unbound.Generics.PermM++-- | Some 'Alpha' operations need to record information about their+-- progress. Instances should just pass it through unchanged.+--+-- The context records whether we are currently operating on terms or patterns,+-- and how many binding levels we've descended.+data AlphaCtx = AlphaCtx { ctxMode :: !Mode, ctxLevel :: !Integer }++data Mode = Term | Pat+ deriving Eq++-- | The starting context for alpha operations: we are expecting to+-- work on terms and we are under no binders.+initialCtx :: AlphaCtx+initialCtx = AlphaCtx { ctxMode = Term, ctxLevel = 0 }++-- | Switches to a context where we expect to operate on patterns.+patternCtx :: AlphaCtx -> AlphaCtx+patternCtx ctx = ctx { ctxMode = Pat }++-- | Switches to a context where we expect to operate on terms.+termCtx :: AlphaCtx -> AlphaCtx+termCtx ctx = ctx { ctxMode = Term }++-- | Returns 'True' iff we are in a context where we expect to see terms.+isTermCtx :: AlphaCtx -> Bool+isTermCtx (AlphaCtx {ctxMode = Term}) = True+isTermCtx _ = False++-- | Increment the number of binders that we are operating under.+incrLevelCtx :: AlphaCtx -> AlphaCtx+incrLevelCtx ctx = ctx { ctxLevel = 1 + ctxLevel ctx }++-- | A @DisjointSet a@ is a 'Just' a list of distinct @a@s. In addition to a monoidal+-- structure, a disjoint set also has an annihilator 'inconsistentDisjointSet'.+--+-- @+-- inconsistentDisjointSet \<> s == inconsistentDisjointSet+-- s \<> inconsistentDisjoinSet == inconsistentDisjointSet+-- @+newtype DisjointSet a = DisjointSet (Maybe [a])++instance Eq a => Monoid (DisjointSet a) where+ mempty = DisjointSet (Just [])+ mappend s1 s2 =+ case (s1, s2) of+ (DisjointSet (Just xs), DisjointSet (Just ys)) | disjointLists xs ys -> DisjointSet (Just (xs <> ys))+ _ -> inconsistentDisjointSet++instance Foldable DisjointSet where+ foldMap summarize (DisjointSet ms) = foldMap (foldMap summarize) ms++-- | Returns a @DisjointSet a@ that is the annihilator element for the 'Monoid' instance of 'DisjointSet'.+inconsistentDisjointSet :: DisjointSet a+inconsistentDisjointSet = DisjointSet Nothing++-- | @singletonDisjointSet x@ a @DisjointSet a@ that contains the single element @x@+singletonDisjointSet :: a -> DisjointSet a+singletonDisjointSet x = DisjointSet (Just [x])++disjointLists :: Eq a => [a] -> [a] -> Bool+disjointLists xs ys = null (intersect xs ys)++-- | @isConsistentDisjointSet@ returns @True@ iff the given disjoint set is not inconsistent.+isConsistentDisjointSet :: DisjointSet a -> Bool+isConsistentDisjointSet (DisjointSet Nothing) = False+isConsistentDisjointSet _ = True++-- | Types that are instances of @Alpha@ may participate in name representation.+--+-- Minimal instance is entirely empty, provided that your type is an instance of+-- 'Generic'.+class (Show a) => Alpha a where+ -- | See 'Unbound.Generics.LocallyNameless.Operations.aeq'.+ aeq' :: AlphaCtx -> a -> a -> Bool+ default aeq' :: (Generic a, GAlpha (Rep a)) => AlphaCtx -> a -> a -> Bool+ aeq' c = (gaeq c) `on` from++ -- | See 'Unbound.Generics.LocallyNameless.Operations.fvAny'.+ -- @@@ fvAny' :: AlphaCtx -> Fold a AnyName @@@+ fvAny' :: (Contravariant f, Applicative f) => AlphaCtx -> (AnyName -> f AnyName) -> a -> f a+ default fvAny' :: (Generic a, GAlpha (Rep a), Contravariant f, Applicative f) => AlphaCtx -> (AnyName -> f AnyName) -> a -> f a+ fvAny' c nfn = fmap to . gfvAny c nfn . from++ -- | Replace free names by bound names.+ close :: Alpha b => AlphaCtx -> b -> a -> a+ default close :: (Generic a, GAlpha (Rep a), Alpha b) => AlphaCtx -> b -> a -> a+ close c b = to . gclose c b . from++ -- | Replace bound names by free names.+ open :: Alpha b => AlphaCtx -> b -> a -> a+ default open :: (Generic a, GAlpha (Rep a), Alpha b) => AlphaCtx -> b -> a -> a+ open c b = to . gopen c b . from++ -- | @isPat x@ dynamically checks whether @x@ can be used as a valid pattern.+ isPat :: a -> DisjointSet AnyName+ default isPat :: (Generic a, GAlpha (Rep a)) => a -> DisjointSet AnyName+ isPat = gisPat . from++ -- | @isPat x@ dynamically checks whether @x@ can be used as a valid term.+ isTerm :: a -> Bool+ default isTerm :: (Generic a, GAlpha (Rep a)) => a -> Bool+ isTerm = gisTerm . from++ -- | @isEmbed@ is needed internally for the implementation of+ -- 'isPat'. @isEmbed@ is true for terms wrapped in 'Embed' and zero+ -- or more occurrences of 'Shift'. The default implementation+ -- simply returns @False@.+ isEmbed :: a -> Bool+ isEmbed _ = False++ -- | If @a@ is a pattern, finds the @n@th name in the pattern+ -- (starting from zero), returning the number of names encountered+ -- if not found.+ nthPatFind :: a -> NthPatFind+ default nthPatFind :: (Generic a, GAlpha (Rep a)) => a -> NthPatFind+ nthPatFind = gnthPatFind . from++ -- | If @a@ is a pattern, find the index of the given name in the pattern.+ namePatFind :: a -> NamePatFind+ default namePatFind :: (Generic a, GAlpha (Rep a)) => a -> NamePatFind+ namePatFind = gnamePatFind . from++ -- | See 'Unbound.Generics.LocallyNameless.Operations.swaps'. Apply+ -- the given permutation of variable names to the given pattern.+ swaps' :: AlphaCtx -> Perm AnyName -> a -> a+ default swaps' :: (Generic a, GAlpha (Rep a)) => AlphaCtx -> Perm AnyName -> a -> a+ swaps' ctx perm = to . gswaps ctx perm . from++ -- | See 'Unbound.Generics.LocallyNameless.Operations.freshen'.+ lfreshen' :: LFresh m => AlphaCtx -> a -> (a -> Perm AnyName -> m b) -> m b+ default lfreshen' :: (LFresh m, Generic a, GAlpha (Rep a))+ => AlphaCtx -> a -> (a -> Perm AnyName -> m b) -> m b+ lfreshen' ctx m cont = glfreshen ctx (from m) (cont . to)++ -- | See 'Unbound.Generics.LocallyNameless.Operations.freshen'. Rename the free variables+ -- in the given term to be distinct from all other names seen in the monad @m@.+ freshen' :: Fresh m => AlphaCtx -> a -> m (a, Perm AnyName)+ default freshen' :: (Generic a, GAlpha (Rep a), Fresh m) => AlphaCtx -> a -> m (a, Perm AnyName)+ freshen' ctx = liftM (first to) . gfreshen ctx . from++-- | The result of @'nthPatFind' a i@ is @Left k@ where @k@ is the+-- number of names in pattern @a@ with @k < i@ or @Right x@ where @x@+-- is the @i@th name in @a@+type NthPatFind = Integer -> Either Integer AnyName++-- | The result of @'namePatFind' a x@ is either @Left i@ if @a@ is a pattern that+-- contains @i@ free names none of which are @x@, or @Right j@ if @x@ is the @j@th name+-- in @a@+type NamePatFind = AnyName -> Either Integer Integer -- Left - names skipped over+ -- Right - index of the name we found++-- | The "Generic" representation version of 'Alpha'+class GAlpha f where+ gaeq :: AlphaCtx -> f a -> f a -> Bool++ gfvAny :: (Contravariant g, Applicative g) => AlphaCtx -> (AnyName -> g AnyName) -> f a -> g (f a)++ gclose :: Alpha b => AlphaCtx -> b -> f a -> f a+ gopen :: Alpha b => AlphaCtx -> b -> f a -> f a++ gisPat :: f a -> DisjointSet AnyName+ gisTerm :: f a -> Bool++ gnthPatFind :: f a -> NthPatFind+ gnamePatFind :: f a -> NamePatFind++ gswaps :: AlphaCtx -> Perm AnyName -> f a -> f a+ gfreshen :: Fresh m => AlphaCtx -> f a -> m (f a, Perm AnyName)++ glfreshen :: LFresh m => AlphaCtx -> f a -> (f a -> Perm AnyName -> m b) -> m b+ +instance (Alpha c) => GAlpha (K1 i c) where+ gaeq ctx (K1 c1) (K1 c2) = aeq' ctx c1 c2++ gfvAny ctx nfn = fmap K1 . fvAny' ctx nfn . unK1++ gclose ctx b = K1 . close ctx b . unK1+ gopen ctx b = K1 . open ctx b . unK1++ gisPat = isPat . unK1+ gisTerm = isTerm . unK1++ gnthPatFind = nthPatFind . unK1+ gnamePatFind = namePatFind . unK1++ gswaps ctx perm = K1 . swaps' ctx perm . unK1+ gfreshen ctx = liftM (first K1) . freshen' ctx . unK1++ glfreshen ctx (K1 c) cont = lfreshen' ctx c (cont . K1)++instance GAlpha f => GAlpha (M1 i c f) where+ gaeq ctx (M1 f1) (M1 f2) = gaeq ctx f1 f2++ gfvAny ctx nfn = fmap M1 . gfvAny ctx nfn . unM1++ gclose ctx b = M1 . gclose ctx b . unM1+ gopen ctx b = M1 . gopen ctx b . unM1++ gisPat = gisPat . unM1+ gisTerm = gisTerm . unM1++ gnthPatFind = gnthPatFind . unM1+ gnamePatFind = gnamePatFind . unM1+ + gswaps ctx perm = M1 . gswaps ctx perm . unM1+ gfreshen ctx = liftM (first M1) . gfreshen ctx . unM1++ glfreshen ctx (M1 f) cont =+ glfreshen ctx f (cont . M1)++instance GAlpha U1 where+ gaeq _ctx _ _ = True++ gfvAny _ctx _nfn _ = pure U1++ gclose _ctx _b _ = U1+ gopen _ctx _b _ = U1++ gisPat _ = mempty+ gisTerm _ = True++ gnthPatFind _ = Left+ gnamePatFind _ _ = Left 0++ gswaps _ctx _perm _ = U1+ gfreshen _ctx _ = return (U1, mempty)++ glfreshen _ctx _ cont = cont U1 mempty++instance GAlpha V1 where+ gaeq _ctx _ _ = False++ gfvAny _ctx _nfn = pure++ gclose _ctx _b _ = undefined+ gopen _ctx _b _ = undefined++ gisPat _ = mempty+ gisTerm _ = False++ gnthPatFind _ = Left+ gnamePatFind _ _ = Left 0++ gswaps _ctx _perm _ = undefined+ gfreshen _ctx _ = return (undefined, mempty)++ glfreshen _ctx _ cont = cont undefined mempty++instance (GAlpha f, GAlpha g) => GAlpha (f :*: g) where+ gaeq ctx (f1 :*: g1) (f2 :*: g2) =+ gaeq ctx f1 f2 && gaeq ctx g1 g2++ gfvAny ctx nfn (f :*: g) = (:*:) <$> gfvAny ctx nfn f+ <*> gfvAny ctx nfn g++ gclose ctx b (f :*: g) = gclose ctx b f :*: gclose ctx b g+ gopen ctx b (f :*: g) = gopen ctx b f :*: gopen ctx b g++ gisPat (f :*: g) = gisPat f <> gisPat g+ gisTerm (f :*: g) = gisTerm f && gisTerm g++ gnthPatFind (f :*: g) i = case gnthPatFind f i of+ Left i' -> gnthPatFind g i'+ Right ans -> Right ans+ gnamePatFind (f :*: g) n = case gnamePatFind f n of+ Left j -> case gnamePatFind g n of+ Left i -> Left $! j + i+ Right k -> Right $! j + k+ Right k -> Right k++ gswaps ctx perm (f :*: g) =+ gswaps ctx perm f :*: gswaps ctx perm g++ gfreshen ctx (f :*: g) = do+ (g', perm2) <- gfreshen ctx g+ (f', perm1) <- gfreshen ctx (gswaps ctx perm2 f)+ return (f' :*: g', perm1 <> perm2)++ glfreshen ctx (f :*: g) cont =+ glfreshen ctx g $ \g' perm2 ->+ glfreshen ctx (gswaps ctx perm2 f) $ \f' perm1 ->+ cont (f' :*: g') (perm1 <> perm2)++instance (GAlpha f, GAlpha g) => GAlpha (f :+: g) where+ gaeq ctx (L1 f1) (L1 f2) = gaeq ctx f1 f2+ gaeq ctx (R1 g1) (R1 g2) = gaeq ctx g1 g2+ gaeq _ctx _ _ = False++ gfvAny ctx nfn (L1 f) = fmap L1 (gfvAny ctx nfn f)+ gfvAny ctx nfn (R1 g) = fmap R1 (gfvAny ctx nfn g)++ gclose ctx b (L1 f) = L1 (gclose ctx b f)+ gclose ctx b (R1 g) = R1 (gclose ctx b g)+ gopen ctx b (L1 f) = L1 (gopen ctx b f)+ gopen ctx b (R1 g) = R1 (gopen ctx b g)++ gisPat (L1 f) = gisPat f+ gisPat (R1 g) = gisPat g++ gisTerm (L1 f) = gisTerm f+ gisTerm (R1 g) = gisTerm g++ gnthPatFind (L1 f) i = gnthPatFind f i+ gnthPatFind (R1 g) i = gnthPatFind g i+ + gnamePatFind (L1 f) n = gnamePatFind f n+ gnamePatFind (R1 g) n = gnamePatFind g n++ gswaps ctx perm (L1 f) = L1 (gswaps ctx perm f)+ gswaps ctx perm (R1 f) = R1 (gswaps ctx perm f)++ gfreshen ctx (L1 f) = liftM (first L1) (gfreshen ctx f)+ gfreshen ctx (R1 f) = liftM (first R1) (gfreshen ctx f)+ + glfreshen ctx (L1 f) cont =+ glfreshen ctx f (cont . L1)+ glfreshen ctx (R1 g) cont =+ glfreshen ctx g (cont . R1)+ ++-- ============================================================+-- Alpha instances for the usual types++instance Alpha Int where+ aeq' _ctx i j = i == j++ fvAny' _ctx _nfn i = pure i++ close _ctx _b i = i+ open _ctx _b i = i++ isPat _ = mempty+ isTerm _ = True++ nthPatFind _ = Left+ namePatFind _ _ = Left 0++ swaps' _ctx _p i = i+ freshen' _ctx i = return (i, mempty)+ lfreshen' _ctx i cont = cont i mempty++instance Alpha Char where+ aeq' _ctx i j = i == j++ fvAny' _ctx _nfn i = pure i++ close _ctx _b i = i+ open _ctx _b i = i++ isPat _ = mempty+ isTerm _ = True++ nthPatFind _ = Left+ namePatFind _ _ = Left 0++ swaps' _ctx _p i = i+ freshen' _ctx i = return (i, mempty)+ lfreshen' _ctx i cont = cont i mempty++instance Alpha Integer where+ aeq' _ctx i j = i == j++ fvAny' _ctx _nfn i = pure i++ close _ctx _b i = i+ open _ctx _b i = i++ isPat _ = mempty+ isTerm _ = True++ nthPatFind _ = Left+ namePatFind _ _ = Left 0++ swaps' _ctx _p i = i+ freshen' _ctx i = return (i, mempty)+ lfreshen' _ctx i cont = cont i mempty++instance Alpha Float where+ aeq' _ctx i j = i == j++ fvAny' _ctx _nfn i = pure i++ close _ctx _b i = i+ open _ctx _b i = i++ isPat _ = mempty+ isTerm _ = True++ nthPatFind _ = Left+ namePatFind _ _ = Left 0++ swaps' _ctx _p i = i+ freshen' _ctx i = return (i, mempty)+ lfreshen' _ctx i cont = cont i mempty++instance Alpha Double where+ aeq' _ctx i j = i == j++ fvAny' _ctx _nfn i = pure i++ close _ctx _b i = i+ open _ctx _b i = i++ isPat _ = mempty+ isTerm _ = True++ nthPatFind _ = Left+ namePatFind _ _ = Left 0++ swaps' _ctx _p i = i+ freshen' _ctx i = return (i, mempty)+ lfreshen' _ctx i cont = cont i mempty++instance (Integral n, Alpha n) => Alpha (Ratio n) where+ aeq' _ctx i j = i == j++ fvAny' _ctx _nfn i = pure i++ close _ctx _b i = i+ open _ctx _b i = i++ isPat _ = mempty+ isTerm _ = True++ nthPatFind _ = Left+ namePatFind _ _ = Left 0++ swaps' _ctx _p i = i+ freshen' _ctx i = return (i, mempty)+ lfreshen' _ctx i cont = cont i mempty++instance Alpha Bool++instance Alpha a => Alpha (Maybe a)+instance Alpha a => Alpha [a]+instance (Alpha a,Alpha b) => Alpha (Either a b)+instance (Alpha a,Alpha b) => Alpha (a,b)+instance (Alpha a,Alpha b,Alpha c) => Alpha (a,b,c)+instance (Alpha a, Alpha b,Alpha c, Alpha d) => Alpha (a,b,c,d)+instance (Alpha a, Alpha b,Alpha c, Alpha d, Alpha e) =>+ Alpha (a,b,c,d,e)++-- ============================================================+-- Alpha instances for interesting types++instance Typeable a => Alpha (Name a) where+ aeq' ctx n1 n2 =+ if isTermCtx ctx+ then n1 == n2 -- in terms, better be the same name+ else True -- in a pattern, names are always equivlent (since+ -- they're both bound, so they can vary).++ fvAny' ctx nfn nm = if isTermCtx ctx && isFreeName nm+ then contramap AnyName (nfn (AnyName nm))+ else pure nm++ open ctx b a@(Bn l k) =+ if ctxMode ctx == Term && ctxLevel ctx == l+ then case nthPatFind b k of+ Right (AnyName nm) -> case gcast nm of+ Just nm' -> nm'+ Nothing -> error "LocallyNameless.open: inconsistent sorts"+ Left _ -> error "LocallyNameless.open : inconsistency - pattern had too few variables"+ else+ a+ open _ctx _ a = a++ close ctx b a@(Fn _ _) =+ if isTermCtx ctx+ then case namePatFind b (AnyName a) of+ Right k -> Bn (ctxLevel ctx) k+ Left _ -> a+ else a+ close _ctx _ a = a+ ++ isPat n = if isFreeName n+ then singletonDisjointSet (AnyName n)+ else inconsistentDisjointSet++ isTerm _ = True++ nthPatFind nm i =+ if i == 0 then Right (AnyName nm) else Left $! i-1++ namePatFind nm1 (AnyName nm2) =+ case gcast nm1 of+ Just nm1' -> if nm1' == nm2 then Right 0 else Left 1+ Nothing -> Left 1++ swaps' ctx perm nm =+ if isTermCtx ctx+ then case apply perm (AnyName nm) of+ AnyName nm' ->+ case gcast nm' of+ Just nm'' -> nm''+ Nothing -> error "Internal error swaps' on a Name returned permuted name of wrong sort"+ else nm+ + freshen' ctx nm =+ if not (isTermCtx ctx)+ then do+ nm' <- fresh nm+ return (nm', single (AnyName nm) (AnyName nm'))+ else error "freshen' on a Name in term position"++ lfreshen' ctx nm cont =+ if not (isTermCtx ctx)+ then do+ nm' <- lfresh nm+ avoid [AnyName nm'] $ cont nm' $ single (AnyName nm) (AnyName nm')+ else error "lfreshen' on a Name in term position"++instance Alpha AnyName where+ aeq' ctx x y =+ if x == y+ then True+ else+ -- in a term unequal variables are unequal, in a pattern it's+ -- ok.+ not (isTermCtx ctx)++ fvAny' ctx nfn n@(AnyName nm) = if isTermCtx ctx && isFreeName nm+ then nfn n+ else pure n++ isTerm _ = True++ isPat n@(AnyName nm) = if isFreeName nm+ then singletonDisjointSet n+ else inconsistentDisjointSet++ swaps' ctx perm n =+ if isTermCtx ctx+ then apply perm n+ else n++ freshen' ctx (AnyName nm) =+ if isTermCtx ctx+ then error "LocallyNameless.freshen' on AnyName in Term mode"+ else do+ nm' <- fresh nm+ return (AnyName nm', single (AnyName nm) (AnyName nm'))++ lfreshen' ctx (AnyName nm) cont =+ if isTermCtx ctx+ then error "LocallyNameless.lfreshen' on AnyName in Term mode"+ else do+ nm' <- lfresh nm+ avoid [AnyName nm'] $ cont (AnyName nm') $ single (AnyName nm) (AnyName nm')++ open ctx b (AnyName nm) = AnyName (open ctx b nm)++ close ctx b (AnyName nm) = AnyName (close ctx b nm)+ + nthPatFind nm i =+ if i == 0 then Right nm else Left $! i - 1++ namePatFind nmHave nmWant =+ if nmHave == nmWant+ then Right 0+ else Left 1
+ src/Unbound/Generics/LocallyNameless/Bind.hs view
@@ -0,0 +1,77 @@+{-# OPTIONS_HADDOCK show-extensions #-}+-- |+-- Module : Unbound.Generics.LocallyNameless.Bind+-- Copyright : (c) 2014, Aleksey Kliger+-- License : BSD3 (See LICENSE)+-- Maintainer : Aleksey Kliger+-- Stability : experimental+--+-- The fundamental binding form. The type @'Bind' p t@ allows you to+-- place a pattern @p@ in a term @t@ such that the names in the+-- pattern scope over the term. Use 'Unbound.Generics.LocallyNameless.Operations.bind'+-- and 'Unbound.Generics.LocallyNameless.Operations.unbind' and 'Unbound.Generics.LocallyNameless.Operations.lunbind'+-- to work with @'Bind' p t@+{-# LANGUAGE DeriveGeneric #-}+module Unbound.Generics.LocallyNameless.Bind (+ -- * Name binding+ Bind(..)+ ) where++import Control.Applicative (Applicative(..), (<$>))+import Data.Monoid ((<>))++import GHC.Generics (Generic)++import Unbound.Generics.LocallyNameless.Alpha++-- | A term of type @'Bind' p t@ is a term that binds the free+-- variable occurrences of the variables in pattern @p@ in the term+-- @t@. In the overall term, those variables are now bound. See also+-- 'Unbound.Generics.LocallyNameless.Operations.bind' and+-- 'Unbound.Generics.LocallyNameless.Operations.unbind' and+-- 'Unbound.Generics.LocallyNameless.Operations.lunbind'+data Bind p t = B p t+ deriving (Generic)++instance (Show p, Show t) => Show (Bind p t) where+ showsPrec prec (B p t) =+ showParen (prec > 0) (showString "<"+ . showsPrec prec p+ . showString "> "+ . showsPrec 0 t)++instance (Alpha p, Alpha t) => Alpha (Bind p t) where++ aeq' ctx (B p1 t1) (B p2 t2) =+ aeq' (patternCtx ctx) p1 p2+ && aeq' (incrLevelCtx ctx) t1 t2++ fvAny' ctx nfn (B p t) = B <$> fvAny' (patternCtx ctx) nfn p+ <*> fvAny' (incrLevelCtx ctx) nfn t++ isPat _ = inconsistentDisjointSet++ isTerm (B p t) = isConsistentDisjointSet (isPat p) && isTerm t++ close ctx b (B p t) =+ B (close (patternCtx ctx) b p) (close (incrLevelCtx ctx) b t)++ open ctx b (B p t) =+ B (open (patternCtx ctx) b p) (open (incrLevelCtx ctx) b t)++ nthPatFind b = error $ "Binding " ++ show b ++ " used as a pattern"+ namePatFind b = error $ "Binding " ++ show b ++ " used as a pattern"++ swaps' ctx perm (B p t) =+ B (swaps' (patternCtx ctx) perm p)+ (swaps' (incrLevelCtx ctx) perm t)++ freshen' ctx (B p t) = do+ (p', perm1) <- freshen' (patternCtx ctx) p+ (t', perm2) <- freshen' (incrLevelCtx ctx) (swaps' (incrLevelCtx ctx) perm1 t)+ return (B p' t', perm1 <> perm2)++ lfreshen' ctx (B p t) cont =+ lfreshen' (patternCtx ctx) p $ \p' pm1 ->+ lfreshen' (incrLevelCtx ctx) (swaps' (incrLevelCtx ctx) pm1 t) $ \t' pm2 ->+ cont (B p' t') (pm1 <> pm2)
+ src/Unbound/Generics/LocallyNameless/Embed.hs view
@@ -0,0 +1,77 @@+{-# OPTIONS_HADDOCK show-extensions #-}+-- |+-- Module : Unbound.Generics.LocallyNameless.Embed+-- Copyright : (c) 2014, Aleksey Kliger+-- License : BSD3 (See LICENSE)+-- Maintainer : Aleksey Kliger+-- Stability : experimental+--+-- The pattern @'Embed' t@ contains a term @t@.+{-# LANGUAGE DeriveGeneric #-}+module Unbound.Generics.LocallyNameless.Embed where++import Control.Applicative (pure, (<$>))+import Data.Monoid (mempty)+import GHC.Generics (Generic)++import Unbound.Generics.LocallyNameless.Alpha++-- | @Embed@ allows for terms to be /embedded/ within patterns. Such+-- embedded terms do not bind names along with the rest of the+-- pattern. For examples, see the tutorial or examples directories.+--+-- If @t@ is a /term type/, then @Embed t@ is a /pattern type/.+--+-- @Embed@ is not abstract since it involves no binding, and hence+-- it is safe to manipulate directly. To create and destruct+-- @Embed@ terms, you may use the @Embed@ constructor directly.+-- (You may also use the functions 'embed' and 'unembed', which+-- additionally can construct or destruct any number of enclosing+-- 'Shift's at the same time.)+newtype Embed t = Embed t deriving (Eq, Generic)++instance Show a => Show (Embed a) where+ showsPrec _ (Embed a) = showString "{" . showsPrec 0 a . showString "}"++instance Alpha t => Alpha (Embed t) where+ isPat (Embed t) = if (isTerm t) then mempty else inconsistentDisjointSet++ isTerm _ = False++ isEmbed (Embed t) = isTerm t++ swaps' ctx perm (Embed t) =+ if isTermCtx ctx+ then Embed t+ else Embed (swaps' (termCtx ctx) perm t)++ freshen' ctx p =+ if isTermCtx ctx+ then error "LocallyNameless.freshen' called on a term"+ else return (p, mempty)++ lfreshen' ctx p cont =+ if isTermCtx ctx+ then error "LocallyNameless.lfreshen' called on a term"+ else cont p mempty+++ aeq' ctx (Embed x) (Embed y) = aeq' (termCtx ctx) x y++ fvAny' ctx afa ex@(Embed x) =+ if isTermCtx ctx+ then pure ex+ else Embed <$> fvAny' (termCtx ctx) afa x++ close ctx b (Embed x) =+ if isTermCtx ctx+ then error "LocallyNameless.close on Embed"+ else Embed (close (termCtx ctx) b x)++ open ctx b (Embed x) =+ if isTermCtx ctx+ then error "LocallyNameless.open on Embed"+ else Embed (open (termCtx ctx) b x)++ nthPatFind _ = Left+ namePatFind _ _ = Left 0
+ src/Unbound/Generics/LocallyNameless/Fresh.hs view
@@ -0,0 +1,140 @@+-- |+-- Module : Unbound.Generics.LocallyNameless.Fresh+-- Copyright : (c) 2014, Aleksey Kliger+-- License : BSD3 (See LICENSE)+-- Maintainer : Aleksey Kliger+-- Stability : experimental+--+-- Global freshness monad.+{-# LANGUAGE CPP, GeneralizedNewtypeDeriving,+ FlexibleInstances, MultiParamTypeClasses,+ UndecidableInstances+ #-}+-- (we expect deprecation warnings about Control.Monad.Trans.Error)+{-# OPTIONS_GHC -Wwarn #-}+module Unbound.Generics.LocallyNameless.Fresh where++import Control.Applicative (Applicative, Alternative)+import Control.Monad ()++import Control.Monad.Identity++import Control.Monad.Trans+#if MIN_VERSION_transformers(0,4,0)+import Control.Monad.Trans.Except+#endif+import Control.Monad.Trans.Error+import Control.Monad.Trans.Maybe+import Control.Monad.Trans.Reader+import Control.Monad.Trans.State.Lazy as Lazy+import Control.Monad.Trans.State.Strict as Strict+import Control.Monad.Trans.Writer.Lazy as Lazy+import Control.Monad.Trans.Writer.Strict as Strict++import qualified Control.Monad.Cont.Class as CC+import qualified Control.Monad.Error.Class as EC+import qualified Control.Monad.State.Class as StC+import qualified Control.Monad.Reader.Class as RC+import qualified Control.Monad.Writer.Class as WC++import Data.Monoid (Monoid)++import qualified Control.Monad.State as St++import Unbound.Generics.LocallyNameless.Name++-- | The @Fresh@ type class governs monads which can generate new+-- globally unique 'Name's based on a given 'Name'.+class Monad m => Fresh m where++ -- | Generate a new globally unique name based on the given one.+ fresh :: Name a -> m (Name a)+++-- | The @FreshM@ monad transformer. Keeps track of the lowest index+-- still globally unused, and increments the index every time it is+-- asked for a fresh name.+newtype FreshMT m a = FreshMT { unFreshMT :: St.StateT Integer m a }+ deriving (Functor, Applicative, Alternative, Monad, MonadPlus, MonadIO, MonadFix)++-- | Run a 'FreshMT' computation (with the global index starting at zero).+runFreshMT :: Monad m => FreshMT m a -> m a+runFreshMT m = contFreshMT m 0++-- | Run a 'FreshMT' computation given a starting index for fresh name+-- generation.+contFreshMT :: Monad m => FreshMT m a -> Integer -> m a+contFreshMT (FreshMT m) = St.evalStateT m++instance MonadTrans FreshMT where+ lift = FreshMT . lift++instance CC.MonadCont m => CC.MonadCont (FreshMT m) where+ callCC c = FreshMT $ CC.callCC (unFreshMT . (\k -> c (FreshMT . k)))++instance EC.MonadError e m => EC.MonadError e (FreshMT m) where+ throwError = lift . EC.throwError+ catchError m h = FreshMT $ EC.catchError (unFreshMT m) (unFreshMT . h)++instance StC.MonadState s m => StC.MonadState s (FreshMT m) where+ get = lift StC.get+ put = lift . StC.put++instance RC.MonadReader r m => RC.MonadReader r (FreshMT m) where+ ask = lift RC.ask+ local f = FreshMT . RC.local f . unFreshMT++instance WC.MonadWriter w m => WC.MonadWriter w (FreshMT m) where+ tell = lift . WC.tell+ listen = FreshMT . WC.listen . unFreshMT+ pass = FreshMT . WC.pass . unFreshMT+++instance Monad m => Fresh (FreshMT m) where+ fresh (Fn s _) = FreshMT $ do+ n <- St.get+ St.put $! n + 1+ return $ (Fn s n)+ fresh nm@(Bn {}) = return nm++instance (Error e, Fresh m) => Fresh (ErrorT e m) where+ fresh = lift . fresh++#if MIN_VERSION_transformers(0,4,0)+instance Fresh m => Fresh (ExceptT e m) where+ fresh = lift . fresh+#endif++instance Fresh m => Fresh (MaybeT m) where+ fresh = lift . fresh++instance Fresh m => Fresh (ReaderT r m) where+ fresh = lift . fresh++instance Fresh m => Fresh (Lazy.StateT s m) where+ fresh = lift . fresh++instance Fresh m => Fresh (Strict.StateT s m) where+ fresh = lift . fresh++instance (Monoid w, Fresh m) => Fresh (Lazy.WriterT w m) where+ fresh = lift . fresh++instance (Monoid w, Fresh m) => Fresh (Strict.WriterT w m) where+ fresh = lift . fresh++------------------------------------------------------------+-- FreshM monad++-- | A convenient monad which is an instance of 'Fresh'. It keeps+-- track of a global index used for generating fresh names, which is+-- incremented every time 'fresh' is called.+type FreshM = FreshMT Identity++-- | Run a FreshM computation (with the global index starting at zero).+runFreshM :: FreshM a -> a+runFreshM = runIdentity . runFreshMT++-- | Run a FreshM computation given a starting index.+contFreshM :: FreshM a -> Integer -> a+contFreshM m = runIdentity . contFreshMT m
+ src/Unbound/Generics/LocallyNameless/Internal/Fold.hs view
@@ -0,0 +1,44 @@+-- |+-- Module : Unbound.Generics.LocallyNameless.Internal.Fold+-- Copyright : (c) 2014, Aleksey Kliger+-- License : BSD3 (See LICENSE)+-- Maintainer : Aleksey Kliger+-- Stability : experimental+--+-- | Some utilities for working with Folds.+{-# LANGUAGE RankNTypes #-}+module Unbound.Generics.LocallyNameless.Internal.Fold (Fold, Traversal', toListOf, filtered, justFiltered) where++import Control.Applicative+import Data.Maybe (fromJust)+import Data.Functor.Contravariant+import Data.Monoid++type Getting r s a = (a -> Const r a) -> s -> Const r s++type Fold s a = forall f . (Contravariant f, Applicative f) => (a -> f a) -> s -> f s++type Traversal' s a = forall f . Applicative f => (a -> f a) -> s -> f s++toListOf :: Fold s a -> s -> [a]+-- toListOf :: Getting (Endo [a]) s a -> s -> [a]+toListOf l = foldrOf l (:) []+{-# INLINE toListOf #-}++foldMapOf :: Getting r s a -> (a -> r) -> s -> r+foldMapOf l f = getConst . l (Const . f)+{-# INLINE foldMapOf #-}++foldrOf :: Getting (Endo r) s a -> (a -> r -> r) -> r -> s -> r+foldrOf l f z = fmap (flip appEndo z) (foldMapOf l (Endo .f))+{-# INLINE foldrOf #-}++filtered :: (a -> Bool) -> Traversal' a a+filtered p afa x = if p x then afa x else pure x+{-# INLINE filtered #-}++justFiltered :: (a -> Maybe b) -> Fold a b+justFiltered p bfb x = case p x of+ Just b -> contramap (fromJust . p) (bfb b)+ Nothing -> pure x+{-# INLINE justFiltered #-}
+ src/Unbound/Generics/LocallyNameless/LFresh.hs view
@@ -0,0 +1,220 @@+{-# OPTIONS_HADDOCK show-extensions #-}+-- |+-- Module : Unbound.Generics.LocallyNameless.LFresh+-- Copyright : (c) 2011, Stephanie Weirich+-- License : BSD3 (See LFresh.hs)+-- Maintainer : Aleksey Kliger+-- Stability : experimental+--+-- Local freshness monad.+{-+Copyright (c)2011, Stephanie Weirich++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * 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.++ * Neither the name of Stephanie Weirich nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT+OWNER 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.+-}+-- we expect deprecation warnings about Control.Monad.Trans.Error+{-# OPTIONS_GHC -Wwarn #-}+{-# LANGUAGE CPP+ , GeneralizedNewtypeDeriving+ , FlexibleInstances+ , MultiParamTypeClasses+ , UndecidableInstances #-}+module Unbound.Generics.LocallyNameless.LFresh+ (+ -- * The 'LFresh' class++ LFresh(..),++ LFreshM, runLFreshM, contLFreshM,+ LFreshMT(..), runLFreshMT, contLFreshMT++ ) where++import Data.Set (Set)+import qualified Data.Set as S++import Data.Monoid+import Data.Typeable (Typeable)++import Control.Monad.Reader+import Control.Monad.Identity+import Control.Applicative (Applicative, Alternative)++import Control.Monad.Trans.Cont+import Control.Monad.Trans.Error+#if MIN_VERSION_transformers(0,4,0)+import Control.Monad.Trans.Except+#endif+import Control.Monad.Trans.Identity+import Control.Monad.Trans.List+import Control.Monad.Trans.Maybe+import Control.Monad.Trans.State.Lazy as Lazy+import Control.Monad.Trans.State.Strict as Strict+import Control.Monad.Trans.Writer.Lazy as Lazy+import Control.Monad.Trans.Writer.Strict as Strict++import qualified Control.Monad.Cont.Class as CC+import qualified Control.Monad.Error.Class as EC+import qualified Control.Monad.State.Class as StC+import qualified Control.Monad.Reader.Class as RC+import qualified Control.Monad.Writer.Class as WC++import Unbound.Generics.LocallyNameless.Name++-- | This is the class of monads that support freshness in an+-- (implicit) local scope. Generated names are fresh for the current+-- local scope, not necessarily globally fresh.+class Monad m => LFresh m where+ -- | Pick a new name that is fresh for the current (implicit) scope.+ lfresh :: Typeable a => Name a -> m (Name a)+ -- | Avoid the given names when freshening in the subcomputation,+ -- that is, add the given names to the in-scope set.+ avoid :: [AnyName] -> m a -> m a+ -- | Get the set of names currently being avoided.+ getAvoids :: m (Set AnyName)++-- | The LFresh monad transformer. Keeps track of a set of names to+-- avoid, and when asked for a fresh one will choose the first numeric+-- prefix of the given name which is currently unused.+newtype LFreshMT m a = LFreshMT { unLFreshMT :: ReaderT (Set AnyName) m a }+ deriving (Functor, Applicative, Alternative, Monad, MonadIO, MonadPlus, MonadFix)++-- | Run an 'LFreshMT' computation in an empty context.+runLFreshMT :: LFreshMT m a -> m a+runLFreshMT m = contLFreshMT m S.empty++-- | Run an 'LFreshMT' computation given a set of names to avoid.+contLFreshMT :: LFreshMT m a -> Set AnyName -> m a+contLFreshMT (LFreshMT m) = runReaderT m++instance Monad m => LFresh (LFreshMT m) where+ lfresh nm = LFreshMT $ do+ let s = name2String nm+ used <- ask+ return $ head (filter (\x -> not (S.member (AnyName x) used))+ (map (makeName s) [0..]))+ avoid names = LFreshMT . local (S.union (S.fromList names)) . unLFreshMT++ getAvoids = LFreshMT ask++-- | A convenient monad which is an instance of 'LFresh'. It keeps+-- track of a set of names to avoid, and when asked for a fresh one+-- will choose the first unused numerical name.+type LFreshM = LFreshMT Identity++-- | Run a LFreshM computation in an empty context.+runLFreshM :: LFreshM a -> a+runLFreshM = runIdentity . runLFreshMT++-- | Run a LFreshM computation given a set of names to avoid.+contLFreshM :: LFreshM a -> Set AnyName -> a+contLFreshM m = runIdentity . contLFreshMT m++instance LFresh m => LFresh (ContT r m) where+ lfresh = lift . lfresh+ avoid = mapContT . avoid+ getAvoids = lift getAvoids++instance (Error e, LFresh m) => LFresh (ErrorT e m) where+ lfresh = lift . lfresh+ avoid = mapErrorT . avoid+ getAvoids = lift getAvoids++#if MIN_VERSION_transformers(0,4,0)+instance LFresh m => LFresh (ExceptT e m) where+ lfresh = lift . lfresh+ avoid = mapExceptT . avoid+ getAvoids = lift getAvoids+#endif++instance LFresh m => LFresh (IdentityT m) where+ lfresh = lift . lfresh+ avoid = mapIdentityT . avoid+ getAvoids = lift getAvoids++instance LFresh m => LFresh (ListT m) where+ lfresh = lift . lfresh+ avoid = mapListT . avoid+ getAvoids = lift getAvoids++instance LFresh m => LFresh (MaybeT m) where+ lfresh = lift . lfresh+ avoid = mapMaybeT . avoid+ getAvoids = lift getAvoids++instance LFresh m => LFresh (ReaderT r m) where+ lfresh = lift . lfresh+ avoid = mapReaderT . avoid+ getAvoids = lift getAvoids++instance LFresh m => LFresh (Lazy.StateT s m) where+ lfresh = lift . lfresh+ avoid = Lazy.mapStateT . avoid+ getAvoids = lift getAvoids++instance LFresh m => LFresh (Strict.StateT s m) where+ lfresh = lift . lfresh+ avoid = Strict.mapStateT . avoid+ getAvoids = lift getAvoids++instance (Monoid w, LFresh m) => LFresh (Lazy.WriterT w m) where+ lfresh = lift . lfresh+ avoid = Lazy.mapWriterT . avoid+ getAvoids = lift getAvoids++instance (Monoid w, LFresh m) => LFresh (Strict.WriterT w m) where+ lfresh = lift . lfresh+ avoid = Strict.mapWriterT . avoid+ getAvoids = lift getAvoids++-- Instances for applying LFreshMT to other monads++instance MonadTrans LFreshMT where+ lift = LFreshMT . lift++instance CC.MonadCont m => CC.MonadCont (LFreshMT m) where+ callCC c = LFreshMT $ CC.callCC (unLFreshMT . (\k -> c (LFreshMT . k)))++instance EC.MonadError e m => EC.MonadError e (LFreshMT m) where+ throwError = lift . EC.throwError+ catchError m h = LFreshMT $ EC.catchError (unLFreshMT m) (unLFreshMT . h)++instance StC.MonadState s m => StC.MonadState s (LFreshMT m) where+ get = lift StC.get+ put = lift . StC.put++instance RC.MonadReader r m => RC.MonadReader r (LFreshMT m) where+ ask = lift RC.ask+ local f = LFreshMT . mapReaderT (RC.local f) . unLFreshMT++instance WC.MonadWriter w m => WC.MonadWriter w (LFreshMT m) where+ tell = lift . WC.tell+ listen = LFreshMT . WC.listen . unLFreshMT+ pass = LFreshMT . WC.pass . unLFreshMT
+ src/Unbound/Generics/LocallyNameless/Name.hs view
@@ -0,0 +1,96 @@+-- |+-- Module : Unbound.Generics.LocallyNameless.Name+-- Copyright : (c) 2014, Aleksey Kliger+-- License : BSD3 (See LICENSE)+-- Maintainer : Aleksey Kliger+-- Stability : experimental+--+-- Names stand for values. They may be bound or free.+{-# LANGUAGE DeriveDataTypeable+ , DeriveGeneric+ , ExistentialQuantification+ , FlexibleContexts+ , GADTs #-}+module Unbound.Generics.LocallyNameless.Name+ (+ -- * Names over terms+ Name(..)+ , isFreeName+ -- * Name construction+ , string2Name+ , s2n+ , makeName+ -- * Name inspection+ , name2String+ -- * Heterogeneous names+ , AnyName(..)+ ) where++import Data.Typeable (Typeable, gcast, typeOf)+import GHC.Generics (Generic)++-- | An abstract datatype of names @Name a@ that stand for terms of+-- type @a@. The type @a@ is used as a tag to distinguish these names+-- from names that may stand for other sorts of terms.+--+-- Two names in a term are consider+-- 'Unbound.Generics.LocallyNameless.Operations.aeq' equal when they+-- are the same name (in the sense of '(==)'). In patterns, however,+-- any two names are equal if they occur in the same place within the+-- pattern. This induces alpha equivalence on terms in general.+--+-- Names may either be free or bound (see 'isFreeName'). Free names+-- may be extracted from patterns using+-- 'Unbound.Generics.LocallyNameless.Alpha.isPat'. Bound names+-- cannot be.+-- +data Name a = Fn String !Integer -- free names+ | Bn !Integer !Integer -- bound names / binding level + pattern index+ deriving (Eq, Ord, Typeable, Generic)++-- | Returns 'True' iff the given @Name a@ is free.+isFreeName :: Name a -> Bool+isFreeName (Fn _ _) = True+isFreeName _ = False++-- | Make a free 'Name a' from a 'String'+string2Name :: String -> Name a+string2Name s = makeName s 0++-- | Synonym for 'string2Name'.+s2n :: String -> Name a+s2n = string2Name++-- | Make a name from a 'String' and an 'Integer' index+makeName :: String -> Integer -> Name a+makeName = Fn++-- | Get the string part of a 'Name'.+name2String :: Name a -> String+name2String (Fn s _) = s+name2String (Bn _ _) = error "Internal Error: cannot call name2Integer for bound names"++instance Show (Name a) where+ show (Fn "" n) = "_" ++ (show n)+ show (Fn x 0) = x+ show (Fn x n) = x ++ (show n)+ show (Bn x y) = show x ++ "@" ++ show y++-- | An @AnyName@ is a name that stands for a term of some (existentially hidden) type.+data AnyName where+ AnyName :: Typeable a => Name a -> AnyName++instance Show AnyName where+ show (AnyName nm) = show nm++instance Eq AnyName where+ (AnyName n1) == (AnyName n2) = case gcast n2 of+ Just n2' -> n1 == n2'+ Nothing -> False++instance Ord AnyName where+ compare (AnyName n1) (AnyName n2) = case compare (typeOf n1) (typeOf n2) of+ EQ -> case gcast n2 of+ Just n2' -> compare n1 n2'+ Nothing -> error "Equal type representations, but gcast failed in comparing two AnyName values"+ ord -> ord
+ src/Unbound/Generics/LocallyNameless/Operations.hs view
@@ -0,0 +1,162 @@+-- |+-- Module : Unbound.Generics.LocallyNameless.Operations+-- Copyright : (c) 2014, Aleksey Kliger+-- License : BSD3 (See LICENSE)+-- Maintainer : Aleksey Kliger+-- Stability : experimental+--+-- Operations on terms and patterns that contain names.+{-# LANGUAGE RankNTypes, ScopedTypeVariables #-}+module Unbound.Generics.LocallyNameless.Operations+ (-- * Equivalence, free variables, freshness+ aeq+ , fvAny+ , fv+ , freshen+ , lfreshen+ , swaps+ -- * Binding, unbinding+ , Bind+ , bind+ , unbind+ , lunbind+ , unbind2+ , unbind2Plus+ -- * Rebinding, embedding+ , Rebind+ , rebind+ , unrebind+ , Embed(..)+ , embed+ , unembed+ ) where++import Control.Applicative (Applicative)+import Control.Monad (MonadPlus(mzero))+import Data.Functor.Contravariant (Contravariant)+import Data.Monoid ((<>))+import Data.Typeable (Typeable, cast)+import Unbound.Generics.LocallyNameless.Alpha+import Unbound.Generics.LocallyNameless.Fresh+import Unbound.Generics.LocallyNameless.LFresh+import Unbound.Generics.LocallyNameless.Name+import Unbound.Generics.LocallyNameless.Bind+import Unbound.Generics.LocallyNameless.Embed (Embed(..))+import Unbound.Generics.LocallyNameless.Rebind+import Unbound.Generics.LocallyNameless.Internal.Fold (toListOf, justFiltered)+import Unbound.Generics.PermM++-- | @'aeq' t1 t2@ returns @True@ iff @t1@ and @t2@ are alpha-equivalent terms.+aeq :: Alpha a => a -> a -> Bool+aeq = aeq' initialCtx++-- | @'fvAny' t@ returns the free variables of the term @t@.+--+-- @+-- fvAny :: Alpha a => a -> Fold a AnyName+-- @+fvAny :: (Alpha a, Contravariant f, Applicative f) => (AnyName -> f AnyName) -> a -> f a+fvAny = fvAny' initialCtx++-- | @'fv' t@ returns the free @b@ variables of term @t@.+--+-- @+-- fv :: (Alpha a, Typeable b) => a -> Fold a (Name b)+-- @+fv :: forall a f b . (Alpha a, Typeable b, Contravariant f, Applicative f)+ => (Name b -> f (Name b)) -> a -> f a+fv = fvAny . justFiltered f+ where f :: AnyName -> Maybe (Name b)+ f (AnyName n) = cast n++-- | Freshen a pattern by replacing all old binding 'Name's with new+-- fresh 'Name's, returning a new pattern and a @'Perm' 'Name'@+-- specifying how 'Name's were replaced.+freshen :: (Alpha p, Fresh m) => p -> m (p, Perm AnyName)+freshen = freshen' (patternCtx initialCtx)++-- | \"Locally\" freshen a pattern, replacing all binding names with+-- new names that are not already \"in scope\". The second argument+-- is a continuation, which takes the renamed term and a permutation+-- that specifies how the pattern has been renamed. The resulting+-- computation will be run with the in-scope set extended by the+-- names just generated.+lfreshen :: (Alpha p, LFresh m) => p -> (p -> Perm AnyName -> m b) -> m b+lfreshen = lfreshen' (patternCtx initialCtx)++-- | Apply the given permutation of variable names to the given term.+swaps :: Alpha t => Perm AnyName -> t -> t+swaps = swaps' initialCtx++ +-- | @'bind' p t@ closes over the variables of pattern @p@ in the term @t@+bind :: (Alpha p, Alpha t) => p -> t -> Bind p t+bind p t = B p (close initialCtx p t)++-- | @'unbind' b@ lets you descend beneath a binder @b :: 'Bind' p t@+-- by returning the pair of the pattern @p@ and the term @t@ where the+-- variables in the pattern have been made globally fresh with respect+-- to the freshness monad @m@.+unbind :: (Alpha p, Alpha t, Fresh m) => Bind p t -> m (p, t)+unbind (B p t) = do+ (p', _) <- freshen p+ return (p', open initialCtx p' t)++-- | @lunbind@ opens a binding in an 'LFresh' monad, ensuring that the+-- names chosen for the binders are /locally/ fresh. The components+-- of the binding are passed to a /continuation/, and the resulting+-- monadic action is run in a context extended to avoid choosing new+-- names which are the same as the ones chosen for this binding.+--+-- For more information, see the documentation for the 'LFresh' type+-- class.+lunbind :: (LFresh m, Alpha p, Alpha t) => Bind p t -> ((p, t) -> m c) -> m c+lunbind (B p t) cont =+ lfreshen p (\x _ -> cont (x, open initialCtx x t))+++-- | Simultaneously unbind two patterns in two terms, returning 'Nothing' if+-- the two patterns don't bind the same number of variables.+unbind2 :: (Fresh m, Alpha p1, Alpha p2, Alpha t1, Alpha t2)+ => Bind p1 t1+ -> Bind p2 t2+ -> m (Maybe (p1, t1, p2, t2))+unbind2 (B p1 t1) (B p2 t2) = do+ case mkPerm (toListOf fvAny p2) (toListOf fvAny p1) of+ Just pm -> do+ (p1', pm') <- freshen p1+ return $ Just (p1', open initialCtx p1' t1,+ swaps (pm' <> pm) p2, open initialCtx p1' t2)+ Nothing -> return Nothing+++-- | Simultaneously unbind two patterns in two terms, returning 'mzero' if+-- the patterns don't bind the same number of variables.+unbind2Plus :: (MonadPlus m, Fresh m, Alpha p1, Alpha p2, Alpha t1, Alpha t2)+ => Bind p1 t1+ -> Bind p2 t2+ -> m (p1, t1, p2, t2)+unbind2Plus bnd bnd' = maybe mzero return =<< unbind2 bnd bnd'+++-- | @'rebind' p1 p2@ is a smart constructor for 'Rebind'. It+-- captures the variables of pattern @p1@ that occur within @p2@ in+-- addition to providing binding occurrences for all the variables of @p1@ and @p2@+rebind :: (Alpha p1, Alpha p2) => p1 -> p2 -> Rebind p1 p2+rebind p1 p2 = Rebnd p1 (close (patternCtx initialCtx) p1 p2)++-- | @'unrebind' p@ is the elimination form for 'Rebind'. It is not+-- monadic (unlike 'unbind') because a @Rebind@ pattern can only occur+-- somewhere in a pattern position of a 'Bind', and therefore 'unbind'+-- must have already been called and all names apropriately+-- 'freshen'ed.+unrebind :: (Alpha p1, Alpha p2) => Rebind p1 p2 -> (p1, p2)+unrebind (Rebnd p1 p2) = (p1, open (patternCtx initialCtx) p1 p2)++-- | An alias for 'Embed'+embed :: t -> Embed t+embed = Embed++-- | @'unembed' p@ extracts the term embedded in the pattern @p@.+unembed :: Embed t -> t+unembed (Embed t) = t
+ src/Unbound/Generics/LocallyNameless/Rebind.hs view
@@ -0,0 +1,85 @@+{-# OPTIONS_HADDOCK show-extensions #-}+-- |+-- Module : Unbound.Generics.LocallyNameless.Rebind+-- Copyright : (c) 2014, Aleksey Kliger+-- License : BSD3 (See LICENSE)+-- Maintainer : Aleksey Kliger+-- Stability : experimental+--+-- The pattern @'Rebind' p1 p2@ binds the names in @p1@ and @p2@ just as @(p1, p2)@ would,+-- however it additionally also brings the names of @p1@ into scope in @p2@.+--+{-# LANGUAGE DeriveGeneric #-}+module Unbound.Generics.LocallyNameless.Rebind where++import Control.Applicative ((<*>), (<$>))+import Data.Monoid ((<>))+import GHC.Generics++import Unbound.Generics.LocallyNameless.Alpha+++-- | @'Rebind' p1 p2@ is a pattern that binds the names of @p1@ and @p2@, and additionally+-- brings the names of @p1@ into scope over @p2@.+--+-- This may be used, for example, to faithfully represent Scheme's @let*@ binding form, defined by:+-- +-- > (let* () body) ≙ body+-- > (let* ([v1, e1] binds ...) body) ≙ (let ([v1, e1]) (let* (binds ...) body))+-- +-- using the following AST:+--+-- @+-- type Var = Name Expr+-- data Lets = EmptyLets+-- | Bind (Rebind (Var, Embed Expr) Lets)+-- data Expr = ...+-- | LetStar { letStarBindings :: Lets+-- , letStarBody :: Expr+-- }+-- | ...+-- @+data Rebind p1 p2 = Rebnd p1 p2+ deriving (Generic, Eq)++instance (Show p1, Show p2) => Show (Rebind p1 p2) where+ showsPrec paren (Rebnd p1 p2) =+ showParen (paren > 0) (showString "<<"+ . showsPrec paren p1+ . showString ">> "+ . showsPrec 0 p2)++instance (Alpha p1, Alpha p2) => Alpha (Rebind p1 p2) where+ isTerm _ = False++ isPat (Rebnd p1 p2) = isPat p1 <> isPat p2++ swaps' ctx perm (Rebnd p1 p2) =+ Rebnd (swaps' ctx perm p1) (swaps' (incrLevelCtx ctx) perm p2)++ freshen' ctx (Rebnd p1 p2) =+ if isTermCtx ctx+ then error "freshen' on Rebind in Term mode"+ else do+ (p1', perm1) <- freshen' ctx p1+ (p2', perm2) <- freshen' (incrLevelCtx ctx) (swaps' (incrLevelCtx ctx) perm1 p2)+ return (Rebnd p1' p2', perm1 <> perm2)++ lfreshen' ctx (Rebnd p q) cont =+ if isTermCtx ctx+ then error "lfreshen' on Rebind in Term mode"+ else+ lfreshen' ctx p $ \ p' pm1 ->+ lfreshen' (incrLevelCtx ctx) (swaps' (incrLevelCtx ctx) pm1 q) $ \ q' pm2 ->+ cont (Rebnd p' q') (pm1 <> pm2)+++ aeq' ctx (Rebnd p1 p2) (Rebnd q1 q2) =+ -- XXX TODO: Unbound had (aeq' ctx p2 q2) here. But that doesn't seem right.+ aeq' ctx p1 q1 && aeq' (incrLevelCtx ctx) p2 q2++ fvAny' ctx afa (Rebnd p1 p2) = Rebnd <$> fvAny' ctx afa p1+ <*> fvAny' (incrLevelCtx ctx) afa p2++ open ctx b (Rebnd p1 p2) = Rebnd (open ctx b p1) (open (incrLevelCtx ctx) b p2)+ close ctx b (Rebnd p1 p2) = Rebnd (close ctx b p1) (close (incrLevelCtx ctx) b p2)
+ src/Unbound/Generics/LocallyNameless/Subst.hs view
@@ -0,0 +1,177 @@+-- |+-- Module : Unbound.Generics.LocallyNameless.Subst+-- Copyright : (c) 2014, Aleksey Kliger+-- License : BSD3 (See LICENSE)+-- Maintainer : Aleksey Kliger+-- Stability : experimental+--+-- A typeclass for types that may participate in capture-avoiding substitution+--+-- The minimal definition is empty, provided your type is an instance of 'GHC.Generics.Generic'+-- +-- @+-- type Var = Name Factor+-- data Expr = SumOf [Summand]+-- deriving (Show, Generic)+-- data Summand = ProductOf [Factor]+-- deriving (Show, Generic)+-- instance Subst Var Expr+-- instance Subst Var Summand+-- @+-- +-- The default instance just propagates the substitution into the constituent factors.+-- +-- If you identify the variable occurrences by implementing the 'isvar' function, the derived 'subst' function+-- will be able to substitute a factor for a variable.+-- +-- @+-- data Factor = V Var+-- | C Int+-- | Subexpr Expr+-- deriving (Show, Generic)+-- instance Subst Var Factor where+-- isvar (V v) = Just (SubstName v)+-- isvar _ = Nothing+-- @+--+{-# LANGUAGE DefaultSignatures+ , FlexibleContexts+ , FlexibleInstances+ , GADTs+ , MultiParamTypeClasses+ , ScopedTypeVariables+ , TypeOperators+ #-}+module Unbound.Generics.LocallyNameless.Subst (+ SubstName(..)+ , SubstCoerce(..)+ , Subst(..)+ ) where++import GHC.Generics++import Data.List (find)++import Unbound.Generics.LocallyNameless.Name+import Unbound.Generics.LocallyNameless.Alpha+import Unbound.Generics.LocallyNameless.Embed+import Unbound.Generics.LocallyNameless.Bind+import Unbound.Generics.LocallyNameless.Rebind++-- | See 'isVar'+data SubstName a b where+ SubstName :: (a ~ b) => Name a -> SubstName a b++-- | See 'isCoerceVar' +data SubstCoerce a b where + SubstCoerce :: Name b -> (b -> Maybe a) -> SubstCoerce a b++-- | Instances of @'Subst' b a@ are terms of type @a@ that may contain+-- variables of type @b@ that may participate in capture-avoiding+-- substitution.+class Subst b a where+ -- | This is the only method that must be implemented+ isvar :: a -> Maybe (SubstName a b)+ isvar _ = Nothing++ -- | This is an alternative version to 'isvar', useable in the case + -- that the substituted argument doesn't have *exactly* the same type+ -- as the term it should be substituted into.+ -- The default implementation always returns 'Nothing'.+ isCoerceVar :: a -> Maybe (SubstCoerce a b)+ isCoerceVar _ = Nothing++ -- | @'subst' nm e tm@ substitutes @e@ for @nm@ in @tm@. It has+ -- a default generic implementation in terms of @isvar@+ subst :: Name b -> b -> a -> a+ default subst :: (Generic a, GSubst b (Rep a)) => Name b -> b -> a -> a+ subst n u x =+ if (isFreeName n)+ then case (isvar x :: Maybe (SubstName a b)) of+ Just (SubstName m) -> if m == n then u else x+ Nothing -> case (isCoerceVar x :: Maybe (SubstCoerce a b)) of+ Just (SubstCoerce m f) -> if m == n then maybe x id (f u) else x+ Nothing -> to $ gsubst n u (from x)+ else error $ "Cannot substitute for bound variable " ++ show n++ substs :: [(Name b, b)] -> a -> a+ default substs :: (Generic a, GSubst b (Rep a)) => [(Name b, b)] -> a -> a+ substs ss x+ | all (isFreeName . fst) ss =+ case (isvar x :: Maybe (SubstName a b)) of+ Just (SubstName m) ->+ case find ((==m) . fst) ss of+ Just (_, u) -> u+ Nothing -> x+ Nothing -> case isCoerceVar x :: Maybe (SubstCoerce a b) of + Just (SubstCoerce m f) ->+ case find ((==m) . fst) ss of + Just (_, u) -> maybe x id (f u)+ Nothing -> x+ Nothing -> to $ gsubsts ss (from x)+ | otherwise =+ error $ "Cannot substitute for bound variable in: " ++ show (map fst ss)++---- generic structural substitution.++class GSubst b f where+ gsubst :: Name b -> b -> f c -> f c+ gsubsts :: [(Name b, b)] -> f c -> f c++instance Subst b c => GSubst b (K1 i c) where+ gsubst nm val = K1 . subst nm val . unK1+ gsubsts ss = K1 . substs ss . unK1++instance GSubst b f => GSubst b (M1 i c f) where+ gsubst nm val = M1 . gsubst nm val . unM1+ gsubsts ss = M1 . gsubsts ss . unM1++instance GSubst b U1 where+ gsubst _nm _val _ = U1+ gsubsts _ss _ = U1++instance GSubst b V1 where+ gsubst _nm _val = id+ gsubsts _ss = id++instance (GSubst b f, GSubst b g) => GSubst b (f :*: g) where+ gsubst nm val (f :*: g) = gsubst nm val f :*: gsubst nm val g+ gsubsts ss (f :*: g) = gsubsts ss f :*: gsubsts ss g++instance (GSubst b f, GSubst b g) => GSubst b (f :+: g) where+ gsubst nm val (L1 f) = L1 $ gsubst nm val f+ gsubst nm val (R1 g) = R1 $ gsubst nm val g++ gsubsts ss (L1 f) = L1 $ gsubsts ss f+ gsubsts ss (R1 g) = R1 $ gsubsts ss g++-- these have a Generic instance, but+-- it's self-refential (ie: Rep Int = D1 (C1 (S1 (Rec0 Int))))+-- so our structural GSubst instances get stuck in an infinite loop.+instance Subst b Int where subst _ _ = id ; substs _ = id+instance Subst b Bool where subst _ _ = id ; substs _ = id+instance Subst b () where subst _ _ = id ; substs _ = id+instance Subst b Char where subst _ _ = id ; substs _ = id+instance Subst b Float where subst _ _ = id ; substs _ = id+instance Subst b Double where subst _ _ = id ; substs _ = id++-- huh, apparently there's no instance Generic Integer. +instance Subst b Integer where subst _ _ = id ; substs _ = id++instance (Subst c a, Subst c b) => Subst c (a,b)+instance (Subst c a, Subst c b, Subst c d) => Subst c (a,b,d)+instance (Subst c a, Subst c b, Subst c d, Subst c e) => Subst c (a,b,d,e)+instance (Subst c a, Subst c b, Subst c d, Subst c e, Subst c f) =>+ Subst c (a,b,d,e,f)+instance (Subst c a) => Subst c [a]+instance (Subst c a) => Subst c (Maybe a)+instance (Subst c a, Subst c b) => Subst c (Either a b)++instance Generic a => Subst b (Name a) where subst _ _ = id ; substs _ = id+instance Subst b AnyName where subst _ _ = id ; substs _ = id++instance (Subst c a) => Subst c (Embed a)++instance (Subst c b, Subst c a, Alpha a, Alpha b) => Subst c (Bind a b)++instance (Subst c p1, Subst c p2) => Subst c (Rebind p1 p2)
+ src/Unbound/Generics/LocallyNameless/Unsafe.hs view
@@ -0,0 +1,24 @@+{-# OPTIONS_HADDOCK show-extensions #-}+-- |+-- Module : Unbound.Generics.LocallyNameless.Unsafe+-- Copyright : (c) 2014, Aleksey Kliger+-- License : BSD3 (See LICENSE)+-- Maintainer : Aleksey Kliger+-- Stability : experimental+--+-- Dangerous operations that may disturb the invariants of+-- "Unbind.Generics.LocallyNameless" or of your AST.+{-# LANGUAGE DeriveGeneric #-}+module Unbound.Generics.LocallyNameless.Unsafe+ (+ unsafeUnbind+ ) where++import Unbound.Generics.LocallyNameless.Alpha+import Unbound.Generics.LocallyNameless.Bind++-- | A destructor for binders that does /not/ guarantee fresh+-- names for the binders.+unsafeUnbind :: (Alpha p, Alpha t) => Bind p t -> (p, t)+unsafeUnbind (B p t) = (p, open initialCtx p t)+
+ src/Unbound/Generics/PermM.hs view
@@ -0,0 +1,165 @@+----------------------------------------------------------------------+-- |+-- Module : Unbound.Generics.PermM+-- Copyright : (c) 2011, Stephanie Weirich <sweirich@cis.upenn.edu>+-- License : BSD-like (see PermM.hs)+-- Maintainer : Aleksey Kliger+-- Portability : portable+--+-- A slow, but hopefully correct implementation of permutations.+--+----------------------------------------------------------------------+{-+Copyright (c)2011, Stephanie Weirich++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * 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.++ * Neither the name of Stephanie Weirich nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT+OWNER 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.+-}+{-# LANGUAGE PatternGuards #-}+module Unbound.Generics.PermM (+ Perm(..), permValid, single, compose, apply, support, isid, join, empty, restrict, mkPerm+ ) where++import Prelude (Eq(..), Show(..), (.), ($), Monad(return), Ord(..), Maybe(..), otherwise, (&&), Bool(..), id, uncurry, Functor(..))+import Data.Monoid+import Data.List+import Data.Map (Map)+import qualified Data.Map as M+import qualified Data.Set as S+import Control.Arrow ((&&&))+import Control.Monad ((>=>))++-- | A /permutation/ is a bijective function from names to names+-- which is the identity on all but a finite set of names. They+-- form the basis for nominal approaches to binding, but can+-- also be useful in general.+newtype Perm a = Perm (Map a a)++-- | @'permValid' p@ returns @True@ iff the perumation is /valid/: if+-- each value in the range of the permutation is also a key.+permValid :: Ord a => Perm a -> Bool+permValid (Perm p) = all (\(_,v) -> M.member v p) (M.assocs p)+ -- a Map sends every key uniquely to a value by construction. So if+ -- every value is also a key, the sizes of the domain and range must+ -- be equal and hence the mapping is a bijection.++instance Ord a => Eq (Perm a) where+ (Perm p1) == (Perm p2) =+ all (\x -> M.findWithDefault x x p1 == M.findWithDefault x x p2) (M.keys p1) &&+ all (\x -> M.findWithDefault x x p1 == M.findWithDefault x x p2) (M.keys p2)++instance Show a => Show (Perm a) where+ show (Perm p) = show p++-- | Apply a permutation to an element of the domain.+apply :: Ord a => Perm a -> a -> a+apply (Perm p) x = M.findWithDefault x x p++-- | Create a permutation which swaps two elements.+single :: Ord a => a -> a -> Perm a+single x y = if x == y then Perm M.empty else+ Perm (M.insert x y (M.insert y x M.empty))++-- | The empty (identity) permutation.+empty :: Perm a+empty = Perm M.empty++-- | Compose two permutations. The right-hand permutation will be+-- applied first.+compose :: Ord a => Perm a -> Perm a -> Perm a+compose (Perm b) (Perm a) =+ Perm (M.fromList ([ (x,M.findWithDefault y y b) | (x,y) <- M.toList a]+ ++ [ (x, M.findWithDefault x x b) | x <- M.keys b, M.notMember x a]))++-- | Permutations form a monoid under composition.+instance Ord a => Monoid (Perm a) where+ mempty = empty+ mappend = compose++-- | Is this the identity permutation?+isid :: Ord a => Perm a -> Bool+isid (Perm p) =+ M.foldrWithKey (\ a b r -> r && a == b) True p++-- | /Join/ two permutations by taking the union of their relation+-- graphs. Fail if they are inconsistent, i.e. map the same element+-- to two different elements.+join :: Ord a => Perm a -> Perm a -> Maybe (Perm a)+join (Perm p1) (Perm p2) =+ let overlap = M.intersectionWith (==) p1 p2 in+ if M.fold (&&) True overlap then+ Just (Perm (M.union p1 p2))+ else Nothing++-- | The /support/ of a permutation is the set of elements which are+-- not fixed.+support :: Ord a => Perm a -> [a]+support (Perm p) = [ x | x <- M.keys p, M.findWithDefault x x p /= x]++-- | Restrict a permutation to a certain domain.+restrict :: Ord a => Perm a -> [a] -> Perm a+restrict (Perm p) l = Perm (foldl' (\p' k -> M.delete k p') p l)++-- | A partial permutation consists of two maps, one in each direction+-- (inputs -> outputs and outputs -> inputs).+data PartialPerm a = PP (M.Map a a) (M.Map a a)+ deriving Show++emptyPP :: PartialPerm a+emptyPP = PP M.empty M.empty++extendPP :: Ord a => a -> a -> PartialPerm a -> Maybe (PartialPerm a)+extendPP x y pp@(PP mfwd mrev)+ | Just y' <- M.lookup x mfwd = if y == y' then Just pp+ else Nothing+ | Just x' <- M.lookup y mrev = if x == x' then Just pp+ else Nothing+ | otherwise = Just $ PP (M.insert x y mfwd) (M.insert y x mrev)++-- | Convert a partial permutation into a full permutation by closing+-- off any remaining open chains into a cycles.+ppToPerm :: Ord a => PartialPerm a -> Perm a+ppToPerm (PP mfwd mrev) = Perm $ foldr (uncurry M.insert) mfwd+ (map (findEnd &&& id) chainStarts)+ -- beginnings of open chains are elements which map to+ -- something in the forward direction but have no ancestor.+ where chainStarts = S.toList (M.keysSet mfwd `S.difference` M.keysSet mrev)+ findEnd x = case M.lookup x mfwd of+ Nothing -> x+ Just x' -> findEnd x'++-- | @mkPerm l1 l2@ creates a permutation that sends @l1@ to @l2@.+-- Fail if there is no such permutation, either because the lists+-- have different lengths or because they are inconsistent (which+-- can only happen if @l1@ or @l2@ have repeated elements).+mkPerm :: Ord a => [a] -> [a] -> Maybe (Perm a)+mkPerm xs ys+ | length xs /= length ys = Nothing+ | otherwise =+ fmap ppToPerm . ($emptyPP) . foldr (>=>) return $ zipWith extendPP xs ys
+ test/Calc.hs view
@@ -0,0 +1,165 @@+-- |+-- Module : Calc+-- Copyright : (c) 2014, Aleksey Kliger+-- License : BSD3 (See LICENSE)+-- Maintainer : Aleksey Kliger+-- Stability : experimental+--+{-# LANGUAGE DeriveGeneric, DeriveDataTypeable #-}+module Calc where++import Control.Arrow (second)+import Data.Typeable (Typeable)+import GHC.Generics (Generic)++import Unbound.Generics.LocallyNameless+import Unbound.Generics.LocallyNameless.Internal.Fold (toListOf)++-- variables will range over expressions+type Var = Name Expr++-- expression is either a variable, a constant int, a summation of two+-- expressions, a list of variables bound to expressions that may+-- occur in the body of an expression (where the expressions in the+-- list of bindings refer to an outer scope), or a sequence of nested bindings+-- where each binding expression can refer to previously bound variables.+data Expr = V Var+ | C Int+ | Add Expr Expr+ | Let (Bind [(Var, Embed Expr)] Expr)+ | LetStar (Bind LetStarBinds Expr)+ deriving (Generic, Typeable, Show)++data LetStarBinds = EmptyLSB+ | ConsLSB (Rebind (Var, Embed Expr) LetStarBinds)+ deriving (Generic, Typeable, Show)++instance Alpha Expr+instance Alpha LetStarBinds++mkVar :: String -> Var+mkVar = s2n++anyFreeVarList :: Alpha a => a -> [AnyName]+anyFreeVarList = toListOf fvAny++freeVarList :: (Alpha a, Typeable b) => a -> [Name b]+freeVarList = toListOf fv++-- smart constructor for Let+mkLet :: [(Var, Expr)] -> Expr -> Expr+mkLet binds body = Let (bind (map (second Embed) binds) body)++-- smart constructor for Let*+mkLetStar :: [(Var, Expr)] -> Expr -> Expr+mkLetStar binds body = LetStar (bind (mkLsb binds) body)+ where+ mkLsb [] = EmptyLSB+ mkLsb ((v,e):rest) = ConsLSB (rebind (v, Embed e) (mkLsb rest))++-- environments are partial maps from (free) variables to expressions.+type Env = Var -> Maybe Expr++emptyEnv :: Env+emptyEnv = const Nothing++extendEnv :: Var -> Expr -> Env -> Env+extendEnv v e rho w =+ if v == w then Just e else rho w++whnf :: (Fresh m) => Env -> Expr -> m Expr+whnf rho (V v) = case rho v of+ Just e -> return e+ Nothing -> fail $ "unbound variable " ++ show v+whnf _rho (C i) = return (C i)+whnf rho (Add e1 e2) = do+ v1 <- whnf rho e1+ v2 <- whnf rho e2+ add v1 v2+ where add :: Monad m => Expr -> Expr -> m Expr+ add (C i1) (C i2) = return (C $ i1 + i2)+ add _ _ = fail "add of two non-integers"+whnf rho0 (Let b) = do+ (binds, body) <- unbind b+ binds' <- mapM (\(v, Embed e) -> do+ e' <- whnf rho0 e+ return (v, e')) binds+ let rho' = foldl (\rho (v,e) -> extendEnv v e rho) rho0 binds'+ whnf rho' body+whnf rho0 (LetStar b) = do+ (lsb, body) <- unbind b+ rho' <- whnfLsb lsb rho0+ whnf rho' body++whnfLsb :: Fresh m => LetStarBinds -> Env -> m Env+whnfLsb EmptyLSB = return+whnfLsb (ConsLSB rbnd) = \rho -> do+ let ((v, Embed e), lsb) = unrebind rbnd+ e' <- whnf rho e+ whnfLsb lsb (extendEnv v e' rho)++runWhnf :: Env -> Expr -> Maybe Expr+runWhnf rho e = runFreshMT (whnf rho e)++ex1 :: Expr+ex1 = Add (C 1) (C 2)++ex2x :: Expr+ex2x = V (mkVar "x")++ex2y :: Expr+ex2y = V (mkVar "y")++ex2xc :: Expr+ex2xc = close initialCtx (mkVar "x") ex2x++ex2yc :: Expr+ex2yc = close initialCtx (mkVar "y") ex2y++ex3x :: Expr+ex3x = let x = mkVar "x"+ in mkLet [(x, (C 1))] $ Add (V x) (C 2)++ex3y :: Expr+ex3y = let y = mkVar "y"+ in mkLet [(y, (C 1))] $ Add (V y) (C 2)++ex4 :: Expr+ex4 = let+ x = mkVar "x"+ y = mkVar "y"+ in+ mkLet [(y, (C 5))]+ $ mkLet [(y, (C 200))+ , (x, (Add (V y) -- refers to the outer y+ (C 6)))]+ $ Add (V x) (V x) -- expect (C 22), not (C 412)++ex4_ans :: Expr+ex4_ans = C 22+ +ex5 :: Expr+ex5 = let+ x = mkVar "x"+ y = mkVar "y"+ in+ mkLet [(y, (C 5))]+ $ mkLetStar [(y, (C 200))+ , (x, (Add (V y) -- refers to the inner y+ (C 6)))]+ $ Add (V x) (V x) -- expect (C 412), not (C 22)++ex5_ans :: Expr+ex5_ans = C 412++ex6 :: [Expr]+ex6 = [V (mkVar "x"), V (mkVar "z"), mkLet [(mkVar "y", C 1)] (V (mkVar "y"))]++ex6_ans :: [AnyName]+ex6_ans = [AnyName (mkVar "x"), AnyName (mkVar "z")]++ex7 :: [Expr]+ex7 = [V (mkVar "x"), V (mkVar "z"), mkLet [(mkVar "y", C 1)] (V (mkVar "y"))]++ex7_ans :: [Var]+ex7_ans = [mkVar "x", mkVar "z"]
+ test/ParallelReduction.hs view
@@ -0,0 +1,92 @@+--+-- We implement the parallel reduction relation e ⇛ e'+-- that is a key to some kinds of confluence proofs for lambda calculi.+{-# LANGUAGE DeriveGeneric, DeriveDataTypeable, MultiParamTypeClasses #-}+module ParallelReduction where++import Control.Applicative +import Control.Monad.Identity+import GHC.Generics (Generic)+import Data.Typeable (Typeable)++import Unbound.Generics.LocallyNameless++type Var = Name Expr++-- in this case we just have an untyped lambda calculus+data Expr = V Var+ | Lam (Bind Var Expr)+ | App Expr Expr+ deriving (Show, Generic, Typeable)++instance Alpha Expr++instance Subst Expr Expr where+ isvar (V n) = Just (SubstName n)+ isvar _ = Nothing++run :: FreshMT Identity Expr -> Expr+run = runIdentity . runFreshMT++-- parallel reduction is the compatible closure of cbn beta : (λx.e)f ⇛ {f'/x}e' where e⇛e' and f⇛f'+-- we choose to allow reduction under a lambda.+parStep :: (Applicative m, Fresh m) => Expr -> m Expr+parStep v@(V _) = return v+parStep (App e1 e2) =+ case e1 of+ Lam b -> betaStep e2 b+ _ -> App <$> parStep e1 <*> parStep e2+parStep (Lam b) = do+ (v, e) <- unbind b+ (Lam . (bind v)) <$> parStep e++betaStep :: (Applicative m, Fresh m) => Expr -> Bind Var Expr -> m Expr+betaStep f b = do+ f' <- parStep f+ (v, e) <- unbind b+ e' <- parStep e+ return (subst v f' e')++-- repeatedly take parStep steps until the term doesn't change anymore.+parSteps :: (Applicative m, Fresh m) => Expr -> m Expr+parSteps e = do+ e' <- parStep e+ if (e `aeq` e')+ then return e+ else parSteps e'++ex1 :: Bind Var Expr+ex1 = let+ x = s2n "x"+ in bind x (V x)+ex1' :: (Var, Expr)+ex1' = let+ y = s2n "y"+ in+ (y, V y)++ex2 :: Expr+ex2 = App (Lam ex2_1) ex2_2++ex2_1 :: Bind Var Expr+ex2_1 = let+ x = s2n "x"+ y = s2n "y"+ in+ bind x $ Lam $ bind y $ App (V x) (V y)++ex2_2 :: Expr+ex2_2 = let+ z = s2n "z"+ in+ (Lam $ bind z $ V z)++ident :: Expr+ident = let+ u = s2n "u"+ in Lam $ bind u $ V u++-- same as ex2 but with some extra beta redices in both halves of the application+ex2_alt :: Expr+ex2_alt = App (App ident (Lam ex2_1)) (App (App ident ident) ex2_2)+
+ test/test-calc.hs view
@@ -0,0 +1,55 @@+-- |+-- Module : test-stlc+-- Copyright : (c) 2014, Aleksey Kliger+-- License : BSD3 (See LICENSE)+-- Maintainer : Aleksey Kliger+-- Stability : experimental+--+{-# LANGUAGE DeriveGeneric, DeriveDataTypeable #-}+module Main where++import Unbound.Generics.LocallyNameless++import Calc++import Test.HUnit++test_ex1 :: Test+test_ex1 = TestCase $ assertBool "example 1" (runWhnf emptyEnv ex1 `aeq` (Just $ C 3))++test_ex2_open :: Test+test_ex2_open = TestCase $ assertBool "example 2 (open)" (not $ aeq ex2x ex2y)++test_ex2_closed :: Test+test_ex2_closed = TestCase $ assertBool "example 2 (closed)" (aeq ex2xc ex2yc)++test_ex3 :: Test+test_ex3 = TestCase $ assertBool "example 3" (aeq ex3x ex3y)++test_ex4 :: Test+test_ex4 = TestCase $ assertBool "example 4 (let scoping)" (runWhnf emptyEnv ex4 `aeq` Just ex4_ans)++test_ex5 :: Test+test_ex5 = TestCase $ assertBool "example 5 (let* scoping)" (runWhnf emptyEnv ex5 `aeq` Just ex5_ans)++test_ex6 :: Test+test_ex6 = TestCase $ assertBool "example 6 (free variables)" (anyFreeVarList ex6 `aeq` ex6_ans)++test_ex7 :: Test+test_ex7 = TestCase $ assertBool "example 7 (sorted free variables)" (freeVarList ex7 `aeq` ex7_ans)+++main :: IO ()+main = do+ result <- runTestTT $ TestList [test_ex1+ , test_ex2_open+ , test_ex2_closed+ , test_ex3+ , test_ex4+ , test_ex5+ , test_ex6+ , test_ex7+ ]+ if failures result > 0+ then fail "Some tests failed!"+ else return ()
+ test/test-parallelreduction.hs view
@@ -0,0 +1,25 @@+module Main where++import Test.HUnit++import Unbound.Generics.LocallyNameless (subst, aeq)++import ParallelReduction+++++test_ex1 :: Test+test_ex1 = TestCase $ assertBool "simple substitution" (subst (fst ex1') (Lam ex1) (snd ex1') `aeq` (Lam ex1))++test_ex2 :: Test+test_ex2 = TestCase $ assertBool "parallel reduction" (run (parSteps ex2) `aeq` run (parSteps ex2_alt))++main :: IO ()+main = do+ result <- runTestTT $ TestList [test_ex1+ , test_ex2+ ]+ if failures result > 0+ then fail "Some tests failed!"+ else return ()
+ unbound-generics.cabal view
@@ -0,0 +1,83 @@+-- Initial unbound-generics.cabal generated by cabal init. For further +-- documentation, see http://haskell.org/cabal/users-guide/++name: unbound-generics+version: 0.0.0.90+synopsis: Reimplementation of Unbound using GHC Generics+description: Specify the binding structure of your data type with an+ expressive set of type combinators, and unbound-generics+ handles the rest! Automatically derives+ alpha-equivalence, free variable calculation,+ capture-avoiding substitution, and more. See+ @Unbound.Generics.LocallyNameless@ to get started.+ .+ This is an independent re-implementation of <http://hackage.haskell.org/package/unbound Unbound>+ but using <http://www.haskell.org/ghc/docs/latest/html/libraries/base-4.7.0.1/GHC-Generics.html GHC.Generics>+ instead of <http://http://hackage.haskell.org/package/RepLib RepLib>.+ See the accompanying README for some porting notes.+ +homepage: http://github.com/lambdageek/unbound-generics+bug-reports: http://github.com/lambdageek/unbound-generics/issues+license: BSD3+license-file: LICENSE+author: Aleksey Kliger+maintainer: aleksey@lambdageek.org+copyright: (c) 2014, Aleksey Kliger+category: Language+build-type: Simple+extra-source-files: examples/*.hs,+ README.md,+ Changelog.md+cabal-version: >=1.10++library+ exposed-modules: Unbound.Generics.LocallyNameless+ Unbound.Generics.LocallyNameless.Name+ Unbound.Generics.LocallyNameless.Fresh+ Unbound.Generics.LocallyNameless.LFresh+ Unbound.Generics.LocallyNameless.Alpha+ Unbound.Generics.LocallyNameless.Bind+ Unbound.Generics.LocallyNameless.Rebind+ Unbound.Generics.LocallyNameless.Embed+ Unbound.Generics.LocallyNameless.Operations+ Unbound.Generics.LocallyNameless.Unsafe+ Unbound.Generics.LocallyNameless.Internal.Fold+ Unbound.Generics.PermM+ Unbound.Generics.LocallyNameless.Subst+ -- other-modules: + -- other-extensions: + build-depends: base >=4.6 && <5,+ mtl >= 2.1,+ transformers >= 0.3,+ containers == 0.5.*,+ contravariant >= 0.5+ hs-source-dirs: src+ default-language: Haskell2010+ ghc-options: -Wall++Test-Suite test-calc+ type: exitcode-stdio-1.0+ main-is: test-calc.hs+ other-modules: Calc+ build-depends: base,+ HUnit == 1.2.*,+ unbound-generics+ hs-source-dirs: test+ default-language: Haskell2010+ ghc-options: -Wall++Test-Suite test-parallelreduction+ type: exitcode-stdio-1.0+ main-is: test-parallelreduction.hs+ other-modules: ParallelReduction+ build-depends: base,+ HUnit == 1.2.*,+ mtl >= 2.1,+ unbound-generics+ hs-source-dirs: test+ default-language: Haskell2010+ ghc-options: -Wall++source-repository head+ type: git+ location: git://github.com/lambdageek/unbound-generics.git