packages feed

unbound-generics 0.1.2.1 → 0.2

raw patch · 12 files changed

+281/−8 lines, 12 filesdep +criteriondep +deepseqdep +deepseq-genericsdep ~basePVP ok

version bump matches the API change (PVP)

Dependencies added: criterion, deepseq, deepseq-generics

Dependency ranges changed: base

API changes (from Hackage documentation)

+ Unbound.Generics.LocallyNameless.Alpha: data FFM f a
+ Unbound.Generics.LocallyNameless.Alpha: instance Applicative (FFM f)
+ Unbound.Generics.LocallyNameless.Alpha: instance Fresh m => Fresh (FFM m)
+ Unbound.Generics.LocallyNameless.Alpha: instance Functor (FFM f)
+ Unbound.Generics.LocallyNameless.Alpha: instance Monad (FFM f)
+ Unbound.Generics.LocallyNameless.Alpha: liftFFM :: Monad m => m a -> FFM m a
+ Unbound.Generics.LocallyNameless.Alpha: retractFFM :: Monad m => FFM m a -> m a
+ Unbound.Generics.LocallyNameless.Bind: instance (NFData p, NFData t) => NFData (Bind p t)
+ Unbound.Generics.LocallyNameless.Embed: instance NFData t => NFData (Embed t)
+ Unbound.Generics.LocallyNameless.Embed: instance Ord t => Ord (Embed t)
+ Unbound.Generics.LocallyNameless.Name: instance NFData (Name a)
+ Unbound.Generics.LocallyNameless.Rebind: instance (NFData p1, NFData p2) => NFData (Rebind p1 p2)
+ Unbound.Generics.LocallyNameless.Rec: instance NFData p => NFData (Rec p)
+ Unbound.Generics.LocallyNameless.Shift: instance NFData e => NFData (Shift e)
- Unbound.Generics.LocallyNameless.Alpha: class Show a => Alpha a where aeq' c = (gaeq c) `on` from fvAny' c nfn = fmap to . gfvAny c nfn . from close c b = to . gclose c b . from open c b = to . gopen c b . from isPat = gisPat . from isTerm = gisTerm . from isEmbed _ = False nthPatFind = gnthPatFind . from namePatFind = gnamePatFind . from swaps' ctx perm = to . gswaps ctx perm . from lfreshen' ctx m cont = glfreshen ctx (from m) (cont . to) freshen' ctx = liftM (first to) . gfreshen ctx . from acompare' c = (gacompare c) `on` from
+ Unbound.Generics.LocallyNameless.Alpha: class Show a => Alpha a where aeq' c = (gaeq c) `on` from fvAny' c nfn = fmap to . gfvAny c nfn . from close c b = to . gclose c b . from open c b = to . gopen c b . from isPat = gisPat . from isTerm = gisTerm . from isEmbed _ = False nthPatFind = gnthPatFind . from namePatFind = gnamePatFind . from swaps' ctx perm = to . gswaps ctx perm . from lfreshen' ctx m cont = glfreshen ctx (from m) (cont . to) freshen' ctx = retractFFM . liftM (first to) . gfreshen ctx . from acompare' c = (gacompare c) `on` from
- Unbound.Generics.LocallyNameless.Alpha: gfreshen :: (GAlpha f, Fresh m) => AlphaCtx -> f a -> m (f a, Perm AnyName)
+ Unbound.Generics.LocallyNameless.Alpha: gfreshen :: (GAlpha f, Fresh m) => AlphaCtx -> f a -> FFM m (f a, Perm AnyName)

Files

