ekmett / bound Goto Github PK
View Code? Open in Web Editor NEWCombinators for manipulating locally-nameless generalized de Bruijn terms
Home Page: https://www.schoolofhaskell.com/user/edwardk/bound
License: Other
Combinators for manipulating locally-nameless generalized de Bruijn terms
Home Page: https://www.schoolofhaskell.com/user/edwardk/bound
License: Other
This is due missing Eq1
instance. I'd like to fix this by using https://github.com/phadej/functor-classes-compat, but I'm waiting whether Johan Tibell will release unordered-containers
in near future.
So the fix is on the way, just few bumps in the road.
I think I figured out how Bound.Name
works, but am unsure which parts of the AST should be decorated with it. The other functions in the module suggest to decorate the first parameter of Scope
, but it's weird not to have access to the name when matching on Lam
.
Could you drop a really short example? Just the modified basic Exp
type + the Lam
case of a pretty printing function like here which figures out the right name for the bound variable should be enough.
I saw that this repo has had its version requirements for transformers
loosened, but not on hackage. Would you mind releasing a new version with the change?
Consider the list monad:
w :: Scope b [] Integer
w = Scope [F [1,2,3]]
On the other hand,
w >>= return
is
Scope [F [1],F [2],F [3]]
I'm not sure how big of a problem this is.
Let's assume that transformers
will start to use QuantifiedConstraints tomorrow
class (forall m. Monad m => Monad (t m)) => MonadTrans t where ...
Then we would be screwed. Should we take the bullet already today?
Does this make sense
instance Bound Free where
(>>>=) :: forall m a c. Monad m => Free m a -> (a -> m c) -> Free m c
Pure a >>>= ĸ = liftF (ĸ a)
Free ms >>>= ĸ = liftF (ms >>= retract >>= ĸ)
It looks weird and I don't know if it's useful but it type checks :) I am too undisciplined to dive into it I'm afraid
Despite having a template-haskell
cabal
flag seemingly for the purpose of allowing users to opt out of the template-haskell
library dependency:
Lines 107 to 108 in 6d2ca74
It turns out that bound
also depends on the template-haskell
library unconditionally elsewhere in the file, defeating the point of having the cabal
flag in the first place:
Line 95 in 6d2ca74
We should either:
template-haskell
library conditionally, ortemplate-haskell
cabal
flag altogether.The problem is, GHC can't figure out how to fmap
the first a
out of Scope (Name a ()) Program a
.
The >>>=
operator will leave the Name a ()
intact as well.
I can make it Name MyName ()
, but this kinda defeats the purpose of the Name
wrapper.
If the Scope' (bound :: *) (f :: * -> * -> *) (name :: *) (a :: *)
is a Bifunctor
instead, thus preserving the names by default, this would allow us to keep the oldest names, erase them with lmap (const ())
or do any possible mapping separately from the current name set.
The Monad
instance in that case would allow to only work with "latest" names.
λ > data E a = V a | App (E a) (E a) | Lam (Scope () E a) | ND [E a] deriving (Functor)
> makeBound ''E; deriveEq1 ''E;
<interactive>:1:1: error:
Exception when trying to run compile-time code:
This is bad: AppT ListT (AppT (ConT Ghci7.E) (VarT a_1627463734)) False
CallStack (from HasCallStack):
error, called at src/Bound/TH.hs:256:39 in bound-2-6f91PaWmBSxGss7Mmo1eYD:Bound.TH
Code: makeBound ''E
is there something I'm doing wrong, Template Haskell error message doesn't help here...
The version of bound
on Hackage, 1.0.7
, supports only transformers<0.5
. This is causing me trouble when installing bound
with a fresh install of the latest Haskell Platform, which comes with transformers-0.5.2.0
.
I see from the Cabal file in this repo that the dependency has already been updated - could you please upload the latest version to Hackage?
Thanks!
The first thing the user reads after clicking on this library is
We represent the target language itself as an ideal monad supplied by the user
I find this a bit confusing because "ideal monad" doesn't seem to be a widely known concept in Haskell (unlike, say, "free monad"). The references I have found give more general category-theoretic explanations and are a bit hard to grasp. In Haskell terms, it seems that ideal monads have a "pure" constructor, but I'm not sure if there are other conditions.
If the conditions are easy to state, perhaps they should be used instead of the term "ideal monad".
The following makeBound
invocation chokes:
{-# LANGUAGE DeriveFunctor, TemplateHaskell #-}
import Bound
import Bound.Name
import Data.Text
type Named b = Name Text b
data Term v
= EVar v
| EApp (Term v) (Term v)
| EAbs (Named ()) (Scope (Named ()) Term v)
deriving (Functor)
makeBound ''Term
boundbug.hs:1:1: error:
Exception when trying to run compile-time code:
This is bad: AppT (ConT Main.Named) (TupleT 0) False
CallStack (from HasCallStack):
error, called at src/Bound/TH.hs:269:35 in bound-2.0.1-13f2bf83f03b2fd0ec7401893dd97861fab71a8bfa55e6a9cf9d765f1a520a4f:Bound.TH
Code: makeBound ''Term
But the following works fine:
data MyUnit = MyUnit deriving (Eq)
data Term v
= EVar v
| EApp (Term v) (Term v)
| EAbs (Named MyUnit) (Scope (Named ()) Term v)
deriving (Functor)
makeBound ''Term
The problem is that makeBound
doesn't recognize ()
as a constant because it parses as an empty tuple, and isKonst
is missing a rule for tuples. The other, remaining occurrence of Named ()
is okay because it gets handled by boundInstance
rather than isKonst
.
I'm thinking about implementing the System DC language (from this paper, essentially "dependent GHC Core", to save you a click) using Bound.
The problem is that it has mutually recursive types for terms and coercions, like so:
data Tm a
= TmVar a | TmConv (Tm a) (Co a)
| TmPi (Tm a) (Scope () Tm a) | TmCoAbs (Tm a) (Scope () Co a) | ...
data Co a = CoVar a | CoRefl (Tm a) | CoSym (Co a) | CoPiFst (Scope () Co a) ...
so I don't think Monad instances are happening for either.
Some points:
Co
nodes appear in the Tm
type, and vice versa, as notedScope b Co a
appears in both typesScope b Tm a
only appears in the first (as far as I can tell)How would I modify these types to support something akin to @gelisam's "sideways" imperative example?
The semantics I want are captured by the simple-minded types I wrote above; unlike in the Imperative
example, I have no need for bound variables being unavailable in certain parts of the ASTs or anything of the sort.
Edit: also see this comment below, it appears I need two kinds of binders
We see Ambiguous occurrence ‘foldMap’
on stackage nightly (GHC 8.10.3).
(Though I don't yet understand why it manifested yesterday, Thursday, 2021-02-04.)
bound
depends on profunctors
to allow it to offer a few trivial bits of lensy glue. Would it be possible to get a bound-core
package or similar that just doesn't bother? Even playing around with bound
is annoying because of the dependency.
Using Bound with Lens breaks Control.Lens.Plated
as traverse
and rewrite
loop infinitely and leak memory. Removing the Lam
alternative (and thereby also the Scope
resolves the issue. Attached is a (somewhat) minimal working example, profiterole
and heap profiling results.
I am using GHC version 8.0.2, all packages with exception of bound-2
are part of stack lts-8.17
. The program is compiled as ghc test.hs
. Enabling or disabling optimizations doesn't affect the problem.
(Potentially) see this SO question for more information/discussion.
This issue might be in the wrong project, feel free to ping me and I'll sumbit it to lens
instead.
{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable, DeriveDataTypeable #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
module Main (main) where
import Bound
import Control.Lens hiding (List)
import Control.Lens.Plated
import Data.Deriving (deriveShow1, deriveEq1)
import Data.Data
import Data.Data.Lens (uniplate)
data Expr a = Var a
| List [Expr a]
| Apply (Expr a) (Expr a)
| Lam (Scope () Expr a)
| Nop
deriving (Functor, Foldable, Traversable, Data)
main = do
print ex
print $ transform removeTest ex
ex :: Expr String
ex = List [ Apply (Var "test") $ List [Var "arg1", Var "arg2"]
, Apply (Var "two") (Var "three")]
removeTest :: Expr String -> Expr String
removeTest = \expr -> case expr of
Apply (Var "test") _ -> Nop
_ -> expr
instance Data a => Plated (Expr a) where
plate = uniplate
makeLenses ''Expr
makeBound ''Expr
deriveEq1 ''Expr
deriveShow1 ''Expr
deriving instance Show a => Show (Expr a)
deriving instance Eq a => Eq (Expr a)
Output:
List [Apply (Var "test") (List [Var "arg1",Var "arg2"])
,Apply (Var "two") (Var "three")]
^CList [Nop,Apply (Var "two") (Var "three")]
-- profiterole stats
TOT INH IND
99.9 99.9 .1 MAIN MAIN (0)
99.9 99.9 .1 MAIN MAIN (0)
99.9 99.9 34.0 Main CAF (0)
65.9 65.9 - Data.Data.Lens fromOracle (35)
65.9 65.9 - Data.Data.Lens hitTest (0)
65.9 65.9 - Data.Data.Lens hitTest.\ (35)
65.9 65.9 - Data.Data.Lens readCacheFollower (30)
65.9 65.9 - Data.Data.Lens readCacheFollower.\ (30)
65.9 65.9 - Data.Data.Lens insertHitMap (1)
65.9 65.9 - Data.Data.Lens insertHitMap.populate (1)
65.9 65.9 56.4 Data.Data.Lens insertHitMap.populate.f (1824891)
2.3 2.3 2.3 Data.HashMap.Base clone16 (2682671)
2.1 2.1 2.1 Data.HashMap.Base hash (6204631)
1.7 1.7 .4 Data.HashMap.Array copy (1620382)
1.3 1.3 1.3 Data.HashMap.Array copy.\ (1620382)
1.1 1.1 1.1 Data.Data.Lens insertHitMap.populate.fs (2189870)
.9 .9 .9 Bound.Var gfoldl (364976)
.5 .5 .5 Data.HashMap.Array new_ (810191)
.5 .5 .5 Bound.Var gunfold (364976)
.3 .3 .3 Data.HashMap.Base sparseIndex (3620621)
.2 .2 .2 Bound.Scope gfoldl (182489)
Heap profile:
It states that it maps over both free and bound variables although it only modifies the bound ones.
The bot is b0rked.
substitute :: Monad f => Eq a => a -> f a -> f a -> f a
substitute = substituteOf . (==)
substituteBy :: Monad m => (a -> Bool) -> m a -> m a -> m a
substituteBy old new as = do
a <- as
if old a
then new
else pure a
substituteVar :: Functor f => Eq a => a -> a -> f a -> f a
substituteVar = substituteVarOf . (==)
substituteVarBy :: Functor f => (a -> Bool) -> a -> f a -> f a
substituteVarBy old new as = do
a <- as
pure
if old a
then new
else a
Is there a use for this?
Is it possible to use bound
with recursion-schemes
library?
i.e. to define the names in an AST using bound
and doing AST transformations (optimisations, inlining, etc) with recursion-schemes
abstractE :: Monad f => (a -> Either a' b) -> f a -> Scope b f a'
abstractE f e = Scope (liftM k e) where
k y = case f y of
Right z -> B z
Left q -> F (return q)
The idea is that the free variable type can change to reflect the fact that some of its values are gone. I don't know if this is practically important, but a nice side effect is that the type of abstractE
expresses/constrains what it does much more thoroughly than the type of abstract
does.
The following example works fine for me with ghc7.10.3, but not with ghc8. It is complaining
src/Exp.hs:56:22: error:
• No instance for (Data.Functor.Classes.Show1 (Exp t))
arising from the second field of ‘Lam’ (type ‘Scope () (Exp t) a’)
Possible fix:
use a standalone 'deriving instance' declaration,
so you can specify the instance context yourself
• When deriving the instance for (Show (Exp t a))
and similar for Eq1
, Ord1
, Read1
Sorry, if I am asking the obvious, but I would appreciate some help of how to
derive Eq1
, Ord1
, Show1
, Read1
for (Exp t)
Note that the use of two type variables: t
, a
makes my expressions: Exp t a
a little more
complicated than most of the Bound examples I usually see (with just Exp a
), an idea I got by
cheating at Term
s in the ermine code originally:
https://github.com/ermine-language/ermine/blob/master/src/Ermine/Syntax/Term.hs
but they have worked fine for me so far (and I have come to rely on them).
Not sure if I have a chance to apply a simple/automatic solution like
import Data.Eq.Deriving (deriveEq1)
import Text.Show.Deriving (deriveShow1)
from deriving-compat
relying on Template Haskell, as in the example at
https://github.com/ekmett/bound
at all, given my two expressions of t
and a
, but I would be happy already
if I just got my code compiled with ghc8
I have tried deriving instance Show t => Show1 (Exp t)
as well, with no luck so far.
Im am using the latest version of Bound
from github (not the slighly older version on hackage),
cf. https://github.com/ekmett/bound/issues/32
, ie. Bound
itself compiles fine for me
under ghc8 from debian sid/unstable.
Thanks in advance,
Andreas
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE CPP #-}
module Exp where
import Control.Monad
import Prelude.Extras
import Bound
infixl 9 :@
data Exp t a =
V a
| (Exp t a) :@ (Exp t a)
| Lam t (Scope () (Exp t) a)
#if MIN_VERSION_GLASGOW_HASKELL(8,0,1,0)
-- what to do here for ghc8 ?
-- these lines just copied, but don't work
deriving (Eq, Ord, Show, Read, Functor, Foldable, Traversable)
instance Eq t => Eq1 (Exp t)
instance (Ord t) => Ord1 (Exp t)
instance Show t => Show1 (Exp t)
instance Read t => Read1 (Exp t)
#else
-- works fine in ghc 7.10.3
deriving (Eq, Ord, Show, Read, Functor, Foldable, Traversable)
instance Eq t => Eq1 (Exp t)
instance (Ord t) => Ord1 (Exp t)
instance Show t => Show1 (Exp t)
instance Read t => Read1 (Exp t)
#endif
instance Applicative (Exp t) where
pure = V
(<*>) = ap
instance Monad (Exp t) where
return = V
V a >>= f = f a
(x :@ y) >>= f = (x >>= f) :@ (y >>= f)
Lam n s >>= f = Lam n (s >>>= f)
-- |
-- >>> lam "a" $ V "a" :@ V "b"
-- Lam "a" (Scope (V (B ()) :@ V (F (V "b"))))
lam v b = Lam v (abstract1 v b)
Scope
can be an instance of MFunctor
from mmorph:
instance MFunctor (Scope b) where
hoist = hoistScope
I'm not sure how you feel about incurring another dependency, but it might be worth considering.
In particular, a file containing:
{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable, RankNTypes, ScopedTypeVariables #-}
module Lib where
import Bound
data Prog operand a
= Ret (operand a)
| Add (operand a) (operand a)
(Prog (Scope () operand) a)
deriving (Eq,Ord,Show,Read,Functor,Foldable,Traversable)
pAbstract1 :: forall operand a. (Applicative operand, Monad operand, Eq a)
=> a
-> Prog operand a
-> Prog (Scope () operand) a
pAbstract1 = go abstract1
where
go :: forall o o' u. Eq u
=> (forall v. Eq v => v -> o v -> o' v)
-> u -> Prog o u -> Prog o' u
go f x (Ret o) = Ret (f x o)
go f x (Add o1 o2 cc) = Add (f x o1) (f x o2)
$ go f' x cc
where
f' :: forall v. Eq v => v -> Scope () o v -> Scope () o' v
f' v = Scope . f (F v) . unscope
gives the following error:
• Occurs check: cannot construct the infinite type: v ~ o v
Expected type: Scope () o v -> o (Var () v)
Actual type: Scope () o v -> o (Var () (o v))
• In the second argument of ‘(.)’, namely ‘unscope’
In the second argument of ‘(.)’, namely ‘f (F v) . unscope’
In the expression: Scope . f (F v) . unscope
|
44 | f' v = Scope . f (F v) . unscope
|
despite being in the examples/
folder of this repo.
Hey, I've been using bound
for quite a while for a personal project, and it satisfies almost all of my needs, except for one issue.
The family of abstractName
allows remembering names of binder occurrences after abstraction as a forgettable property, and that's pretty neat. But when a binder does not occur in a term, abtracting over it forgets it entirely.
I have been wondering whether it would be feasible / worth it to remember this as a forgettable property stored in the Scope
itself, rather than in B
, so that one could re-instantiate the scoped variables with the same name.
I'm not sure whether this is clearly explained. In my use case, I have some dependent types like:
∀ (A : Type) (v : A) , Type
And, when representing them using bound, the binder v
gets completely forgotten (which makes sense, since it has no occurrence). But for debugging purposes, it'd be nice to be able to have the Scope
remember it.
Let me know what people think about this. Is this unwanted/unfeasible for some reason I'm not thinking about?
[1 of 2] Compiling Main ( /tmp/stack11223/bound-2/Setup.lhs,
/tmp/stack11223/bound-2/.stack-work/dist/x86_64-linux-ncurses6/Cabal-
1.24.2.0/setup/Main.o )
/tmp/stack11223/bound-2/Setup.lhs:24:1: error:
Failed to load interface for ‘Warning’
Use -v to see a list of the files searched for.
I checked the source tarball and it seems like Warning.hs
is missing entirely.
Edward mentioned to tag you @phadej to fix it.
Sorry to post this as a GitHub issue, this might not be the best venue for such questions.
I used to have a type like:
data Term v
= Constructor (Scope N Term v)
which easily satisfied all the good things we could care about (like Monad).
Now, I would like to have a pair of two terms sharing the exact same scope. My initial approach is the following:
data Paired f v = Paired (f v, f v)
data Term v
= Constructor (Scope N (Paired Term) v)
Unfortunately, the Paired
type is not a Monad
, because it makes no sense to substitute variables for pairs of terms. It is, however, an instance of Bound
, as it supports Paired f a -> (a -> f c) -> Paired f c
. I cannot figure out how to write the Monad
instance for Term
though.
The easy way out is to instead define:
data Term v
= Constructor (Scope N f v) (Scope N f v)
and manually enforce that the scopes are the same.
But I wonder whether the former approach is doomed, or whether I'm just missing something.
Do all the Scope
methods that have a requirement Monad f
really need this requirement, or is it required because it is expected that f
would be a Monad
?
ListT
from transformers
isn't really a monad transformer, so it seems most unlikely to be a valid Bound
instance with the default definition.
I tried using bound
for extracting equality constraint on meta variables for type reconstruction in a lambda calculus:
data Type b
= MetaVar b
| Arrow (Type b) (Type b)
| TyNat
data ConstraintScheme b
= Poly (Scope () f b) -- What's f?
| Mono (Constraints b)
newtype Constraints b
= Constraints [(Type b, Type b)]
Note that where I mentioned Scope
, I want it to unwrap to ConstraintScheme (Var () (Type b))
. I think that would only be possible by separating the two occurences of f
in Scope
, e.g.
newtype Scope g b f a = Scope { unscope :: g (Var b (f a)) }
Is there another way to use bound
for tracking unification variables?
kinda hard to test out stuff using on ghc 8.0 without it,
i'll do some testing as i can
In the example I recently contributed, I made two mistakes which happen to cancel each other out: pInstantiate1
and pAbstract1
both recur over the wrong variable, and as a result bound variables behave like De Bruijn indices instead of De Bruijn levels.
Despite this, the example works fine, because it only manipulates variables via those two functions and they both use the same numbering convention. Since the fix is a bit hairy, maybe it would be better to keep the example as is?
Details after the cut.
The easiest way to see why this is a mistake is to generalize the signature of pInstantiate
so that it accepts a Scope b operand
instead of just Scope () operand
, just like the original instantiate1
does. The given Prog (Scope b operand) a
contains a bunch of Scope b operand a
sub-terms, which in turn contain variables of type Var b a
, and pInstantiate
is supposed to instantiate all the variables of type b
with the term x
("instantiate b
with x
" for short).
pInstantiate1 :: Monad operand
=> operand a
-> Prog (Scope b operand) a
-> Prog operand a
pInstantiate1 x (Add ... cc) = Add ... (pInstantiate1 (lift x) cc)
Since cc
has type Prog (Scope () (Scope b operand)) a
, the recursive call instantiates ()
with x
instead of instantiating b
with x
. Oops!
To recur on b
instead, I'd need a more general type accepting a Scope b
regardless of the number of nested Scope ()
wrappers under which it is hidden:
pInstantiate1 :: Monad operand
=> operand a
-> Prog (Scope () (Scope () ... (Scope b operand))) a
-> Prog (Scope () (Scope () ... operand)) a
pInstantiate1 x (Add ... cc) = Add ... (pInstantiate1 x cc)
I can implement this and pAbstract1
using about 50 lines of type-level machinery, but doing so makes the example a lot less appealing.
To keep the types simple, I can temporarily hide the Scope ()
within the a
, leaving only the Scope b
on which we can safely recur, after which I'd recreate the Scope ()
.
pInstantiate1 :: forall operand a. (Monad operand, Eq a)
=> operand a
-> Prog (Scope () operand) a
-> Prog operand a
pInstantiate1 x (Ret o) = Ret (instantiate1 x o)
pInstantiate1 x (Add o1 o2 cc) = Add (instantiate1 x o1)
(instantiate1 x o2)
cc'
where
cc' :: Prog (Scope () operand) a
cc' = fmap fromJust
$ pAbstract1 Nothing
$ pInstantiate1 (fmap Just x)
$ pInstantiate1 (pure Nothing)
$ fmap Just cc
This works, but since pAbstract1
is implemented the same way, the complexity for n nested binders is O(3^n), quite steep!
So, what should we do? Keep the morally-wrong-but-working solution, implement the correct-but-complicated solution, or implement the correct-but-slow solution? Or maybe there's a better solution I'm not seeing?
traverseBound docs say it works on both bound and free variables, but it clearly is only for bound variables, unlike traverseScope, which does both
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.