free-foil
Efficient Type-Safe Capture-Avoiding Substitution for Free (Scoped Monads)
https://github.com/fizruk/free-foil#readme
| LTS Haskell 24.66: | 0.2.0 |
| Stackage Nightly 2026-11-07: | 0.6.1 |
| Latest on Hackage: | 0.6.1 |
free-foil-0.6.1@sha256:f527b4e54eaf70a55fb610b2b1ca74d8846b2ee25bb68f6d236dd3a4c3144c79,10218Module documentation for 0.6.1
- Control
- Control.Monad
- Control.Monad.Foil
- Control.Monad.Free
- Control.Monad
- Data
free-foil
Efficient Type-Safe Capture-Avoiding Substitution for Free (Scoped Monads).
This package provides a scope-safe representation for syntax with binders. Scopes are tracked in a phantom type index, so that capture-avoidance is a property the type checker enforces rather than a discipline the programmer keeps. It builds on the foil of Maclaurin, Radul, and Paszke, extends it with patterns, and adds free scoped monads, so that substitution, α-equivalence, and conversion to and from a raw syntax are implemented once for every language whose syntax is given as a signature bifunctor.
Control.Monad.Foil— names, scopes, binders, and theSinkable/CoSinkableclasses.Control.Monad.Free.Foil— the free foil:AST binder sig n, substitution, α-equivalence, supports, and conversions.Control.Monad.Foil.THandControl.Monad.Free.Foil.TH— Template Haskell that generates the scope-safe syntax from a raw (BNFC-generated) one.Control.Monad.Foil.BlocksandControl.Monad.Foil.Registry— reserved ranges of names, for units that are checked independently and linked afterwards.Control.Monad.Free.Foil.BinaryandControl.Monad.Free.Foil.Artifact— serialisation of a checked unit, and the checks that loading one rests on.
See the repository README for the design and the papers behind it, and the documentation on Hackage for the modules themselves.
Changes
CHANGELOG for free-foil
0.6.1 — 2026-10-27
This release lets substitute, alphaEquiv and unsafeEqAST specialise in client code, and fixes problems found by an audit of the documentation. One of the fixes changes the types of unsinkAST and nameSetSubsetOfScope, described first.
Breaking changes:
unsinkASTrequiresDExt n linstead ofDistinct l, andnameSetSubsetOfScoperequiresExt n l: the scopelof the term (or set) must extend the scopenit is restricted to. Both compare raw identifiers, which identify a name only within one chain of scope extensions, so with unrelated scopes the answer was meaningless (see #127).- To migrate, bring
Ext n linto scope where it is missing, e.g. withassertExton the binder that leads fromntol, or fromwithFreshandwithThinnedNameBinderList, which provide it.
- To migrate, bring
Changed:
zipMatchWith1,zipMatch1,zipMatchWith2andzipMatch2no longer requireTraversable(orBitraversable).alphaEquiv,unsafeEqASTand the functions behind them no longer requireBitraversableorSinkableK, andalphaEquivRefreshedrequiresBifunctorinstead ofBitraversable. None of them used these instances (see #125).
Fixes:
alphaEquivRefreshedcompares patterns withunifyPatternsIn, asalphaEquivdoes (through the newunsafeEqASTIn). It compared them withunifyPatterns, which compares binders only, so it accepted terms whose binders differ in a payload (such as a type annotation) thatalphaEquivrejects (see #127).
Performance:
substitute,alphaEquiv,unsafeEqASTand the functions behind them areINLINEwrappers around local loops, which take the instances from the wrapper. Inlined at a known signature, the loops use its instances directly. Before, they looked the instances up at every node, and aSPECIALIZEpragma in a client did not help: GHC split each function into a wrapper and a worker, since some of its dictionaries (such asDistinct) are never used at runtime, and the pragma reached only the wrapper. On the library’s benchmarks, α-equivalence takes 0.54–0.91 of the time and normalisation 0.58–0.85 (see #125).
New:
unsafeEqASTInandunsafeEqScopedASTIncompare two terms in one scope, likeunsafeEqAST, but compare patterns withunifyPatternsIn, so the payloads of patterns are compared as well (see #127).unresolvedNamesWithisunresolvedNamesfor the arguments oftryConvertToASTWith: it also takes the table of constants, so that the constants no longer count as unresolved (see #127).Control.Monad.Free.Foil.Internalholds the implementation ofControl.Monad.Free.Foil, which now has an export list. The loops behind α-equivalence,unsafeEqASTandunsafeEqASTIn(AlphaEquivLoops,alphaEquivLoops,EqLoops,unsafeEqLoops,EqInLoops,unsafeEqInLoops) are exported only from the internal module (see #125, #127).
Documentation:
- Doc comments and README passages that the code contradicted are corrected, among them the equation given for
substituteRefreshedand thezipMatchKexample. Haddock links written through a module alias (such as'Foil.sink') resolve, where in 0.6.0 they pointed to a nonexistent page (see #126).
0.6.0 — 2026-10-26
This release lets mkFreeFoil generate most instances of a signature, and adds folds that traverse terms without rebuilding them. Several breaking changes come with it, described first.
Breaking changes:
- Scope indices have the nominal role, so
coercecan no longer change a scope (see #113, #112). This applies toName,NameBinder,Scope,NameSet,NameBinders,NameMap, the first index ofSubstitution, and the types ofControl.Monad.Foil.Blocks.- To migrate, compare names of different scopes by
nameId, and move a term to an extended scope withsink.
- To migrate, compare names of different scopes by
mkFreeFoilgenerates theBifunctor,Bifoldable,Bitraversable,ZipMatchK,ZipMatchFoldKandNFDatainstances of every signature, andNFDataof every binding type (see #116, #118, #119).- To migrate, delete these instances (or the splices deriving them), or leave them out with
mkFreeFoilWith. - Instances for the payload types (identifiers, positions) must now be declared before the
mkFreeFoilsplice.
- To migrate, delete these instances (or the splices deriving them), or leave them out with
deriveCoSinkabletakes the scope-safe pattern type, like the otherderive*functions (see #122). The old function, which takes the raw identifier and pattern types, is renamed tomkCoSinkableFoilPattern.nameBinderListOfis a method ofCoSinkable(see #110). A module that importsCoSinkable(..)and defines its ownnameBinderListOfmust hide one of them.deriveZipMatchKneeds aZipMatchFoldKinstance for every container type applied to a zipped parameter (see #119). The library provides them for lists,Maybe,Either, pairs,NonEmpty,SumandProduct. Derive others withderiveZipMatchK1.
New:
ZipMatchFoldKmatches two values one level deep, likeZipMatchK, but folds the paired components into a monoid instead of rebuilding the value (see #119).deriveZipMatchKderives it together withZipMatchK.zipMatchAllKandzipMatchAnyK(with1and2variants) check a relation on all or on any of the paired components (see #121).foldASTfolds a term bottom-up, andhoistASTchanges its signature by a natural transformation (see #115).deriveCoSinkablederivesCoSinkablefor a scope-safe pattern type (see #122).mkFreeFoilWithtakesFreeFoilOptionsto choose which instances to generate (see #116, #118).zipMatchKInstancebuilds aZipMatchKinstance from constructor fields, for Template Haskell code (see #116).gnameBinderListOflists the binders of a pattern generically (see #110).
Performance:
nameBinderListOfno longer traverses the pattern for the library’s instances and the generated ones (see #110, #123).withRefreshedPatternisINLINE, so that it specialises at a known pattern type (see #120).rnfofASTspecialises at a known signature, and is about four times faster there (see #114).- The generated signature instances are marked
INLINE, since GHC does not inline the derived methods of a large signature (see #116). - The
ZipMatchKinstances forSumandProductare written out instead of generic, and no longer requireBitraversable(orTraversable) of the components (see #109).
0.5.0 — 2026-10-07
A release about binders. The law tests added after 0.4.0 (#100) found bugs in how binders are sunk, ordered, paired and transported, and this release fixes them. The fixes change α-equivalence for patterns of several binders, so the release is major. It also generates single-binder pattern types as newtypes and removes the functions deprecated in 0.3.3 and 0.4.0.
Breaking changes:
-
The default
unifyPatternsis structural, and it requiresGenericK(#101, #98). It is the newgunifyPatterns. Two patterns unify when they consist of the same constructors, nested in the same way, and their binders are then paired in order. The old default compared only the binders, so it unified(x, _)with(_, y), and the conversion check ofmlttaccepted ill-typed programs. Non-binding fields are still ignored.- This changes α-equivalence for every client that takes the default, and reverses the plan in the 0.3.3 entry below to make structural derivation opt-in.
- A pattern type that takes the default and has no
GenericKinstance no longer compiles. DeriveGenericKfor it or writeunifyPatternsby hand. mltt,soasandImpl.FreeFoilTHoflambda-pitake the default. Forsoas, whose binders form a flat list, the result is the same as before.
-
transportPayloadtakes the ambient scope and requiresRelMonad Name einstead ofSinkable e, andCoSinkable (Telescope label e)requiresRelMonad Name etoo (#101, #99). A refreshed binder renames the payloads after it, and renaming a payload with binders needs the scope. For instance, ifx0is refreshed tox1, a later payloadλx1. x0becomesλx2. x1. A hand-writtenwithPatternpasses the scope it holds before the payload’s binder (see the recipe in the documentation oftransportPayload), and an instance carrying payloads of typeeaddsRelMonad Name eto its context.- The new
instance RelMonad Name Namekeeps telescopes of names working. RelMonadis now defined inControl.Monad.Foil.Internal.Control.Monad.Foil.Relativere-exports it, so imports need no change.telescopeParamsandtelescopePayloadskeep their constraints.
- The new
-
mkFreeFoilgenerates a pattern type as anewtypewhen its raw type has exactly one constructor with exactly one field, and that field is an identifier or a nested pattern, as innewtype Pattern = PatternVar VarIdent(#106, #85). Such a pattern costs no heap object of its own. Code that matches on the constructor or derives instances needs no change, but Template Haskell thatreifys the generated type now sees aNewtypeDand has to accept it. -
The deprecated functions are removed (#104). Use
sink1instead ofsinkContainer, andunsafeConvertToASTandunsafeConvertToScopedASTinstead ofconvertToASTandconvertToScopedAST(ortryConvertToAST, which reports unresolved identifiers instead of callingerror).deriveUnifiablePatternis removed without a replacement: deriveGenericKand take an emptyUnifiablePatterninstance, or write the instance by hand withunsafeUnifyPatternBinders, asLanguage.LambdaPi.Impl.Foildoes.
New:
unsafeUnifyPatternBinderspairs the binders of two patterns in order, for a hand-writtenunifyPatternsof a pattern with several binders. It is unsafe: call it only once the two patterns are known to agree on everything except the names of their binders, asgunifyPatternsdoes after its shape check and asLanguage.LambdaPi.Impl.Foildoes inlambda-pi(#103, #102).
Fixes:
-
The generic
sinkabilityProofno longer fails withNon-exhaustive patterns in function sinkabilityProofKon a term with a binder (#101, #94). This also fixes the defaultcoSinkabilityProofof every pattern that derivesCoSinkablegenerically, andtransportPayloadon such a payload. -
alphaEquivis correct for patterns of two or more binders (#101, #95). It used to pair the binders of the two sides wrongly, with both false negatives and false positives (λ[x0 x1]. x0andλ[x1 x2]. x2were α-equivalent). This affected the defaultunifyPatternsof every pattern type, the conversion check ofmlttandisSolutionForinsoas.NameBinderList,Telescopeand the defaultunifyPatternspair the binders of two patterns by position, all at once, and rename the binders of the right pattern to those of the left one. A single pair of binders keeps the convention ofunifyNameBinders.andThenUnifyPatternsandandThenUnifyNameBindersno longer compose renamings. A chain of verdicts that gives two binders the same name now answersNotUnifiableinstead of a wrong verdict. WithandThenUnifyNameBinders, a chain of two pairs always unifies.
-
The generic
withPattern, the default for a pattern type that derivesHasNameBinders, keeps the binders of a pattern in their order (#101, #97). It used to put them in ascending order of names, which permuted the binders of a pattern whose names do not ascend, such as one thatsubstitute,liftRMorrefreshASTproduces, and changed the meaning of the term.nameBinderListOf,addSubstPattern, the defaultunifyPatternsandalphaEquivRefreshedwere affected through it. The instances thatmkFreeFoilgenerates were not. -
In
lambda-pi,alphaEquivofImpl.Foilno longer captures a free name when a binder is already in the scope, as for a term built in a smaller scope and sunk (#101, #96). It falls back toalphaEquivRefreshedin that case.
0.4.0 — 2026-08-29
A release about units: checking a module independently of its neighbours, linking the results without renaming, and storing a checked one on disk. Scope restriction, a family of $O(1)$ sinks, and a linear α-equivalence rename path come with it.
Changed:
-
Clients of
mkFreeFoilmust delete their ownFoil.CoSinkableinstances for the generated binding types, sincemkFreeFoilnow generates a concrete one (#87, #82).SinkableK,HasNameBinders, andUnifiablePatternremain the client’s. -
withPattern’s continuation now also receives the scope the pattern extends to, and so do those ofwithRefreshedPattern,withFreshPattern, andwithRefreshedPattern'(#88, #84). Add the argument, and drop theextendScopePatternthat recomputed it. -
mkFreeFoilConversionsnow requiresBifoldableon each signature, for thetryToXit generates.deriveBifoldableif a signature lacks it. -
freeVarsOfandfreeVarsOfScopedASTno longer repeat a variable, and return names in ascending order of their identifiers rather than in the order the term mentions them. -
rawFreshNameallocates0over a scope whose maximum is negative, leaving the negative range to explicit policy, and reports a scope holdingmaxBoundas exhausted. Nothing changes on a scope without negative names. -
convertToASTandconvertToScopedASTare deprecated in favour ofunsafeConvertToASTandunsafeConvertToScopedAST, andsinkContainerin favour ofsink1. -
containers >= 0.6.8is required, forData.IntSet.fromRange.binaryandbytestringare new dependencies, both boot libraries.
New:
-
Reserved name ranges, and linking.
withFreshInallocates inside aNameRange, so units drawing from disjoint reservations cannot collide.Control.Monad.Foil.Blockstracks that asExtWithinevidence and links two units withwithDisjointUnion, one sweep over their range sets.checkExtScopeandcheckScopeUnionre-mint it for a unit loaded from a cache, and are documented as a trust boundary (#58). -
Deterministic stripes.
Control.Monad.Foil.Registrygives each unit its own run of the name line,registerUnithanding out theStripeIndexthat every reservation for that unit is derived from. ARegionLayoutdoes the same for the locals of each declaration, so an edit to one file moves no name in another (#78, #80). -
Scope restriction.
NameSet,withRestrictedScope,supportOf,withRelevantScope, andwithThinnedNameBinderList: a term’s support, the scope it really inhabits, and a chain of binders thinned against a set of names (#56). -
Serialisation.
Control.Monad.Free.Foil.Binaryhas opt-in orphanBinaryinstances for the syntax, andControl.Monad.Free.Foil.Artifactthe checks that loading a stored unit rests on, with relocation of its constants (#68, #69). Decoding mints scope evidence, so both are a trust boundary. -
Patterns carrying scope-indexed payloads.
PatternTransportis the sanctioned way to rebuild one, and the generic implementation now refuses such a pattern with a type error rather than mis-refreshing it silently (#71).Control.Monad.Foil.Telescopeis the worked example, andunifyPatternsInwith the newAlphaEquivclass lets payloads take part in α-equivalence (#72, #79). -
Conversions that report rather than crash.
tryConvertToASTgives the first unresolved identifier together with what was in scope there, in one pass, andunresolvedNamespays a second pass for all of them (#55).tryConvertToASTWithresolves an identifier to a whole term, for constants and abbreviations (#57);convertFromASTWithnames free and bound variables separately, which a raw name alone cannot (#59); andtoXInand its siblings allocate within a given range, so the same source elaborates to the same term (#60). -
A family of $O(1)$ sinks.
sink1through aFunctorandsink2through aBifunctor, with rewrite rules and hlint hints steering the elementwise forms towards them (#66). -
nameBinderListOf, theIdandRawNamesynonyms, andmapWithNameare exported. Therestrictionandalphaequivbenchmarks are new.
Performance:
-
alphaEquivthreads the verdict’s renamings down the recursion instead of applying them, which turns a quadratic rename path linear: 353 ms to 211 µs on two α-equivalent 2000-binder chains (#81). -
substituteshort-circuits the empty substitution (#80). -
The binder and substitution operations are
INLINABLE, so a downstreamSPECIALIZEreaches through them, and the free foil’sVarunpacks its name (#86, #83).
Documentation:
-
Every exported entity carries a
@since, and CI fails if a new one lacks it. -
The haddocks and the READMEs are proofread. The README’s documentation links pointed at a version of the GitHub Pages haddocks that no longer exists, and point at Hackage now.
0.3.3 — 2026-07-20
A bugfix and documentation release. Upgrading from 0.3.2 needs no work.
Fixes:
- Unifying two patterns that bind different numbers of names no longer throws
PatternMatchFail. TheUnifiablePattern NameBinderListinstance covered only the empty/empty and cons/cons cases, and since the defaultunifyPatternsflattens every pattern to aNameBinderList, this was reachable from any language with patterns of differing arity: inlambda-pi,alphaEquivonλ_.xandλy.xcrashed. Such patterns are now reported as not unifiable, so the terms are not α-equivalent. The missing case went unnoticed becauseControl.Monad.Foil.Internalsets-Wno-incomplete-patterns.
Documentation:
-
UnifiablePatternstates what its class default compares. The default flattens both patterns to their binders, so it ignores the constructor (two patterns built from different constructors with the same number of binders unify), non-binding fields, and the nesting of sub-patterns ((x, (y, z))unifies with((x, y), z)). For most languages this is the intended α-equivalence, since what a body can refer to is exactly the pattern’s binders in order, but every client gets it from an empty instance and nothing said so. The newControl.Monad.Foil.UnifiablePatternSpecpins the behaviour down. -
withRefreshedPatternandwithRefreshedPattern'explain why they have no fast path for the case when every binder is already fresh in the ambient scope. Testing all binders at once and handing the continuationsinkwould be unsound:addRename’s delete is how a binder shadows an outer binding of the same raw name, andsinkis a coercion that does not rename, so a binder can share a raw name with its own enclosing scope.addRenamenow says that its delete is not only an optimization.
Changed:
deriveUnifiablePatternis deprecated. It reifies the raw (BNFC) pattern type and synthesises the scope-safe type and constructor names by prefixing"Foil", and it errors on GADT constructors, so it cannot produce an instance for any pattern typemkFoilPatternormkFreeFoilgenerates, nor for a hand-written pattern GADT. It has no call sites and predates theGenericKroute clients use. Deprecated rather than removed, sinceControl.Monad.Foil.THre-exports the module wholesale; removal is scheduled for the next major. Structural derivation ofUnifiablePatternremains tracked in #23, and when it lands it will be opt-in rather than a new default, since changing the default would silently change α-equivalence for every existing client.
0.3.2 — 2026-07-15
An additive release: the annotation layer, the ZipMatchK derivers, and a set of performance improvements. Upgrading from 0.3.1 needs no work.
New:
-
Control.Monad.Free.Foil.Annotated, a layer that annotates every node with anann termbuilt from the node’s own term (#42, #46).Bifoldableskips the annotation, soalphaEquivis annotation-blind;freeVarsOfAnnotatedsees the variables inside annotations thatfreeVarsOfskips.AnnSig‘sZipMatchKis derived rather than generic, since on an annotated signature it sits on a typechecker’s hottest path. An annotation-blind instance must returnJustlazily, and the module documents why a strict one is wrong. -
Data.ZipMatchK.TH, Template Haskell derivers forZipMatchK(#43).deriveZipMatchKzips every type parameter,deriveZipMatchK2the last two (for a signature with an annotation parameter), withderiveZipMatchK1andderiveZipMatchKWithfor the rest. The generic instance rebuilds aGenerics.Kindview of every node on every comparison, at a cost that grows with the number of constructors. The derived instance is flat, and on a 44-constructor signature runsalphaEquiv1.8 times faster for 2.3 times less allocation. It replaces the deriver 0.3.0 dropped along with the oldZipMatchclass. -
sinkContainersinks a whole container of sinkables (anIntMaporMapof terms) in O(1) by coercion, instead offmap sinkover the spine (#44). AScopeis not sinkable and aNameMapmust stay total, both noted with the function. -
zipmatchkandnormalizebenchmarks (#43, #45):alphaEquivacross signature sizes, and full β-normalisation of untyped λ-terms. CI builds them and does not run them.
Performance:
-
alphaEquivandunsafeEqASTno longer copy the node into a tuple before folding over it. They recurse inside the zipping functions, allocating nothing and short-circuiting on the first mismatch (#44). On the benchmark, 294 µs to 217 µs and 3.1 MB to 2.8 MB with a derived instance. Behaviour is unchanged. -
substitute,alphaEquiv,refreshASTand friends are nowINLINABLE, so a call site can specialise them for its own signature instead of passing dictionaries (#44).
Changed:
-
soasandLanguage.LambdaPi.Impl.FreeFoilTHderive theirZipMatchKinstances.Language.LambdaPi.Impl.FreeFoilwrites them out by hand, so the two implementations show both ways. -
The
baselower bound is now>= 4.19(GHC 9.8). The package has required GHC 9.8 all along throughtemplate-haskell >= 2.21, so this only makes the bound honest, and lets Hackage’s documentation builder pick a compatible GHC (#15).
0.3.1 — 2026-07-14
A bug fix and a set of additions, all of them prompted by the projects built on free-foil. Nothing is removed or changed, so upgrading from 0.3.0 needs no work.
Fixes:
-
mkFreeFoilandmkFreeFoilConversionsgenerated ill-typed code for a raw constructor whose shape does not line up with the free foil node it becomes (see #38):Let ::= Pattern Term ScopedTerm -- a term between the pattern and its scope LetRec ::= Pattern ScopedTerm ScopedTerm -- one pattern binding two scopes- The pattern synonym’s arguments and its type signature were computed by two traversals that disagreed, and the conversions had the mirror problem.
- A constructor binding several scopes binds the same raw name in each of them, so raw-to-foil now sends that one binder into every scoped child, and foil-to-raw reads it back from the first.
- Constructors with at most one scoped child generate exactly what they did before.
- Reported and diagnosed by @AbsoluteNikola, who had to vendor the generated code by hand in free-foil-refinement-types.
New functions, absorbed from the projects built on free-foil, several of which were importing Control.Monad.Foil.Internal to get at them (see #40):
popNameBinder— the inverse ofaddNameBinder, for leaving a binder.withFreshNameBinderList— a fresh binder for each element of a list, bound to it in aNameMap.snocNameBinderListandconcatNameBinderLists.nameBindersList,fromNameBindersListandnameMapToScopeare now exported (they existed, but were unreachable).- With thanks to @Probirochniy, @fedor-ivn and @snejugal (free-foil-hou), and to @evermake, @frog-da and @Vikono (free-foil-typecheck), whose copies of these helpers carried the comment “Should be in
Control.Monad.Foil”.
Documentation:
unifyNameBindersrenames the binder with the larger name towards the one with the smaller name. That is safe, since the renaming is pushed through a term withliftRM, which refreshes a binder whenever it would capture. The haddock now says so, with a test to match (see #39). Raised by @AbsoluteNikola.
0.3.0 — 2026-07-14
This release makes generic deriving the default way to instantiate the library:
Sinkable, CoSinkable, UnifiablePattern and ZipMatchK can now all be
derived via kind-generics,
so a user-defined pattern normally needs no hand-written instances at all.
The cost is one breaking change, described first.
Breaking changes
-
The
ZipMatchclass is removed in favour of the kind-polymorphicZipMatchK, and the modules are reorganised (see #30):Control.Monad.Free.Foil.Genericis replaced byData.ZipMatchK(withData.ZipMatchK.Bifunctor,Data.ZipMatchK.Functor,Data.ZipMatchK.GenericandData.ZipMatchK.Mappings);Control.Monad.Free.Foil.TH.ZipMatch(and itsderiveZipMatch) is removed, andControl.Monad.Free.Foil.THno longer re-exports it.
To migrate, delete the
ZipMatchinstances and anyderiveZipMatchsplices, and keep only theZipMatchKones. Where you hadimport Control.Monad.Free.Foil.Generic instance ZipMatchK a => ZipMatchK (TermSig a) instance ZipMatchK a => ZipMatch (TermSig a) where zipMatch = genericZipMatch2the second instance simply goes away:
import Data.ZipMatchK instance ZipMatchK a => ZipMatchK (TermSig a)ZipMatchKis derived generically, so the signature needs aGenericKinstance (deriveGenericK ''TermSigfromkind-generics-th). Note thatkind-generics-this not on Stackage, and is not a dependency of this library: a package usingderiveGenericKmust depend on it itself. -
α-equivalence and refreshing now ask for more of the binder:
alphaEquiv,alphaEquivRefreshed,refreshASTandrefreshScopedASTrequireSinkableK binder(andZipMatchK sigin place ofZipMatch sig). For binders and patterns generated by our Template Haskell, or derived generically, this constraint is already satisfied and no change is needed.
Generic deriving of patterns (see #31)
- New
SinkableKclass, generalising bothSinkableandCoSinkable: a typef n₁ n₂ … nₖis treated as a generalised binder with variables and terms in scopesn₁, n₂, …, nₖ. Generic (kind-polymorphic) implementations are provided forsinkabilityProofandcoSinkabilityProof, soSinkableandCoSinkableinstances can be left empty. - New
HasNameBindersclass, generalising access to the nestedNameBinders of a pattern. This is what makes a genericwithPatternpossible, so user-defined patterns no longer need a hand-written traversal. UnifiablePatternnow has a default implementation viaCoSinkable, soinstance UnifiablePattern MyPatternnormally suffices.- Malformed user-defined patterns are now rejected with a readable type error
by a separate generic check (
GValidNameBinders), instead of failing obscurely deeper in the machinery. Terms (and other types) are allowed in patterns, as long as the binders are threaded correctly.
New functions (see #32)
unsinkAST— unsink anASTfrom a larger scope into a smaller one, when possible.freeVarsOfandfreeVarsOfScopedAST— collect the free variables of anAST.nameMapToScope— recover aScopefrom aNameMap.NameMapis now aFunctor,FoldableandTraversable.
Fixes
GValidNameBindersno longer rejects a valid constructor that equates its scopes and carries more than one field (substitution now recurses through sums and products).
0.2.0 — 2024-10-27
- Generate
COMPLETEpragma inmkPatternSynonyms(see #26) - Polykind
ZipMatchKclass with default generic implementation viakind-generics(see #27) - New experimental TH generation for Free Foil with support for rich syntax in
Control.Monad.Free.Foil.TH.MkFreeFoil(see #28)
0.1.0 — 2024-08-18
-
Generalize functions for binders, support general patterns (see #16)
-
Add
withPatternmethod toCoSinkable. It can be seen as a CPS-style traversal over binders in a pattern. Our Template Haskell support covers generation ofwithPattern, so normally the user does not have to think about it. -
Generalize many functions to work with arbitrary patterns, not just
NameBinder:withFreshPattern— towithRefreshedPatternandwithRefreshedPattern'extendScopePattern— extend a given scope with all binders in a given patternnamesOfPattern— collect all names from a patternunsinkNamePattern— try to unsink names from a scope extended with binders from a given patternassertDistinctPattern— establish that extended scope is distinct (if outer scope is)assertDistinctExt— establish that extended scope is distinct and indeed an extension
-
Implement unification for patterns in
unifyPatterns. This turns out to be one of the most difficult places, especially for compound patterns. Implementing patterns properly on the user side not comfortable at all! Luckily, we provide useful helpers likeandThenUnifyPatternsandandThenUnifyNameBinders, as well as Template Haskell support to deriveUnifiablePattern. -
Generalize Free Foil to support arbitrary patterns.
-
The
FoilandFreeFoilTHimplementations now make use of the generalized pattern support.
-
0.0.3 — 2024-06-20
-
Add α-equivalence checks and α-normalization (see #12):
Control.Monad.Foilnow offers more helpers to work with binder renaming and binder unification. These helpers can be used to implement (efficient) α-equivalence.- In
Control.Monad.Free.Foilgeneral implementation of α-equivalence and α-normalization is provided.
-
Add general conversion functions for free foil (see #14):
Control.Monad.Free.Foilnow offersconvertToASTandconvertFromASTfunctions enabling easier implementation of conversions raw and scope-safe representations.
-
Add Template Haskell functions for free foil (see #14):
Control.Monad.Free.Foil.THcontains many useful functions to generate free foil from a raw representation (e.g. generated via BNFC), including generation of the signature, convenient pattern synonyms,ZipMatchinstance, and conversion helpers.
0.0.2 — 2024-06-18
- Improve TH to support parametrized data types (see #11)
- Split
lambda-piinto its own package (see #10) - Switch to
template-haskell >= 2.21.0.0(to support latest Stackage Nightly) - Fix doctests (see #9)
0.0.1 — 2024-06-08
First release, corresponding to the ICCQ 2024 paper.