Changelog.md view
@@ -1,3 +1,22 @@+# 0.2++* Incorporating some of the extras/oversights from+  [clash-lib Unbound.Generics.LocallyNameless.Extra](https://github.com/clash-lang/clash-compiler/blob/master/clash-lib/src/Unbound/Generics/LocallyNameless/Extra.hs)++	* Make `Embed` an instance of `Ord`+	* `NFData` instances (see below)++* Re-implement `freshen'` and `gfreshen` using a free monad to give+  GHC a chance to inline it all away.  This changes the type of+  `gfreshen`.  Major version bump.++	* Expose `FFM`, `liftFFM` and `retractFFM`++* Provide `NFData` instances for all the combinators.+  Depend on 'deepseq'++* Start benchmarking some of the operations (particularly `unbind`).+ # 0.1.2.1  * Fix ghc-7.10 build.
README.md view
@@ -2,9 +2,70 @@  [![Build Status](https://travis-ci.org/lambdageek/unbound-generics.svg)](https://travis-ci.org/lambdageek/unbound-generics) +Support for programming with names and binders using GHC Generics. +## Summary++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`](src/Unbound/Generics/LocallyNameless.hs) to get started.+ 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). +## Example++Here is how you would implement call by value evaluation for the untyped lambda calculus:++```haskell+{-# LANGUAGE DeriveDataTypeable, DeriveGeneric, MultiParamTypeClasses #-}+module UntypedLambdaCalc where+import Unbound.Generics.LocallyNameless+import GHC.Generics (Generic)+import Data.Typeable (Typeable)++-- | Variables stand for expressions+type Var = Name Expr++-- | Expressions+data Expr = V Var                -- ^ variables+          | Lam (Bind Var Expr) -- ^ lambdas bind a variable within a body expression+          | App Expr Expr       -- ^ application+          deriving (Show, Generic, Typeable)++-- Automatically construct alpha equivalence, free variable computation and binding operations.+instance Alpha Expr++-- semi-automatically implement capture avoiding substitution of expressions for expressions+instance Subst Expr Expr where+  -- `isvar` identifies the variable case in your AST.+  isvar (V x) = Just (SubstName x)+  isvar _     = Nothing++-- evaluation takes an expression and returns a value while using a source of fresh names+eval :: Expr -> FreshM Expr+eval (V x) = fail $ "unbound variable " ++ show x+eval e@(Lam {}) = return e+eval (App e1 e2) = do+  v1 <- eval e1+  v2 <- eval e2+  case v1 of+   (Lam bnd) -> do+     -- open the lambda by picking a fresh name for the bound variable x in body+     (x, body) <- unbind bnd+     let body' = subst x v2 body+     eval body'+   _ -> fail "application of non-lambda"++example :: Expr+example =+  let x = s2n "x"+      y = s2n "y"+      e = Lam $ bind x (Lam $ bind y (App (V y) (V x)))+  in runFreshM $ eval (App (App e e) e)+  +-- >>> example+-- Lam (<y> App (V 0@0) (Lam (<x> Lam (<y> App (V 0@0) (V 1@0)))))++``` ## Differences from `unbound`  For the most part, I tried to keep the same methods with the same signatures.  However there are a few differences.
+ benchmarks/BenchLam.hs view
@@ -0,0 +1,75 @@+-- | Untyped lambda calc for benchmarking+{-# LANGUAGE DeriveGeneric, DeriveDataTypeable #-}+module BenchLam where++import Control.Applicative+import Control.Monad (replicateM)+import Data.List (foldl')++import GHC.Generics (Generic)+import Data.Typeable (Typeable)++import Control.DeepSeq (NFData(..), deepseq)+import Control.DeepSeq.Generics (genericRnf)+import Criterion (Benchmark, env, bench, nf)++import Unbound.Generics.LocallyNameless++type Var = Name Term++data Term =+  V !Var+  | App !Term !Term+  | Lam !(Bind Var Term)+    deriving (Show, Generic, Typeable)++instance Alpha Term+instance NFData Term where rnf = genericRnf+++-- | lambda abstract over all the given vars+lams :: [Var] -> Term -> Term+lams [] = id+lams (v:vs) = Lam . bind v . lams vs++-- | apply the given term to the given terms+apps :: Term -> [Term] -> Term+apps = foldl' App++-- eta-expand a term a given number of times+etaN :: Fresh m => Term -> Int -> m Term+etaN m n = do+  vs <- replicateM n (fresh $ s2n "v")+  let ms = map V vs+  return (lams vs $ apps m ms)++-- | While the head is a lambda, descend under it and then do something;+-- then close all the lambdas back up.+workHeadUnderLams :: Fresh m => (Term -> m Term) -> (Term -> m Term)+workHeadUnderLams comp = go+  where+    go m =+      case m of+      Lam bnd -> do+        (x, m') <- unbind bnd+        m'' <- go m'+        return $ Lam $ bind x m''+      _ -> comp m++freshNeutralTermHead :: (Applicative m, Fresh m) => Term -> m Term+freshNeutralTermHead (App m n) = App <$> freshNeutralTermHead m <*> pure n+freshNeutralTermHead (V v) = V <$> fresh v+freshNeutralTermHead lam@(Lam {}) = return lam++-- | A benchmark that creates an eta expansion of the term "x" of a given size+-- and then freshens the "x" by traversing down below all the lambdas.+--+-- Every time we go under a lambda, we freshen the body, so to go+-- under N lambdas, we do O(N²) work.+freshenEtaTermBench :: Int -> Benchmark+freshenEtaTermBench n =+  let name = "freshen eta term of size " ++ show n+  in name `deepseq` env setup $ \m ->+  bench name $ nf (runFreshM . workHeadUnderLams freshNeutralTermHead) m+  where+    setup = return $ runFreshM $ etaN (V $ s2n "x") n
+ benchmarks/benchmark-main.hs view
@@ -0,0 +1,20 @@+module Main where++import Criterion.Main++import BenchLam (freshenEtaTermBench)++main :: IO ()+main =+  defaultMain+  [+    bgroup "unbound-generics" [+        freshenEtaTermBench 10+        , freshenEtaTermBench 20+        , freshenEtaTermBench 30+        , freshenEtaTermBench 40+        , freshenEtaTermBench 50+        , freshenEtaTermBench 100+        , freshenEtaTermBench 200+        ]+  ]
src/Unbound/Generics/LocallyNameless/Alpha.hs view
@@ -9,7 +9,9 @@ -- Use the 'Alpha' typeclass to mark types that may contain 'Name's. {-# LANGUAGE DefaultSignatures              , FlexibleContexts-             , TypeOperators #-}+             , TypeOperators+             , RankNTypes+  #-} module Unbound.Generics.LocallyNameless.Alpha (   -- * Name-aware opertions   Alpha(..)@@ -43,6 +45,10 @@   , gfreshen   , glfreshen   , gacompare+    -- ** Interal helpers for gfreshen+  , FFM+  , liftFFM+  , retractFFM   ) where  import Control.Applicative (Applicative(..), (<$>))@@ -216,13 +222,59 @@   -- 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+  freshen' ctx = retractFFM . liftM (first to) . gfreshen ctx . from    -- | See 'Unbound.Generics.LocallyNameless.Operations.acompare'. An alpha-respecting total order on terms involving binders.   acompare' :: AlphaCtx -> a -> a -> Ordering   default acompare' :: (Generic a, GAlpha (Rep a)) => AlphaCtx -> a -> a -> Ordering   acompare' c = (gacompare c) `on` from +-- Internal: the free monad over the Functor f.  Note that 'freshen''+-- has a monadic return type and moreover we have to thread the+-- permutation through the 'gfreshen' calls to crawl over the value+-- constructors.  Since we don't know anything about the monad @m@,+-- GHC can't help us.  But note that none of the code in the generic+-- 'gfreshen' instances actually makes use of the 'Fresh.fresh'+-- function; they just plumb the dictionary through to any 'K' nodes+-- that happen to contain a value of a type like 'Name' that does+-- actually freshen something.  So what we do is we actually make+-- gfreshen work not in the monad @m@, but in the monad @FFM m@ and+-- then use 'retractFFM' in the default 'Alpha' method to return back+-- down to @m@.  We don't really make use of the fact that 'FFM'+-- reassociates the binds of the underlying monad, but it doesn't hurt+-- anything.  Mostly what we care about is giving the inliner a chance+-- to eliminate most of the monadic plumbing.+newtype FFM f a = FFM { runFFM :: forall r . (a -> r) -> (f r -> r) -> r }++instance Functor (FFM f) where+  fmap f (FFM h) = FFM (\r j -> h (r . f) j)+  {-# INLINE fmap #-}++instance Applicative (FFM f) where+  pure = return+  (FFM h) <*> (FFM k) = FFM (\r j -> h (\f -> k (r . f) j) j)+  {-# INLINE (<*>) #-}++instance Monad (FFM f) where+  return x = FFM (\r _j -> r x)+  {-# INLINE return #-}+  (FFM h) >>= f = FFM (\r j -> h (\x -> runFFM (f x) r j) j)+  {-# INLINE (>>=) #-}++instance Fresh m => Fresh (FFM m) where+  fresh = liftFFM . fresh +  {-# INLINE fresh #-}++liftFFM :: Monad m => m a -> FFM m a+liftFFM m = FFM (\r j -> j (liftM r m))+{-# INLINE liftFFM #-}++retractFFM :: Monad m => FFM m a -> m a+retractFFM (FFM h) = h return j+  where+    j mmf = mmf >>= \mf -> mf+{-# INLINE retractFFM #-}+ -- | 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@@@ -250,7 +302,7 @@   gnamePatFind :: f a -> NamePatFind    gswaps :: AlphaCtx -> Perm AnyName -> f a -> f a-  gfreshen :: Fresh m => AlphaCtx -> f a -> m (f a, Perm AnyName)+  gfreshen :: Fresh m => AlphaCtx -> f a -> FFM m (f a, Perm AnyName)    glfreshen :: LFresh m => AlphaCtx -> f a -> (f a -> Perm AnyName -> m b) -> m b @@ -271,7 +323,8 @@   gnamePatFind = namePatFind . unK1    gswaps ctx perm = K1 . swaps' ctx perm . unK1-  gfreshen ctx = liftM (first K1) . freshen' ctx . unK1+  gfreshen ctx = liftM (first K1) . liftFFM . freshen' ctx . unK1+  {-# INLINE gfreshen #-}    glfreshen ctx (K1 c) cont = lfreshen' ctx c (cont . K1) @@ -293,6 +346,7 @@    gswaps ctx perm = M1 . gswaps ctx perm . unM1   gfreshen ctx = liftM (first M1) . gfreshen ctx . unM1+  {-# INLINE gfreshen #-}    glfreshen ctx (M1 f) cont =     glfreshen ctx f (cont . M1)@@ -315,6 +369,7 @@    gswaps _ctx _perm _ = U1   gfreshen _ctx _ = return (U1, mempty)+  {-# INLINE gfreshen #-}    glfreshen _ctx _ cont = cont U1 mempty @@ -336,6 +391,7 @@    gswaps _ctx _perm _ = undefined   gfreshen _ctx _ = return (undefined, mempty)+  {-# INLINE gfreshen #-}    glfreshen _ctx _ cont = cont undefined mempty @@ -367,9 +423,10 @@     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)+    ~(g', perm2) <- gfreshen ctx g+    ~(f', perm1) <- gfreshen ctx (gswaps ctx perm2 f)     return (f' :*: g', perm1 <> perm2)+  {-# INLINE gfreshen #-}    glfreshen ctx (f :*: g) cont =     glfreshen ctx g $ \g' perm2 ->@@ -409,6 +466,7 @@    gfreshen ctx (L1 f) = liftM (first L1) (gfreshen ctx f)   gfreshen ctx (R1 f) = liftM (first R1) (gfreshen ctx f)+  {-# INLINE gfreshen #-}      glfreshen ctx (L1 f) cont =     glfreshen ctx f (cont . L1)
src/Unbound/Generics/LocallyNameless/Bind.hs view
@@ -18,6 +18,7 @@   ) where  import Control.Applicative (Applicative(..), (<$>))+import Control.DeepSeq (NFData(..)) import Data.Monoid ((<>))  import GHC.Generics (Generic)@@ -33,6 +34,9 @@ data Bind p t = B p t               deriving (Generic) +instance (NFData p, NFData t) => NFData (Bind p t) where+  rnf (B p t) = rnf p `seq` rnf t `seq` ()+ instance (Show p, Show t) => Show (Bind p t) where   showsPrec prec (B p t) =     showParen (prec > 0) (showString "<"@@ -70,6 +74,7 @@     (p', perm1) <- freshen' (patternCtx ctx) p     (t', perm2) <- freshen' (incrLevelCtx ctx) (swaps' (incrLevelCtx ctx) perm1 t)     return (B p' t', perm1 <> perm2)+  {-# INLINE freshen' #-}    lfreshen' ctx (B p t) cont =     lfreshen' (patternCtx ctx) p $ \p' pm1 ->
src/Unbound/Generics/LocallyNameless/Embed.hs view
@@ -11,6 +11,7 @@ module Unbound.Generics.LocallyNameless.Embed where  import Control.Applicative (pure, (<$>))+import Control.DeepSeq (NFData(..)) import Data.Monoid (mempty) import Data.Profunctor (Profunctor(..)) @@ -31,7 +32,7 @@ --   (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)+newtype Embed t = Embed t deriving (Eq, Ord, Generic)  class IsEmbed e where   -- | The term type embedded in the embedding 'e'@@ -49,6 +50,9 @@   type Embedded (Embed t) = t   embedded = iso (\(Embed t) -> t) Embed   +instance NFData t => NFData (Embed t) where+  rnf (Embed t) = rnf t `seq` ()+ instance Show a => Show (Embed a) where   showsPrec _ (Embed a) = showString "{" . showsPrec 0 a . showString "}" 
src/Unbound/Generics/LocallyNameless/Name.hs view
@@ -27,6 +27,7 @@        , AnyName(..)        ) where +import Control.DeepSeq (NFData(..)) import Data.Typeable (Typeable, gcast, typeOf) import GHC.Generics (Generic) @@ -48,6 +49,10 @@ data Name a = Fn String !Integer    -- free names             | Bn !Integer !Integer  -- bound names / binding level + pattern index             deriving (Eq, Ord, Typeable, Generic)++instance NFData (Name a) where+  rnf (Fn s n) = rnf s `seq` rnf n `seq` ()+  rnf (Bn i j) = rnf i `seq` rnf j `seq` ()  -- | Returns 'True' iff the given @Name a@ is free. isFreeName :: Name a -> Bool
src/Unbound/Generics/LocallyNameless/Rebind.hs view
@@ -13,6 +13,7 @@ module Unbound.Generics.LocallyNameless.Rebind where  import Control.Applicative ((<*>), (<$>))+import Control.DeepSeq (NFData(..)) import Data.Monoid ((<>)) import GHC.Generics @@ -39,6 +40,9 @@ -- @ data Rebind p1 p2 = Rebnd p1 p2                   deriving (Generic, Eq)++instance (NFData p1, NFData p2) => NFData (Rebind p1 p2) where+  rnf (Rebnd p1 p2) = rnf p1 `seq` rnf p2 `seq` ()  instance (Show p1, Show p2) => Show (Rebind p1 p2) where   showsPrec paren (Rebnd p1 p2) =
src/Unbound/Generics/LocallyNameless/Rec.hs view
@@ -19,6 +19,7 @@        , TRec (..)        ) where +import Control.DeepSeq (NFData(..)) import GHC.Generics (Generic)  import Unbound.Generics.LocallyNameless.Alpha@@ -30,6 +31,9 @@ -- Agda's dot notation. newtype Rec p = Rec p               deriving (Generic, Eq)++instance NFData p => NFData (Rec p) where+  rnf (Rec p) = rnf p `seq` ()  instance Show a => Show (Rec a) where   showsPrec _ (Rec a) = showString "[" . showsPrec 0 a . showString "]"
src/Unbound/Generics/LocallyNameless/Shift.hs view
@@ -12,6 +12,7 @@ module Unbound.Generics.LocallyNameless.Shift where  import Control.Applicative+import Control.DeepSeq (NFData(..)) import Data.Monoid (Monoid(..))  import Unbound.Generics.LocallyNameless.Alpha (Alpha(..),@@ -33,6 +34,9 @@   type Embedded (Shift e) = Embedded e   embedded = iso (\(Shift e) -> e) Shift . embedded   +instance NFData e => NFData (Shift e) where+  rnf (Shift e) = rnf e `seq` ()+ instance Show e => Show (Shift e) where   showsPrec _ (Shift e) = showString "{" . showsPrec 0 e . showString "}" 
unbound-generics.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/  name:                unbound-generics-version:             0.1.2.1+version:             0.2 synopsis:            Support for programming with names and binders using GHC Generics description:         Specify the binding structure of your data type with an                      expressive set of type combinators, and unbound-generics@@ -53,6 +53,7 @@   -- other-extensions:       build-depends:       base >=4.6 && <5,                        template-haskell >= 2.8.0.0,+                       deepseq >= 1.3,                        mtl >= 2.1,                        transformers >= 0.3,                        transformers-compat >= 0.3,@@ -86,6 +87,19 @@   hs-source-dirs:      test   default-language:    Haskell2010   ghc-options:         -Wall++Benchmark benchmark-unbound-generics+  type:                exitcode-stdio-1.0+  default-language:    Haskell2010+  hs-source-dirs:      benchmarks+  main-is:             benchmark-main.hs+  build-depends:       base+                     , criterion+                     , deepseq >= 1.3.0.0+                     , deepseq-generics >= 0.1.1.2+                     , unbound-generics+  other-modules:       BenchLam+  ghc-options:       -Wall  source-repository head   type:                git