packages feed

hegg 0.1.0.0 → 0.2.0.0

raw patch · 23 files changed

+678/−325 lines, 23 filesdep +tasty-benchdep ~basedep ~containersdep ~deriving-compatPVP ok

version bump matches the API change (PVP)

Dependencies added: tasty-bench

Dependency ranges changed: base, containers, deriving-compat, hegg, tasty, tasty-hunit, tasty-quickcheck

API changes (from Hackage documentation)

- Data.Equality.Extraction: instance GHC.Classes.Eq (Data.Equality.Extraction.CostWithExpr lang)
- Data.Equality.Extraction: instance GHC.Classes.Ord (Data.Equality.Extraction.CostWithExpr lang)
- Data.Equality.Extraction: type Cost = Int
- Data.Equality.Graph: EGraph :: !ReprUnionFind -> !ClassIdMap (EClass l) -> !Memo l -> !Worklist l -> !Worklist l -> EGraph l
- Data.Equality.Graph: [analysisWorklist] :: EGraph l -> !Worklist l
- Data.Equality.Graph: [classes] :: EGraph l -> !ClassIdMap (EClass l)
- Data.Equality.Graph: [memo] :: EGraph l -> !Memo l
- Data.Equality.Graph: [unionFind] :: EGraph l -> !ReprUnionFind
- Data.Equality.Graph: [worklist] :: EGraph l -> !Worklist l
- Data.Equality.Graph: instance (GHC.Show.Show (Data.Equality.Analysis.Domain l), Data.Functor.Classes.Show1 l) => GHC.Show.Show (Data.Equality.Graph.EGraph l)
- Data.Equality.Graph: type Memo l = NodeMap l ClassId
- Data.Equality.Graph: type Worklist l = NodeMap l ClassId
- Data.Equality.Graph.Nodes: [sizeNodeMap] :: NodeMap (l :: Type -> Type) a -> {-# UNPACK #-} !Int
- Data.Equality.Graph.Nodes: data NodeMap (l :: Type -> Type) a
- Data.Equality.Graph.Nodes: instance (Data.Functor.Classes.Eq1 l, Data.Functor.Classes.Ord1 l) => GHC.Base.Monoid (Data.Equality.Graph.Nodes.NodeMap l a)
- Data.Equality.Graph.Nodes: instance (Data.Functor.Classes.Eq1 l, Data.Functor.Classes.Ord1 l) => GHC.Base.Semigroup (Data.Equality.Graph.Nodes.NodeMap l a)
+ Data.Equality.Extraction: instance GHC.Classes.Eq a => GHC.Classes.Eq (Data.Equality.Extraction.CostWithExpr lang a)
+ Data.Equality.Extraction: instance GHC.Classes.Ord a => GHC.Classes.Ord (Data.Equality.Extraction.CostWithExpr lang a)
+ Data.Equality.Graph.Nodes: instance Data.Functor.Classes.Ord1 l => GHC.Base.Monoid (Data.Equality.Graph.Nodes.NodeMap l a)
+ Data.Equality.Graph.Nodes: instance Data.Functor.Classes.Ord1 l => GHC.Base.Semigroup (Data.Equality.Graph.Nodes.NodeMap l a)
+ Data.Equality.Graph.Nodes: newtype NodeMap (l :: Type -> Type) a
+ Data.Equality.Saturation: runEqualitySaturation :: forall l schd. (Language l, Scheduler schd) => Proxy schd -> [Rewrite l] -> EGraphM l ()
+ Data.Equality.Utils.SizedList: (|:) :: a -> SList a -> SList a
+ Data.Equality.Utils.SizedList: SList :: ![a] -> {-# UNPACK #-} !Int -> SList a
+ Data.Equality.Utils.SizedList: data SList a
+ Data.Equality.Utils.SizedList: instance Data.Foldable.Foldable Data.Equality.Utils.SizedList.SList
+ Data.Equality.Utils.SizedList: instance Data.Traversable.Traversable Data.Equality.Utils.SizedList.SList
+ Data.Equality.Utils.SizedList: instance GHC.Base.Functor Data.Equality.Utils.SizedList.SList
+ Data.Equality.Utils.SizedList: instance GHC.Base.Monoid (Data.Equality.Utils.SizedList.SList a)
+ Data.Equality.Utils.SizedList: instance GHC.Base.Semigroup (Data.Equality.Utils.SizedList.SList a)
+ Data.Equality.Utils.SizedList: instance GHC.Exts.IsList (Data.Equality.Utils.SizedList.SList a)
+ Data.Equality.Utils.SizedList: sizeSL :: SList a -> Int
+ Data.Equality.Utils.SizedList: toListSL :: SList a -> [a]
- Data.Equality.Analysis: class Eq (Domain l) => Analysis l where {
+ Data.Equality.Analysis: class Eq (Domain l) => Analysis (l :: Type -> Type) where {
- Data.Equality.Extraction: depthCost :: Language l => CostFunction l
+ Data.Equality.Extraction: depthCost :: Language l => CostFunction l Int
- Data.Equality.Extraction: extractBest :: forall lang. Language lang => EGraph lang -> CostFunction lang -> ClassId -> Fix lang
+ Data.Equality.Extraction: extractBest :: forall lang cost. (Language lang, Ord cost) => EGraph lang -> CostFunction lang cost -> ClassId -> Fix lang
- Data.Equality.Extraction: type CostFunction l = l Cost -> Cost
+ Data.Equality.Extraction: type CostFunction l cost = l cost -> cost
- Data.Equality.Graph.Classes: EClass :: {-# UNPACK #-} !ClassId -> !Set (ENode l) -> Domain l -> !NodeMap l ClassId -> EClass l
+ Data.Equality.Graph.Classes: EClass :: {-# UNPACK #-} !ClassId -> !Set (ENode l) -> Domain l -> !SList (ClassId, ENode l) -> EClass l
- Data.Equality.Graph.Classes: [eClassParents] :: EClass l -> !NodeMap l ClassId
+ Data.Equality.Graph.Classes: [eClassParents] :: EClass l -> !SList (ClassId, ENode l)
- Data.Equality.Graph.Lens: _memo :: Lens' (EGraph l) (Memo l)
+ Data.Equality.Graph.Lens: _memo :: Lens' (EGraph l) (NodeMap l ClassId)
- Data.Equality.Graph.Lens: _parents :: Lens' (EClass l) (NodeMap l ClassId)
+ Data.Equality.Graph.Lens: _parents :: Lens' (EClass l) (SList (ClassId, ENode l))
- Data.Equality.Graph.Nodes: NodeMap :: !Map (ENode l) a -> {-# UNPACK #-} !Int -> NodeMap (l :: Type -> Type) a
+ Data.Equality.Graph.Nodes: NodeMap :: Map (ENode l) a -> NodeMap (l :: Type -> Type) a
- Data.Equality.Graph.Nodes: [unNodeMap] :: NodeMap (l :: Type -> Type) a -> !Map (ENode l) a
+ Data.Equality.Graph.Nodes: [unNodeMap] :: NodeMap (l :: Type -> Type) a -> Map (ENode l) a
- Data.Equality.Saturation: equalitySaturation :: forall l. Language l => Fix l -> [Rewrite l] -> CostFunction l -> (Fix l, EGraph l)
+ Data.Equality.Saturation: equalitySaturation :: forall l cost. (Language l, Ord cost) => Fix l -> [Rewrite l] -> CostFunction l cost -> (Fix l, EGraph l)
- Data.Equality.Saturation: equalitySaturation' :: forall l schd. (Language l, Scheduler schd) => Proxy schd -> Fix l -> [Rewrite l] -> CostFunction l -> (Fix l, EGraph l)
+ Data.Equality.Saturation: equalitySaturation' :: forall l schd cost. (Language l, Scheduler schd, Ord cost) => Proxy schd -> Fix l -> [Rewrite l] -> CostFunction l cost -> (Fix l, EGraph l)
- Data.Equality.Saturation: type CostFunction l = l Cost -> Cost
+ Data.Equality.Saturation: type CostFunction l cost = l cost -> cost

Files

CHANGELOG.md view
@@ -1,5 +1,16 @@-# Revision history for hsym+# Revision history for hegg -## 0.1.0.0 -- YYYY-mm-dd+## 0.2.0.0 -- 2022-09-19++* Expose `runEqualitySaturation` to run equality saturation on existing e-graphs+    whole instead of focusing on individual expressions+* (Very) significant performance improvements!+* Make `CostFunction` polymorphic over the `Cost` type, requiring that type+    to instance `Ord`+* Make e-graph abstract. The internal structure can still be modified through+    the available lenses in `Data.Equality.Graph.Lens`+* Fix a bug related to `NodeMap`'s size.++## 0.1.0.0 -- 2022-08-25  * First version. Released on an unsuspecting world.
+ README.md view
@@ -0,0 +1,233 @@+## hegg++Fast equality saturation in Haskell++Based on [*egg: Fast and Extensible Equality Saturation*](https://arxiv.org/pdf/2004.03082.pdf), [*Relational E-matching*](https://arxiv.org/pdf/2108.02290.pdf) and the [rust implementation](https://github.com/egraphs-good/egg).++### Equality Saturation and E-graphs++Suggested material on equality saturation and e-graphs for beginners+* (tutorial) https://docs.rs/egg/latest/egg/tutorials/_01_background/index.html+* (5m video) https://www.youtube.com/watch?v=ap29SzDAzP0++## Equality saturation in Haskell++To get a feel for how we can use `hegg` and do equality saturation in Haskell,+we'll write a simple numeric *symbolic* manipulation library that can simplify expressions+according to a set of rewrite rules by leveraging equality saturation.++If you've never heard of symbolic mathematics you might get some intuition from+reading [Let’s Program a Calculus+Student](https://iagoleal.com/posts/calculus-symbolic/) first.++### Syntax++We'll start by defining the abstract syntax tree for our simple symbolic expressions:+```hs+data SymExpr = Const Double+             | Symbol String+             | SymExpr :+: SymExpr+             | SymExpr :*: SymExpr+             | SymExpr :/: SymExpr+infix 6 :+:+infix 7 :*:, :/:++e1 :: SymExpr+e1 = (Symbol "x" :*: Const 2) :/: (Const 2) -- (x*2)/2+```++You might notice that `(x*2)/2` is the same as just `x`. Our goal is to get+equality saturation to do that for us.++Our second step is to instance `Language` for our `SymExpr`++### Language++`Language` is the required constraint on *expressions* that are to be+represented in e-graph and on which equality saturation can be run:++```hs+class (Analysis l, Traversable l, Ord1 l) => Language l+```++To declare a `Language` we must write the "base functor" of `SymExpr` +(i.e. use a type parameter where the recursion points used to be in the original `SymExpr`),+then instance `Traversable`, `Ord1`, and write an `Analysis` instance for it (see next section).++```hs+data SymExpr a = Const Double+               | Symbol String+               | a :+: a+               | a :*: a+               | a :/: a+               deriving (Functor, Foldable, Traversable)+infix 6 :+:+infix 7 :*:, :/:+```++Suggested reading on defining recursive data types in their parametrized+version: [Introduction To Recursion+Schemes](https://blog.sumtypeofway.com/posts/introduction-to-recursion-schemes.html)++If we now wanted to represent an expression, we'd write it in its+fixed-point form++```hs+e1 :: Fix SymExpr+e1 = Fix (Fix (Fix (Symbol "x") :*: Fix (Const 2)) :/: (Fix (Const 2))) -- (x*2)/2+```++We've already automagically derived `Functor`, `Foldable` and `Traversable`+instances, and can use the following template haskell functions from `derive-compat` to derive `Ord1`.+```hs+deriveEq1   ''SymExpr+deriveOrd1  ''SymExpr+```++Then, we define an `Analysis` for our `SymExpr`.++### Analysis++E-class analysis is first described in [*egg: Fast and Extensible Equality+Saturation*](https://arxiv.org/pdf/2004.03082.pdf) as a way to make equality+saturation more *extensible*.++With it, we can attach *analysis data* from a semilattice to each e-class. More+can be read about e-class analysis in the [`Data.Equality.Analsysis`]() module and+in the paper.++We could easily define constant folding (`2+2` being simplified to `4`) through+an `Analysis` instance, but for the sake of simplicity we'll simply define the+analysis data as `()` and always ignore it.++```hs+instance Analysis SymExpr where+  type Domain SymExpr = ()+  makeA _ _ = ()+  joinA _ _ = ()+```++### Language, again++With this setup, we can now express that `SymExpr` forms a `Language` which we+can represent and manipulate in an e-graph by simply instancing it (there are no+additional functions to define).+```hs+instance Language SymExpr+```++### Equality saturation++Equality saturation is defined as the function+```hs+equalitySaturation :: forall l. Language l+                   => Fix l             -- ^ Expression to run equality saturation on+                   -> [Rewrite l]       -- ^ List of rewrite rules+                   -> CostFunction l    -- ^ Cost function to extract the best equivalent representation+                   -> (Fix l, EGraph l) -- ^ Best equivalent expression and resulting e-graph+```++To recap, our goal is to reach `x` starting from `(x*2)/2` by means of equality+saturation.++We already have a starting expression, so we're missing a list of rewrite rules+(`[Rewrite l]`) and a cost function (`CostFunction`).++### Cost function++Picking up the easy one first:+```hs+type CostFunction l cost = l cost -> cost+```++A cost function is used to attribute a cost to representations in the e-graph and to extract the best one.+The first type parameter `l` is the language we're going to attribute a cost to, and+the second type parameter `cost` is the type with which we will model cost. For+the cost function to be valid, `cost` must instance `Ord`.++We'll say `Const`s and `Symbol`s are the cheapest and then in increasing cost we+have `:+:`, `:*:` and `:/:`, and model cost with the `Int` type.+```hs+cost :: CostFunction SymExpr Int+cost = \case+  Const  x -> 1+  Symbol x -> 1+  c1 :+: c2 -> c1 + c2 + 2+  c1 :*: c2 -> c1 + c2 + 3+  c1 :/: c2 -> c1 + c2 + 4+```++### Rewrite rules++Rewrite rules are transformations applied to matching expressions represented in+an e-graph.++We can write simple rewrite rules and conditional rewrite rules, but we'll only look at the simple ones.++A simple rewrite is formed of its left hand side and right hand side. When the+left hand side is matched in the e-graph, the right hand side is added to the+e-class where the left hand side was found.+```hs+data Rewrite lang = Pattern lang := Pattern lang          -- Simple rewrite rule+                  | Rewrite lang :| RewriteCondition lang -- Conditional rewrite rule+```++A `Pattern` is basically an expression that might contain variables and which can be matched against actual expressions.+```hs+data Pattern lang+    = NonVariablePattern (lang (Pattern lang))+    | VariablePattern Var+```+A patterns is defined by its non-variable and variable parts, and can be+constructed directly or using the helper function `pat` and using+`OverloadedStrings` for the variables, where `pat` is just a synonym for+`NonVariablePattern` and a string literal `"abc"` is turned into a `Pattern`+constructed with `VariablePattern`.++We can then write the following very specific set of rewrite rules to simplify+our simple symbolic expressions.+```hs+rewrites :: [Rewrite SymExpr]+rewrites =+  [ pat (pat ("a" :*: "b") :/: "c") := pat ("a" :*: pat ("b" :/: "c"))+  , pat ("x" :/: "x")               := pat (Const 1)+  , pat ("x" :*: (pat (Const 1)))   := "x"+  ]+```+### Equality saturation, again++We can now run equality saturation on our expression!++```hs+let expr = fst (equalitySaturation e1 rewrites cost)+```+And upon printing we'd see `expr = Symbol "x"`!++This was a first introduction which skipped over some details but that tried to+walk through fundamental concepts for using e-graphs and equality saturation+with this library.++The final code for this tutorial is available under `test/SimpleSym.hs`++A more complicated symbolic rewrite system which simplifies some derivatives and+integrals was written for the testsuite. It can be found at `test/Sym.hs`.++This library could also be used not only for equality-saturation but also for+the equality-graphs and other equality-things (such as e-matching) available.+For example, using just the e-graphs from `Data.Equality.Graph` to improve GHC's+pattern match checker (https://gitlab.haskell.org/ghc/ghc/-/issues/19272).++## Profiling++Notes on profiling for development.++For producing the info table, ghc-options must include `-finfo-table-map+-fdistinct-constructor-tables`++```+cabal run --enable-profiling hegg-test -- +RTS -p -s -hi -l-agu+ghc-prof-flamegraph hegg-test.prof+eventlog2html hegg-test.eventlog+open hegg-test.svg+open hegg-test.eventlog.html+```
hegg.cabal view
@@ -1,24 +1,28 @@ cabal-version:      2.4 name:               hegg-version:            0.1.0.0+version:            0.2.0.0 Tested-With:        GHC ==9.4.1 || ==9.2.2 || ==9.0.2 || ==8.10.7 synopsis:           Fast equality saturation in Haskell  description:        Fast equality saturation and equality graphs based on "egg:                     Fast and Extensible Equality Saturation" and "Relational E-matching".                     .-                    This package provides e-graphs (see 'Data.Equality.Graph'),+                    This package provides e-graphs (see "Data.Equality.Graph"),                     a data structure which efficiently represents a congruence-                    relation over many expressions+                    relation over many expressions.                     .+                    For a monadic interface to e-graphs check out+                    "Data.Equality.Graph.Monad" (home to the convenient+                    function 'represent').+                    .                     Secondly, it provides functions for doing equality-                    saturation (see 'Data.Equality.Saturation'), an+                    saturation (see "Data.Equality.Saturation"), an                     optimization/term-rewriting technique that applies rewrite                     rules non-destructively to an expression represented in an                     e-graph until saturation, and then extracts the best                     representation.                     .-                    Equality matching (see 'Data.Equality.Matching') is done as+                    Equality matching (see "Data.Equality.Matching") is done as                     described in "Relational E-Matching"                     .                     For a walkthrough of writing a simple symbolic@@ -35,6 +39,7 @@ copyright:          Copyright (C) 2022 Rodrigo Mesquita category:           Data extra-source-files: CHANGELOG.md+                    README.md  source-repository head     type: git@@ -58,6 +63,7 @@                       -- -dsuppress-var-kinds      exposed-modules:  Data.Equality.Graph,+                      Data.Equality.Graph.Internal,                       Data.Equality.Graph.ReprUnionFind,                       Data.Equality.Graph.Classes,                       Data.Equality.Graph.Classes.Id,@@ -73,7 +79,8 @@                       Data.Equality.Analysis,                       Data.Equality.Saturation.Scheduler,                       Data.Equality.Saturation.Rewrites,-                      Data.Equality.Utils+                      Data.Equality.Utils,+                      Data.Equality.Utils.SizedList     if impl(ghc >= 9.2)         exposed-modules: Data.Equality.Utils.IntToIntMap @@ -93,22 +100,37 @@     default-language: Haskell2010  test-suite hegg-test-    ghc-options:      -threaded -Wall+    ghc-options:      -Wall                       -- -finfo-table-map -fdistinct-constructor-tables-                      -- -threaded+                      -threaded     default-language: Haskell2010     type:             exitcode-stdio-1.0     hs-source-dirs:   test     main-is:          Test.hs     other-modules:    Invariants, Sym, Lambda, SimpleSym     other-extensions: OverloadedStrings-    build-depends:    base >= 4.4 && < 5,-                      hegg >= 0.1 && < 0.2,-                      containers >= 0.4 && < 0.7,+    build-depends:    base,+                      hegg,+                      containers,                       deriving-compat  >= 0.6 && < 0.7,                       tasty            >= 1.4 && < 1.5,                       tasty-hunit      >= 0.10 && < 0.11,                       tasty-quickcheck >= 0.10 && < 0.11++benchmark hegg-bench+  default-language: Haskell2010+  hs-source-dirs: test+  other-modules:  Invariants, Sym, Lambda, SimpleSym+  main-is:        Bench.hs+  type:           exitcode-stdio-1.0+  build-depends:  base, hegg,+                  containers,+                  deriving-compat,+                  tasty,+                  tasty-hunit,+                  tasty-quickcheck,+                  tasty-bench >= 0.2  && < 0.4+  ghc-options:    -with-rtsopts=-A32m -threaded  Flag vizdot     Description: Compile 'Data.Equality.Graph.Dot' module to visualize e-graphs
src/Data/Equality/Analysis.hs view
@@ -22,13 +22,15 @@ -} module Data.Equality.Analysis where +import Data.Kind (Type)+ import Data.Equality.Graph.Classes.Id import Data.Equality.Graph.Nodes -import {-# SOURCE #-} Data.Equality.Graph (EGraph)+import {-# SOURCE #-} Data.Equality.Graph.Internal (EGraph)  -- | The e-class analysis defined for a language @l@.-class Eq (Domain l) => Analysis l where+class Eq (Domain l) => Analysis (l :: Type -> Type) where      -- | Domain of data stored in e-class according to e-class analysis     type Domain l
src/Data/Equality/Extraction.hs view
@@ -21,7 +21,6 @@    -- * Cost   , CostFunction-  , Cost   , depthCost   ) where @@ -30,6 +29,7 @@  import Data.Equality.Utils import Data.Equality.Graph+import Data.Equality.Graph.Lens  -- vvvv and necessarily all the best sub-expressions from children equilalence classes @@ -45,18 +45,19 @@ -- @ -- -- For a real example you might want to check out the source code of 'Data.Equality.Saturation.equalitySaturation''-extractBest :: forall lang. Language lang-            => EGraph lang       -- ^ The e-graph out of which we are extracting an expression-            -> CostFunction lang -- ^ The cost function to define /best/-            -> ClassId           -- ^ The e-class from which we'll extract the expression-            -> Fix lang          -- ^ The resulting /best/ expression, in its fixed point form.-extractBest g@EGraph{classes = eclasses'} cost (flip find g -> i) = +extractBest :: forall lang cost+             . (Language lang, Ord cost)+            => EGraph lang            -- ^ The e-graph out of which we are extracting an expression+            -> CostFunction lang cost -- ^ The cost function to define /best/+            -> ClassId                -- ^ The e-class from which we'll extract the expression+            -> Fix lang               -- ^ The resulting /best/ expression, in its fixed point form.+extractBest egr cost (flip find egr -> i) =       -- Use `egg`s strategy of find costs for all possible classes and then just     -- picking up the best from the target e-class.  In practice this shouldn't     -- find the cost of unused nodes because the "topmost" e-class will be the     -- target, and all sub-classes must be calculated?-    let allCosts = findCosts eclasses' mempty+    let allCosts = findCosts (egr^._classes) mempty       in case findBest i allCosts of         Just (CostWithExpr (_,n)) -> n@@ -65,15 +66,14 @@   where      -- | Find the lowest cost of all e-classes in an e-graph in an extraction-    findCosts :: ClassIdMap (EClass lang) -> ClassIdMap (CostWithExpr lang) -> ClassIdMap (CostWithExpr lang)+    findCosts :: ClassIdMap (EClass lang) -> ClassIdMap (CostWithExpr lang cost) -> ClassIdMap (CostWithExpr lang cost)     findCosts eclasses current =        let (modified, updated) = IM.foldlWithKey f (False, current) eclasses            {-# INLINE f #-}-          f :: (Bool, ClassIdMap (CostWithExpr lang)) -> Int -> EClass lang -> (Bool, ClassIdMap (CostWithExpr lang))-          f = \acc@(_, beingUpdated) i' (EClass _ nodes _ _) ->-+          f :: (Bool, ClassIdMap (CostWithExpr lang cost)) -> Int -> EClass lang -> (Bool, ClassIdMap (CostWithExpr lang cost))+          f = \acc@(_, beingUpdated) i' EClass{eClassNodes = nodes} ->                 let                     currentCost = IM.lookup i' beingUpdated @@ -104,20 +104,24 @@     -- For a node to have a cost, all its (canonical) sub-classes have a cost and     -- an associated better expression. We return the constructed best expression     -- with its cost-    nodeTotalCost :: Traversable lang => ClassIdMap (CostWithExpr lang) -> ENode lang -> Maybe (CostWithExpr lang)+    nodeTotalCost :: Traversable lang => ClassIdMap (CostWithExpr lang cost) -> ENode lang -> Maybe (CostWithExpr lang cost)     nodeTotalCost m (Node n) = do-        expr <- traverse ((`IM.lookup` m) . flip find g) n+        expr <- traverse ((`IM.lookup` m) . flip find egr) n         return $ CostWithExpr (cost ((fst . unCWE) <$> expr), (Fix $ (snd . unCWE) <$> expr))     {-# INLINE nodeTotalCost #-}--{-# SCC extractBest #-}+{-# INLINABLE extractBest #-}  -- | A cost function is used to attribute a cost to representations in the -- e-graph and to extract the best one. --+-- The cost function is polymorphic over the type used for the cost, however+-- @cost@ must instance 'Ord' in order for the defined 'CostFunction' to+-- fulfill its purpose. That's why we have an @Ord cost@ constraint in+-- 'Data.Equality.Saturation.equalitySaturation' and 'extractBest'+-- -- === Example -- @--- symCost :: Expr Cost -> Cost+-- symCost :: Expr Int -> Int -- symCost = \case --     BinOp Integral e1 e2 -> e1 + e2 + 20000 --     BinOp Diff e1 e2 -> e1 + e2 + 500@@ -126,29 +130,26 @@ --     Sym _ -> 1 --     Const _ -> 1 -- @-type CostFunction l = l Cost -> Cost---- | 'Cost' is simply an integer-type Cost = Int+type CostFunction l cost = l cost -> cost  -- | Simple cost function: the deeper the expression, the bigger the cost-depthCost :: Language l => CostFunction l+depthCost :: Language l => CostFunction l Int depthCost = (+1) . sum {-# INLINE depthCost #-}  -- | Find the current best node and its cost in an equivalence class given only the class and the current extraction -- This is not necessarily the best node in the e-graph, only the best in the current extraction state-findBest :: ClassId -> ClassIdMap (CostWithExpr lang) -> Maybe (CostWithExpr lang)+findBest :: ClassId -> ClassIdMap (CostWithExpr lang a) -> Maybe (CostWithExpr lang a) findBest i = IM.lookup i {-# INLINE findBest #-} -newtype CostWithExpr lang = CostWithExpr { unCWE :: (Cost, Fix lang) }+newtype CostWithExpr lang a = CostWithExpr { unCWE :: (a, Fix lang) } -instance Eq (CostWithExpr lang) where+instance Eq a => Eq (CostWithExpr lang a) where   (==) (CostWithExpr (a,_)) (CostWithExpr (b,_)) = a == b   {-# INLINE (==) #-} -instance Ord (CostWithExpr lang) where+instance Ord a => Ord (CostWithExpr lang a) where   compare (CostWithExpr (a,_)) (CostWithExpr (b,_)) = a `compare` b   {-# INLINE compare #-} 
src/Data/Equality/Graph.hs view
@@ -3,7 +3,6 @@ {-# LANGUAGE TupleSections #-} -- {-# LANGUAGE ApplicativeDo #-} {-# LANGUAGE BlockArguments #-}-{-# LANGUAGE UndecidableInstances #-} -- tmp show {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-}@@ -15,9 +14,7 @@ module Data.Equality.Graph     (       -- * Definition of e-graph-      EGraph(..)--    , Memo, Worklist+      EGraph        -- * Functions on e-graphs     , emptyEGraph@@ -38,12 +35,15 @@ -- import GHC.Conc  import Data.Function--import Data.Functor.Classes+import Data.Bifunctor+import Data.Containers.ListUtils  import qualified Data.IntMap.Strict as IM import qualified Data.Set    as S +import Data.Equality.Utils.SizedList++import Data.Equality.Graph.Internal import Data.Equality.Graph.ReprUnionFind import Data.Equality.Graph.Classes import Data.Equality.Graph.Nodes@@ -51,50 +51,23 @@ import Data.Equality.Language import Data.Equality.Graph.Lens --- | E-graph representing terms of language @l@.------ Intuitively, an e-graph is a set of equivalence classes (e-classes). Each e-class is a--- set of e-nodes representing equivalent terms from a given language, and an e-node is a function--- symbol paired with a list of children e-classes.-data EGraph l = EGraph-    { unionFind :: !ReprUnionFind           -- ^ Union find like structure to find canonical representation of an e-class id-    , classes   :: !(ClassIdMap (EClass l)) -- ^ Map canonical e-class ids to their e-classes-    , memo      :: !(Memo l)                -- ^ Hashcons maps all canonical e-nodes to their e-class ids-    , worklist  :: !(Worklist l)            -- ^ Worklist of e-class ids that need to be upward merged-    , analysisWorklist :: !(Worklist l)     -- ^ Like 'worklist' but for analysis repairing-    }---- | The hashcons 𝐻  is a map from e-nodes to e-class ids-type Memo l = NodeMap l ClassId---- | Maintained worklist of e-class ids that need to be “upward merged”-type Worklist l = NodeMap l ClassId- -- ROMES:TODO: join things built in paralell? -- instance Ord1 l => Semigroup (EGraph l) where --     (<>) eg1 eg2 = undefined -- not so easy -- instance Ord1 l => Monoid (EGraph l) where --     mempty = EGraph emptyUF mempty mempty mempty -instance (Show (Domain l), Show1 l) => Show (EGraph l) where-    show (EGraph a b c d e) =-        "UnionFind: " <> show a <>-            "\n\nE-Classes: " <> show b <>-                "\n\nHashcons: " <> show c <>-                    "\n\nWorklist: " <> show d <>-                        "\n\nAnalWorklist: " <> show e - -- | Add an e-node to the e-graph -- -- If the e-node is already represented in this e-graph, the class-id of the -- class it's already represented in will be returned. add :: forall l. Language l => ENode l -> EGraph l -> (ClassId, EGraph l) add uncanon_e egr =-    let !new_en = {-# SCC "-2" #-} canonicalize uncanon_e egr+    let !new_en = canonicalize uncanon_e egr -     in case {-# SCC "-1" #-} lookupNM new_en (memo egr) of-      Just canon_enode_id -> {-# SCC "0" #-} (find canon_enode_id egr, egr)+     in case lookupNM new_en (memo egr) of+      Just canon_enode_id -> (find canon_enode_id egr, egr)       Nothing ->          let@@ -111,9 +84,9 @@             -- to the e-class parents the new e-node and its e-class id             --             -- And add new e-class to existing e-classes-            new_parents      = insertNM new_en new_eclass_id-            new_classes      = {-# SCC "2" #-} IM.insert new_eclass_id new_eclass $-                                    foldr  (IM.adjust (_parents %~ new_parents))+            new_parents      = ((new_eclass_id, new_en) |:)+            new_classes      = IM.insert new_eclass_id new_eclass $+                                    foldr  (IM.adjust ((_parents %~ new_parents)))                                            (classes egr)                                            (unNode new_en) @@ -142,24 +115,24 @@             -- something else?             --             -- So in the end, we do need to addToWorklist to get correct results-            new_worklist     = {-# SCC "4" #-} insertNM new_en new_eclass_id (worklist egr)+            new_worklist     = (new_eclass_id, new_en):(worklist egr)              -- Add the e-node's e-class id at the e-node's id-            new_memo         = {-# SCC "5" #-} insertNM new_en new_eclass_id (memo egr)+            new_memo         = insertNM new_en new_eclass_id (memo egr)           in ( new_eclass_id              , egr { unionFind = new_uf                   , classes   = new_classes                   , worklist  = new_worklist-                  , memo     = new_memo+                  , memo      = new_memo                   }                    -- Modify created node according to analysis-                  & {-# SCC "6" #-} modifyA new_eclass_id+                  & modifyA new_eclass_id              )-{-# SCC add #-}+{-# INLINABLE add #-}  -- | Merge 2 e-classes by id merge :: forall l. Language l => ClassId -> ClassId -> EGraph l -> (ClassId, EGraph l)@@ -180,7 +153,7 @@             -- Leader is the class with more parents            (leader, leader_class, sub, sub_class) =-               if (sizeNM (class_a^._parents)) < (sizeNM (class_b^._parents))+               if sizeSL (class_a^._parents) < sizeSL (class_b^._parents)                   then (b', class_b, a', class_a) -- b is leader                   else (a', class_a, b', class_b) -- a is leader @@ -189,8 +162,8 @@             -- Update leader class with all e-nodes and parents from the            -- subsumed class-           updatedLeader = leader_class & _parents %~ (<> sub_class^._parents)-                                        & _nodes   %~ (<> sub_class^._nodes)+           updatedLeader = leader_class & _parents %~ (sub_class^._parents <>)+                                        & _nodes   %~ (sub_class^._nodes <>)                                         & _data    .~ new_data            new_data = joinA @l (leader_class^._data) (sub_class^._data) @@ -201,18 +174,18 @@            -- Add all subsumed parents to worklist We can do this instead of            -- adding the new e-class itself to the worklist because it would end            -- up adding its parents anyway-           new_worklist = sub_class^._parents <> (worklist egr0)+           new_worklist = toListSL (sub_class^._parents) <> (worklist egr0)             -- If the new_data is different from the classes, the parents of the            -- class whose data is different from the merged must be put on the            -- analysisWorklist            new_analysis_worklist =+             (if new_data /= (sub_class^._data)+                then toListSL (sub_class^._parents)+                else mempty) <>              (if new_data /= (leader_class^._data)-                then leader_class^._parents+                then toListSL (leader_class^._parents)                 else mempty) <>-             (if new_data /= (sub_class^._data)-                 then sub_class^._parents-                 else mempty) <>              (analysisWorklist egr0)             -- ROMES:TODO: The code that makes the -1 * cos test pass when some other things are tweaked@@ -231,10 +204,10 @@              & modifyA new_id          in (new_id, new_egr)-{-# SCC merge #-}+{-# INLINEABLE merge #-}              --- | The rebuild operation processes the e-graph's current 'Worklist',+-- | The rebuild operation processes the e-graph's current worklist, -- restoring the invariants of deduplication and congruence. Rebuilding is -- similar to other approaches in how it restores congruence; but it uniquely -- allows the client to choose when to restore invariants in the context of a@@ -244,47 +217,52 @@   -- empty worklists   -- repair deduplicated e-classes   let-    egr'  = foldrWithKeyNM' repair (EGraph uf cls mm mempty mempty) wl-    egr'' = foldrWithKeyNM' repairAnal egr' awl+    emptiedEgr = (EGraph uf cls mm mempty mempty)++    wl'   = nubOrd $ bimap (`find` emptiedEgr) (`canonicalize` emptiedEgr) <$> wl+    egr'  = foldr repair emptiedEgr wl'++    awl'  = nubIntOn fst $ first (`find` egr') <$> awl+    egr'' = foldr repairAnal egr' awl'   in   -- Loop until worklist is completely empty   if null (worklist egr'') && null (analysisWorklist egr'')      then egr''-     else rebuild egr''--{-# SCC rebuild #-}+     else rebuild egr'' -- ROMES:TODO: Doesn't seem to be needed at all in the testsuite.+{-# INLINEABLE rebuild #-}  -- ROMES:TODO: find repair_id could be shared between repair and repairAnal?  -- | Repair a single worklist entry.-repair :: forall l. Language l => ENode l -> ClassId -> EGraph l -> EGraph l-repair node repair_id egr =+repair :: forall l. Language l => (ClassId, ENode l) -> EGraph l -> EGraph l+repair (repair_id, node) egr = -   case insertLookupNM (node `canonicalize` egr) (find repair_id egr) (deleteNM node $ memo egr) of-- TODO: I seem to really need it. Is find needed? (they don't use it)+   -- TODO We're no longer deleting the uncanonicalized node, how much does it matter that the structure keeps growing? -      (Nothing, memo2) -> egr { memo = memo2 } -- Return new memo but delete uncanonicalized node+   case insertLookupNM node repair_id (memo egr) of -      (Just existing_class, memo2) -> snd (merge existing_class repair_id egr{memo = memo2})-{-# SCC repair #-}+      (Nothing, memo') -> egr { memo = memo' } -- new memo with inserted node +      (Just existing_class, memo') -> snd (merge existing_class repair_id egr{memo = memo'})+{-# INLINE repair #-}+ -- | Repair a single analysis-worklist entry.-repairAnal :: forall l. Language l => ENode l -> ClassId -> EGraph l -> EGraph l-repairAnal node repair_id egr =+repairAnal :: forall l. Language l => (ClassId, ENode l) -> EGraph l -> EGraph l+repairAnal (repair_id, node) egr =     let-        canon_id = find repair_id egr-        c        = egr^._class canon_id+        c        = (egr^._classes) IM.! repair_id         new_data = joinA @l (c^._data) (makeA node egr)     in     -- Take action if the new_data is different from the existing data     if c^._data /= new_data         -- Merge result is different from original class data, update class         -- with new_data-       then egr { analysisWorklist = c^._parents <> analysisWorklist egr+       then egr { analysisWorklist = toListSL (c^._parents) <> analysisWorklist egr                 }-                & _class canon_id._data .~ new_data-                & modifyA canon_id+                & _classes %~ (IM.adjust (_data .~ new_data) repair_id)+                & modifyA repair_id        else egr-{-# SCC repairAnal #-}+{-# INLINE repairAnal #-}  -- | Canonicalize an e-node --@@ -296,7 +274,7 @@ -- canonicalize(𝑓(𝑎,𝑏,𝑐,...)) = 𝑓((find 𝑎), (find 𝑏), (find 𝑐),...) canonicalize :: Functor l => ENode l -> EGraph l -> ENode l canonicalize (Node enode) eg = Node $ fmap (`find` eg) enode-{-# SCC canonicalize #-}+{-# INLINE canonicalize #-}  -- | Find the canonical representation of an e-class id in the e-graph -- Invariant: The e-class id always exists.
− src/Data/Equality/Graph.hs-boot
@@ -1,22 +0,0 @@-{-# LANGUAGE RoleAnnotations #-}-{-# LANGUAGE KindSignatures #-}-module Data.Equality.Graph where--import Data.Equality.Graph.Classes.Id-import Data.Equality.Graph.Nodes-import Data.Equality.Graph.ReprUnionFind-import {-# SOURCE #-} Data.Equality.Graph.Classes (EClass)--type role EGraph nominal-data EGraph l = EGraph-    { unionFind :: !ReprUnionFind-    , classes   :: !(ClassIdMap (EClass l))-    , memo      :: !(Memo l)-    , worklist  :: !(Worklist l)-    , analysisWorklist :: !(Worklist l)-    }--find :: ClassId -> EGraph l -> ClassId--type Memo l = NodeMap l ClassId-type Worklist l = NodeMap l ClassId
src/Data/Equality/Graph/Classes.hs view
@@ -15,6 +15,8 @@ import Data.Equality.Graph.Classes.Id import Data.Equality.Graph.Nodes +import Data.Equality.Utils.SizedList+ import Data.Equality.Analysis  -- | An e-class (an equivalence class of terms) of a language @l@.@@ -27,9 +29,9 @@     { eClassId      :: {-# UNPACK #-} !ClassId -- ^ E-class identifier     , eClassNodes   :: !(S.Set (ENode l))      -- ^ E-nodes in this class     , eClassData    :: Domain l                -- ^ The analysis data associated with this eclass.-    , eClassParents :: !(NodeMap l ClassId)    -- ^ E-nodes which are parents of this e-class and their corresponding e-class ids. We found a mapping from nodes to e-class ids a better representation than @[(ENode l, ClassId)]@, and we get de-duplication built-in.+    , eClassParents :: !(SList (ClassId, ENode l))   -- ^ E-nodes which are parents of this e-class and their corresponding e-class ids.     }  instance (Show (Domain l), Show1 l) => Show (EClass l) where-    show (EClass a b d c) = "Id: " <> show a <> "\nNodes: " <> show b <> "\nParents: " <> show c <> "\nData: " <> show d+    show (EClass a b d (SList c _)) = "Id: " <> show a <> "\nNodes: " <> show b <> "\nParents: " <> show c <> "\nData: " <> show d 
+ src/Data/Equality/Graph/Internal.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE UndecidableInstances #-} -- tmp show+{-# LANGUAGE FlexibleContexts #-}+{-# OPTIONS_HADDOCK hide #-}+{-|+ Non-abstract definition of e-graphs+ -}+module Data.Equality.Graph.Internal where++import Data.Functor.Classes++import Data.Equality.Graph.ReprUnionFind+import Data.Equality.Graph.Classes+import Data.Equality.Graph.Nodes+import Data.Equality.Analysis++-- | E-graph representing terms of language @l@.+--+-- Intuitively, an e-graph is a set of equivalence classes (e-classes). Each e-class is a+-- set of e-nodes representing equivalent terms from a given language, and an e-node is a function+-- symbol paired with a list of children e-classes.+data EGraph l = EGraph+    { unionFind :: !ReprUnionFind           -- ^ Union find like structure to find canonical representation of an e-class id+    , classes   :: !(ClassIdMap (EClass l)) -- ^ Map canonical e-class ids to their e-classes+    , memo      :: !(Memo l)                -- ^ Hashcons maps all canonical e-nodes to their e-class ids+    , worklist  :: !(Worklist l)            -- ^ Worklist of e-class ids that need to be upward merged+    , analysisWorklist :: !(Worklist l)     -- ^ Like 'worklist' but for analysis repairing+    }++-- | The hashcons 𝐻  is a map from e-nodes to e-class ids+type Memo l = NodeMap l ClassId++-- | Maintained worklist of e-class ids that need to be “upward merged”+type Worklist l = [(ClassId, ENode l)]++instance (Show (Domain l), Show1 l) => Show (EGraph l) where+    show (EGraph a b c d e) =+        "UnionFind: " <> show a <>+            "\n\nE-Classes: " <> show b <>+                "\n\nHashcons: " <> show c <>+                    "\n\nWorklist: " <> show d <>+                        "\n\nAnalWorklist: " <> show e
+ src/Data/Equality/Graph/Internal.hs-boot view
@@ -0,0 +1,9 @@+{-# LANGUAGE RoleAnnotations #-}+{-# LANGUAGE StandaloneKindSignatures #-}+module Data.Equality.Graph.Internal where++import Data.Kind++type EGraph :: (Type -> Type) -> Type+type role EGraph nominal+data EGraph l
src/Data/Equality/Graph/Lens.hs view
@@ -1,8 +1,10 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE Rank2Types #-} {-|-  Hand-rolled lenses on e-graphs and e-classes which come in quite handy and-  are heavily used in 'Data.Equality.Graph'.+  Hand-rolled lenses on e-graphs and e-classes which come in quite handy, are+  heavily used in 'Data.Equality.Graph', and are the only exported way of+  editing the structure of the e-graph. If you want to write some complex+  'Analysis' you'll probably need these.  -} module Data.Equality.Graph.Lens where @@ -12,11 +14,13 @@ import Data.Functor.Identity import Data.Functor.Const +import Data.Equality.Utils.SizedList+import Data.Equality.Graph.Internal import Data.Equality.Graph.Classes.Id import Data.Equality.Graph.Nodes import Data.Equality.Graph.Classes+import Data.Equality.Graph.ReprUnionFind import Data.Equality.Analysis-import {-# SOURCE #-} Data.Equality.Graph (EGraph(..), Memo, find)  -- | A 'Lens'' as defined in other lenses libraries type Lens' s a = forall f. Functor f => (a -> f a) -> (s -> f s)@@ -37,12 +41,13 @@ -- Calls 'error' when the e-class doesn't exist _class :: ClassId -> Lens' (EGraph l) (EClass l) _class i afa s =-    let canon_id = find i s+    let canon_id = findRepr i (unionFind s)      in (\c' -> s { classes = IM.insert canon_id c' (classes s) }) <$> afa (classes s IM.! canon_id) {-# INLINE _class #-} --- | Lens for the 'Memo' of e-nodes in an e-graph-_memo :: Lens' (EGraph l) (Memo l)+-- | Lens for the memo of e-nodes in an e-graph, that is, a mapping from+-- e-nodes to the e-class they're represented in+_memo :: Lens' (EGraph l) (NodeMap l ClassId) _memo afa egr = (\m1 -> egr {memo = m1}) <$> afa (memo egr) {-# INLINE _memo #-} @@ -57,8 +62,8 @@ {-# INLINE _data #-}  -- | Lens for the parent e-classes of an e-class-_parents :: Lens' (EClass l) (NodeMap l ClassId)-_parents afa EClass{..} = EClass eClassId eClassNodes eClassData <$> afa eClassParents+_parents :: Lens' (EClass l) (SList (ClassId, ENode l))+_parents afa EClass{..} = (\ps -> EClass eClassId eClassNodes eClassData ps) <$> afa eClassParents {-# INLINE _parents #-}  -- | Lens for the e-nodes in an e-class
src/Data/Equality/Graph/Nodes.hs view
@@ -37,7 +37,7 @@ -- | Get the children e-class ids of an e-node children :: Traversable l => ENode l -> [ClassId] children = toList . unNode-{-# SCC children #-}+{-# INLINE children #-}  -- * Operator @@ -48,7 +48,7 @@ -- | Get the operator (function symbol) of an e-node operator :: Traversable l => ENode l -> Operator l operator = Operator . void . unNode-{-# SCC operator #-}+{-# INLINE operator #-}  instance Eq1 l => (Eq (ENode l)) where     (==) (Node a) (Node b) = liftEq (==) a b@@ -75,20 +75,14 @@ -- * Node Map  -- | A mapping from e-nodes of @l@ to @a@-data NodeMap (l :: Type -> Type) a = NodeMap { unNodeMap :: !(M.Map (ENode l) a), sizeNodeMap :: {-# UNPACK #-} !Int }+newtype NodeMap (l :: Type -> Type) a = NodeMap { unNodeMap :: M.Map (ENode l) a } -- TODO: Investigate whether it would be worth it requiring a trie-map for the -- e-node definition. Probably it isn't better since e-nodes aren't recursive.-  deriving (Show, Functor, Foldable, Traversable)--instance (Eq1 l, Ord1 l) => Semigroup (NodeMap l a) where-  NodeMap m1 s1 <> NodeMap m2 s2 = NodeMap (m1 <> m2) (s1 + s2)--instance (Eq1 l, Ord1 l) => Monoid (NodeMap l a) where-  mempty = NodeMap mempty 0+  deriving (Show, Functor, Foldable, Traversable, Semigroup, Monoid)  -- | Insert a value given an e-node in a 'NodeMap' insertNM :: Ord1 l => ENode l -> a -> NodeMap l a -> NodeMap l a-insertNM e v (NodeMap m s) = NodeMap (M.insert e v m) (s+1)+insertNM e v (NodeMap m) = NodeMap (M.insert e v m) {-# INLINE insertNM #-}  -- | Lookup an e-node in a 'NodeMap'@@ -98,12 +92,12 @@  -- | Delete an e-node in a 'NodeMap' deleteNM :: Ord1 l => ENode l -> NodeMap l a -> NodeMap l a-deleteNM e (NodeMap m s) = NodeMap (M.delete e m) (s-1)+deleteNM e (NodeMap m) = NodeMap (M.delete e m) {-# INLINE deleteNM #-}  -- | Insert a value and lookup by e-node in a 'NodeMap' insertLookupNM :: Ord1 l => ENode l -> a -> NodeMap l a -> (Maybe a, NodeMap l a)-insertLookupNM e v (NodeMap m s) = second (flip NodeMap (s+1)) $ M.insertLookupWithKey (\_ a _ -> a) e v m+insertLookupNM e v (NodeMap m) = second NodeMap $ M.insertLookupWithKey (\_ a _ -> a) e v m {-# INLINE insertLookupNM #-}  -- | As 'Data.Map.foldlWithKeyNM'' but in a 'NodeMap'@@ -120,12 +114,12 @@ -- -- This operation takes constant time (__O(1)__) sizeNM :: NodeMap l a -> Int-sizeNM = sizeNodeMap+sizeNM = M.size . unNodeMap {-# INLINE sizeNM #-}  -- | As 'Data.Map.traverseWithKeyNM' but in a 'NodeMap' traverseWithKeyNM :: Applicative t => (ENode l -> a -> t b) -> NodeMap l a -> t (NodeMap l b) -traverseWithKeyNM f (NodeMap m s) = (`NodeMap` s) <$> M.traverseWithKey f m+traverseWithKeyNM f (NodeMap m) = NodeMap <$> M.traverseWithKey f m {-# INLINE traverseWithKeyNM #-}  -- Node Set
src/Data/Equality/Graph/ReprUnionFind.hs view
@@ -75,7 +75,6 @@ #else makeNewSet (RUF im si) = (si, RUF (IIM.insert si 0 im) (si + 1)) #endif-{-# SCC makeNewSet #-}  -- | Union operation of the union find. --@@ -94,21 +93,20 @@     -- represented_by_b = hc IM.! b     -- -- Overwrite previous id of b (which should be 0#) with new representative (a)     -- -- AND "rebuild" all nodes represented by b by making them represented directly by a-    -- new_im = {-# SCC "rebuild_im" #-} IIM.unliftedFoldr (\(I# x) -> IIM.insert x a#) (IIM.insert b# a# im) represented_by_b-    -- new_hc = {-# SCC "adjust_hc" #-} IM.adjust ((b:) . (represented_by_b <>)) a (IM.delete b hc)-{-# SCC unionSets #-}+    -- new_im = IIM.unliftedFoldr (\(I# x) -> IIM.insert x a#) (IIM.insert b# a# im) represented_by_b+    -- new_hc = IM.adjust ((b:) . (represented_by_b <>)) a (IM.delete b hc)  -- | Find the canonical representation of an e-class id findRepr :: ClassId -> ReprUnionFind          -> ClassId -- ^ The found canonical representation #if __GLASGOW_HASKELL__ >= 902 findRepr v@(I# v#) (RUF m s) =-  case {-# SCC "findRepr_TAKE" #-} m IIM.! v# of+  case m IIM.! v# of     0# -> v     x  -> findRepr (I# x) (RUF m s) #else findRepr v (RUF m s) =-  case {-# SCC "findRepr_TAKE" #-} m IIM.! v of+  case m IIM.! v of     0 -> v     x -> findRepr x (RUF m s) #endif@@ -121,7 +119,6 @@ -- -- When using the ad-hoc path compression in `unionSets`, the depth of -- recursion never even goes above 1!-{-# SCC findRepr #-}   -- {-# RULES
src/Data/Equality/Matching.hs view
@@ -28,6 +28,7 @@ import qualified Data.IntSet as IS  import Data.Equality.Graph+import Data.Equality.Graph.Lens import Data.Equality.Matching.Database import Data.Equality.Matching.Pattern @@ -69,7 +70,7 @@  -- | Convert an e-graph into a database eGraphToDatabase :: Language l => EGraph l -> Database l-eGraphToDatabase EGraph{..} = foldrWithKeyNM' addENodeToDB (DB mempty) memo+eGraphToDatabase egr = foldrWithKeyNM' addENodeToDB (DB mempty) (egr^._memo)   where      -- Add an enode in an e-graph, given its class, to a database@@ -78,7 +79,6 @@         -- ROMES:TODO map find         -- Insert or create a relation R_f(i1,i2,...,in) for lang in which          DB $ M.alter (Just . populate (classid:children enode)) (operator enode) m-    {-# SCC addENodeToDB #-}      -- Populate or create a triemap given the population D_x (ClassIds)     -- Insert remaining ids population doesn't exist, recursively merge tries with remaining ids@@ -89,8 +89,7 @@     -- If trie map entry already exists, populate the existing map with the remaining ids     populate []     (Just it)              = it     populate (x:xs) (Just (MkIntTrie k m)) = MkIntTrie (x `IS.insert` k) $ IM.alter (Just . populate xs) x m-    {-# SCC populate #-}-{-# SCC eGraphToDatabase #-}+{-# INLINABLE eGraphToDatabase #-}   -- * Database related internals@@ -134,4 +133,4 @@         vars :: Foldable lang => Pattern lang -> [Var]         vars (VariablePattern x) = [x]         vars (NonVariablePattern p) = nubInt $ join $ map vars $ toList p-{-# SCC compileToQuery #-}+{-# INLINABLE compileToQuery #-}
src/Data/Equality/Matching/Database.hs view
@@ -123,35 +123,35 @@   where    genericJoin' :: [Atom l] -> [Var] -> [Subst]-   genericJoin' !atoms' = \case+   genericJoin' atoms' = \case -     [] -> map mempty atoms+     [] -> mempty <$> atoms' -     (!x):xs -> -       -- IS.foldl' (\acc x_in_D -> genericJoin' (substitute x x_in_D atoms') (map (IM.insert x x_in_D) substs) xs <> acc)-       --           mempty-       --           (domainX x atoms')-       IS.foldl'-         (\acc x_in_D ->-           map (\y -> let !y' = IM.insert x x_in_D y in y') -- TODO: A bit contrieved, perhaps better to avoid map ?-             -- Each valid sub-query assumed the x -> x_in_D substitution-             (genericJoin' (substitute x x_in_D atoms') xs)-               <> acc)-         mempty-         (domainX x atoms')-   {-# SCC genericJoin' #-}+     (!x):xs -> do -   atomsWithX :: Var -> [Atom l] -> [Atom l]-   atomsWithX x = filter (x `elemOfAtom`)-   {-# INLINE atomsWithX #-}+       x_in_D <- domainX x atoms' -   domainX :: Var -> [Atom l] -> IS.IntSet-   domainX x = intersectAtoms x d . atomsWithX x+       -- Each valid sub-query assumes x -> x_in_D substitution+       y <- genericJoin' (substitute x x_in_D atoms') xs++       return $! IM.insert x x_in_D y -- TODO: A bit contrieved, perhaps better to avoid map ?++   domainX :: Var -> [Atom l] -> [Int]+   domainX x = IS.toList . intersectAtoms x d . filter (x `elemOfAtom`)    {-# INLINE domainX #-} +   -- | Substitute all occurrences of 'Var' with given 'ClassId' in all given atoms.+   substitute :: Functor lang => Var -> ClassId -> [Atom lang] -> [Atom lang]+   substitute r i = map $ \case+      Atom x l -> Atom (if CVar r == x then CClassId i else x) $ fmap (\v -> if CVar r == v then CClassId i else v) l+ {-# INLINABLE genericJoin #-}-{-# SCC genericJoin #-} +-- | Returns True if 'Var' occurs in given 'Atom'+elemOfAtom :: (Functor lang, Foldable lang) => Var -> Atom lang -> Bool+elemOfAtom !x (Atom v l) = case v of+ CVar v' -> x == v'+ _ -> or $ fmap (\v' -> CVar x == v') l  -- ROMES:TODO: Batching? How? https://arxiv.org/pdf/2108.02290.pdf @@ -182,7 +182,7 @@         -- | Get the size of an atom         atomLength :: Foldable lang => Atom lang -> Int         atomLength (Atom _ l) = 1 + F.length l-        {-# SCC atomLength #-}+        {-# INLINE atomLength #-}          -- | Extract 'Var' from 'ClassIdOrVar'         toVar :: ClassIdOrVar -> Maybe Var@@ -190,23 +190,7 @@         toVar (CClassId _) = Nothing         {-# INLINE toVar #-} -{-# SCC orderedVarsInQuery #-}  ---- | Substitute all occurrences of 'Var' with given 'ClassId' in all given atoms.-substitute :: Functor lang => Var -> ClassId -> [Atom lang] -> [Atom lang]-substitute !r !i = map $ \case-   Atom x l -> Atom (if CVar r == x then CClassId i else x) $ fmap (\v -> if CVar r == v then CClassId i else v) l-{-# SCC substitute #-}---- | Returns True if 'Var' occurs in given 'Atom'-elemOfAtom :: (Functor lang, Foldable lang) => Var -> Atom lang -> Bool-elemOfAtom !x (Atom v l) = case v of-  CVar v' -> x == v'-  _ -> or $ fmap (\v' -> CVar x == v') l-{-# SCC elemOfAtom #-}-- -- ROMES:TODO Terrible name 'intersectAtoms'  -- | Given a database and a list of Atoms with an occurring var @x@, find@@ -231,8 +215,6 @@                      Just xs -> xs  intersectAtoms _ _ [] = error "can't intersect empty list of atoms?"-{-# INLINABLE intersectAtoms #-}-{-# SCC intersectAtoms #-}  -- | Find the matching ids that a variable can take given a list of variables -- and ids that must match the structure@@ -306,7 +288,7 @@                    Just _  -> k `IS.insert` acc                          ) mempty m           -- (3)-          -- else {-# SCC "intersect_new_OTHER_var" #-} IS.unions $ IM.elems $ IM.mapMaybeWithKey (\k ls -> intersectInTrie var ({-# SCC "putSubst" #-} IM.insert x k substs) ls xs) m+          -- else IS.unions $ IM.elems $ IM.mapMaybeWithKey (\k ls -> intersectInTrie var (IM.insert x k substs) ls xs) m           else IM.foldrWithKey (\k ls (!acc) ->             case intersectInTrie var (IM.insert x k substs) ls xs of                 Nothing -> acc@@ -318,6 +300,3 @@       isVarDifferentFrom _ (CClassId _) = False       isVarDifferentFrom x (CVar     y) = x /= y       {-# INLINE isVarDifferentFrom #-}--{-# INLINABLE intersectInTrie #-}-{-# SCC intersectInTrie #-}
src/Data/Equality/Saturation.hs view
@@ -23,7 +23,7 @@ module Data.Equality.Saturation     (       -- * Equality saturation-      equalitySaturation, equalitySaturation'+      equalitySaturation, equalitySaturation', runEqualitySaturation        -- * Re-exports for equality saturation @@ -33,7 +33,7 @@       -- ** Writing cost functions       --       -- | 'CostFunction' re-exported from 'Data.Equality.Extraction' since they are required to do equality saturation-    , CostFunction --, Cost, depthCost+    , CostFunction --, depthCost        -- ** Writing expressions       -- @@ -51,6 +51,8 @@ import Data.Proxy  import Data.Equality.Utils+import Data.Equality.Graph.Nodes+import Data.Equality.Graph.Lens import qualified Data.Equality.Graph as G import Data.Equality.Graph.Monad import Data.Equality.Language@@ -63,11 +65,12 @@ import Data.Equality.Saturation.Scheduler  -- | Equality saturation with defaults-equalitySaturation :: forall l. Language l-                   => Fix l             -- ^ Expression to run equality saturation on-                   -> [Rewrite l]       -- ^ List of rewrite rules-                   -> CostFunction l    -- ^ Cost function to extract the best equivalent representation-                   -> (Fix l, EGraph l) -- ^ Best equivalent expression and resulting e-graph+equalitySaturation :: forall l cost+                    . (Language l, Ord cost)+                   => Fix l               -- ^ Expression to run equality saturation on+                   -> [Rewrite l]         -- ^ List of rewrite rules+                   -> CostFunction l cost -- ^ Cost function to extract the best equivalent representation+                   -> (Fix l, EGraph l)   -- ^ Best equivalent expression and resulting e-graph equalitySaturation = equalitySaturation' (Proxy @BackoffScheduler)  @@ -75,113 +78,123 @@ -- extract the best equivalent expression according to the given cost function -- -- This variant takes all arguments instead of using defaults-equalitySaturation' :: forall l schd-                    . (Language l, Scheduler schd)-                    => Proxy schd        -- ^ Proxy for the scheduler to use-                    -> Fix l             -- ^ Expression to run equality saturation on-                    -> [Rewrite l]       -- ^ List of rewrite rules-                    -> CostFunction l    -- ^ Cost function to extract the best equivalent representation-                    -> (Fix l, EGraph l) -- ^ Best equivalent expression and resulting e-graph-equalitySaturation' _ expr rewrites cost = egraph $ do+equalitySaturation' :: forall l schd cost+                    . (Language l, Scheduler schd, Ord cost)+                    => Proxy schd          -- ^ Proxy for the scheduler to use+                    -> Fix l               -- ^ Expression to run equality saturation on+                    -> [Rewrite l]         -- ^ List of rewrite rules+                    -> CostFunction l cost -- ^ Cost function to extract the best equivalent representation+                    -> (Fix l, EGraph l)   -- ^ Best equivalent expression and resulting e-graph+equalitySaturation' proxy expr rewrites cost = egraph $ do      -- Represent expression as an e-graph     origClass <- represent expr      -- Run equality saturation (by applying non-destructively all rewrites)-    equalitySaturation'' 0 mempty -- Start at iteration 0+    runEqualitySaturation proxy rewrites      -- Extract best solution from the e-class of the original expression     gets $ \g -> extractBest g cost origClass+{-# INLINABLE equalitySaturation' #-} -      where -        -- Take map each rewrite rule to stats on its usage so we can do-        -- backoff scheduling. Each rewrite rule is assigned an integer-        -- (corresponding to its position in the list of rewrite rules)-        equalitySaturation'' :: Int -> IM.IntMap (Stat schd) -> EGraphM l ()-        equalitySaturation'' 30 _ = return () -- Stop after X iterations-        equalitySaturation'' i stats = do+-- | Run equality saturation on an e-graph by non-destructively applying all+-- given rewrite rules until saturation (using the given 'Scheduler')+runEqualitySaturation :: forall l schd+                       . (Language l, Scheduler schd)+                      => Proxy schd          -- ^ Proxy for the scheduler to use+                      -> [Rewrite l]         -- ^ List of rewrite rules+                      -> EGraphM l ()+runEqualitySaturation _ rewrites = runEqualitySaturation' 0 mempty where -- Start at iteration 0 -            egr@G.EGraph{ G.memo = beforeMemo, G.classes = beforeClasses } <- get+  -- Take map each rewrite rule to stats on its usage so we can do+  -- backoff scheduling. Each rewrite rule is assigned an integer+  -- (corresponding to its position in the list of rewrite rules)+  runEqualitySaturation' :: Int -> IM.IntMap (Stat schd) -> EGraphM l ()+  runEqualitySaturation' 30 _ = return () -- Stop after X iterations+  runEqualitySaturation' i stats = do -            let db = eGraphToDatabase egr+      egr <- get -            -- Read-only phase, invariants are preserved-            -- With backoff scheduler-            -- ROMES:TODO parMap with chunks-            let (!matches, newStats) = mconcat (fmap (matchWithScheduler db i stats) (zip [1..] rewrites))+      let (beforeMemo, beforeClasses) = (egr^._memo, egr^._classes)+          db = eGraphToDatabase egr -            -- Write-only phase, temporarily break invariants-            forM_ matches applyMatchesRhs+      -- Read-only phase, invariants are preserved+      -- With backoff scheduler+      -- ROMES:TODO parMap with chunks+      let (!matches, newStats) = mconcat (fmap (matchWithScheduler db i stats) (zip [1..] rewrites)) -            -- Restore the invariants once per iteration-            rebuild-            -            G.EGraph { G.memo = afterMemo, G.classes = afterClasses } <- get+      -- Write-only phase, temporarily break invariants+      forM_ matches applyMatchesRhs -            -- ROMES:TODO: Node limit...-            -- ROMES:TODO: Actual Timeout... not just iteration timeout-            -- ROMES:TODO Better saturation (see Runner)-            -- Apply rewrites until saturated or ROMES:TODO: timeout-            unless (G.sizeNM afterMemo == G.sizeNM beforeMemo-                      && IM.size afterClasses == IM.size beforeClasses)-                (equalitySaturation'' (i+1) newStats)+      -- Restore the invariants once per iteration+      rebuild+      +      (afterMemo, afterClasses) <- gets (\g -> (g^._memo, g^._classes)) -        matchWithScheduler :: Database l -> Int -> IM.IntMap (Stat schd) -> (Int, Rewrite l) -> ([(Rewrite l, Match)], IM.IntMap (Stat schd))-        matchWithScheduler db i stats = \case-            (rw_id, rw :| cnd) -> first (map (first (:| cnd))) $ matchWithScheduler db i stats (rw_id, rw)-            (rw_id, lhs := rhs) -> do-                case IM.lookup rw_id stats of-                  -- If it's banned until some iteration, don't match this rule-                  -- against anything.-                  Just s | isBanned @schd i s -> ([], stats)+      -- ROMES:TODO: Node limit...+      -- ROMES:TODO: Actual Timeout... not just iteration timeout+      -- ROMES:TODO Better saturation (see Runner)+      -- Apply rewrites until saturated or ROMES:TODO: timeout+      unless (G.sizeNM afterMemo == G.sizeNM beforeMemo+                && IM.size afterClasses == IM.size beforeClasses)+          (runEqualitySaturation' (i+1) newStats) -                  -- Otherwise, match and update stats-                  x -> do+  matchWithScheduler :: Database l -> Int -> IM.IntMap (Stat schd) -> (Int, Rewrite l) -> ([(Rewrite l, Match)], IM.IntMap (Stat schd))+  matchWithScheduler db i stats = \case+      (rw_id, rw :| cnd) -> first (map (first (:| cnd))) $ matchWithScheduler db i stats (rw_id, rw)+      (rw_id, lhs := rhs) -> do+          case IM.lookup rw_id stats of+            -- If it's banned until some iteration, don't match this rule+            -- against anything.+            Just s | isBanned @schd i s -> ([], stats) -                      -- Match pattern-                      let matches' = ematch db lhs -- Add rewrite to the e-match substitutions+            -- Otherwise, match and update stats+            x -> do -                      -- Backoff scheduler: update stats-                      let newStats = updateStats @schd i rw_id x stats matches'+                -- Match pattern+                let matches' = ematch db lhs -- Add rewrite to the e-match substitutions -                      (map (lhs := rhs,) matches', newStats)+                -- Backoff scheduler: update stats+                let newStats = updateStats @schd i rw_id x stats matches' -        applyMatchesRhs :: (Rewrite l, Match) -> EGraphM l ()-        applyMatchesRhs =-            \case-                (rw :| cond, m@(Match subst _)) -> do-                    -- If the rewrite condition is satisfied, applyMatchesRhs on the rewrite rule.-                    egr <- get-                    when (cond subst egr) $-                       applyMatchesRhs (rw, m)+                (map (lhs := rhs,) matches', newStats) -                (_ := VariablePattern v, Match subst eclass) -> do-                    -- rhs is equal to a variable, simply merge class where lhs-                    -- pattern was found (@eclass@) and the eclass the pattern-                    -- variable matched (@lookup v subst@)-                    case IM.lookup v subst of-                      Nothing -> error "impossible: couldn't find v in subst"-                      Just n  -> do-                          _ <- merge n eclass-                          return ()+  applyMatchesRhs :: (Rewrite l, Match) -> EGraphM l ()+  applyMatchesRhs =+      \case+          (rw :| cond, m@(Match subst _)) -> do+              -- If the rewrite condition is satisfied, applyMatchesRhs on the rewrite rule.+              egr <- get+              when (cond subst egr) $+                 applyMatchesRhs (rw, m) -                (_ := NonVariablePattern rhs, Match subst eclass) -> do-                    -- rhs is (at the top level) a non-variable pattern, so substitute-                    -- all pattern variables in the pattern and create a new e-node (and-                    -- e-class that represents it), then merge the e-class of the-                    -- substituted rhs with the class that matched the left hand side-                    eclass' <- reprPat subst rhs-                    _ <- merge eclass eclass'+          (_ := VariablePattern v, Match subst eclass) -> do+              -- rhs is equal to a variable, simply merge class where lhs+              -- pattern was found (@eclass@) and the eclass the pattern+              -- variable matched (@lookup v subst@)+              case IM.lookup v subst of+                Nothing -> error "impossible: couldn't find v in subst"+                Just n  -> do+                    _ <- merge n eclass                     return () -        -- | Represent a pattern in the e-graph a pattern given substitions-        reprPat :: Subst -> l (Pattern l) -> EGraphM l ClassId-        reprPat subst = add . G.Node <=< traverse \case-            VariablePattern v ->-                case IM.lookup v subst of-                    Nothing -> error "impossible: couldn't find v in subst?"-                    Just i  -> return i-            NonVariablePattern p -> reprPat subst p-{-# SCC equalitySaturation' #-}+          (_ := NonVariablePattern rhs, Match subst eclass) -> do+              -- rhs is (at the top level) a non-variable pattern, so substitute+              -- all pattern variables in the pattern and create a new e-node (and+              -- e-class that represents it), then merge the e-class of the+              -- substituted rhs with the class that matched the left hand side+              eclass' <- reprPat subst rhs+              _ <- merge eclass eclass'+              return () +  -- | Represent a pattern in the e-graph a pattern given substitions+  reprPat :: Subst -> l (Pattern l) -> EGraphM l ClassId+  reprPat subst = add . Node <=< traverse \case+      VariablePattern v ->+          case IM.lookup v subst of+              Nothing -> error "impossible: couldn't find v in subst?"+              Just i  -> return i+      NonVariablePattern p -> reprPat subst p++{-# INLINEABLE runEqualitySaturation #-}
src/Data/Equality/Saturation/Scheduler.hs view
@@ -79,7 +79,6 @@           updateBans = \case             Nothing -> Just (BSS (i + ban_length) 1)             Just (BSS _ n)  -> Just (BSS (i + ban_length) (n+1))-    {-# SCC updateStats #-}      isBanned i s = i < bannedUntil s 
src/Data/Equality/Utils/IntToIntMap.hs view
@@ -78,7 +78,6 @@   | otherwise = find' k r find' k (Tip kx x) | isTrue# (k `eqWord#` kx) = x find' _ _ = error ("IntMap.!: key ___ is not an element of the map")-{-# SCC find' #-}  -- * Other stuff taken from IntMap 
+ src/Data/Equality/Utils/SizedList.hs view
@@ -0,0 +1,66 @@+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE TypeFamilies #-}+{-|+   Util: A list with a O(1) size function+ -}+module Data.Equality.Utils.SizedList where++import qualified Data.List+import GHC.Exts+import Data.Foldable++-- | A list with O(1) size access and O(1) conversion to normal list+data SList a = SList ![a] {-# UNPACK #-} !Int+  deriving Traversable++instance Semigroup (SList a) where+  (<>) (SList a i) (SList b j) = SList (a <> b) (i+j)+  {-# INLINE (<>) #-}++instance Monoid (SList a) where+  mempty = SList mempty 0+  {-# INLINE mempty #-}++instance Functor SList where+  fmap f (SList a i) = SList (fmap f a) i+  {-# INLINE fmap #-}++instance Foldable SList where+  fold       ( SList l _) = fold l+  foldMap f  ( SList l _) = foldMap f l+  foldMap' f ( SList l _) = foldMap' f l+  foldr f b  ( SList l _) = foldr f b l+  foldr' f b ( SList l _) = foldr' f b l+  foldl f b  ( SList l _) = foldl f b l+  foldl' f b ( SList l _) = foldl' f b l+  foldr1 f   ( SList l _) = foldr1 f l+  foldl1 f   ( SList l _) = foldl1 f l+  toList     ( SList l _) = l+  null       ( SList l _) = Data.List.null l+  length     ( SList _ i) = i+  elem x     ( SList l _) = x `elem` l+  maximum    ( SList l _) = maximum l+  minimum    ( SList l _) = minimum l+  sum        ( SList l _) = sum l+  product    ( SList l _) = product l++instance IsList (SList a) where+  type Item (SList a) = a+  fromList l          = SList l (length l)+  fromListN i l       = SList l i+  toList (SList l _)  = l++-- | Prepend an item to the list in O(1)+(|:) :: a -> SList a -> SList a+(|:) a (SList l i) = SList (a:l) (i+1)+{-# INLINE (|:) #-}++-- | Make a normal list from the sized list in O(1)+toListSL :: SList a -> [a]+toListSL (SList l _) = l+{-# INLINE toListSL #-}++-- | Get the size of the list in O(1)+sizeSL :: SList a -> Int+sizeSL (SList _ i) = i+{-# INLINE sizeSL #-}
+ test/Bench.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE OverloadedStrings #-}+import Test.Tasty.Bench++import Data.Equality.Utils+import Invariants+import Sym+import Lambda+import SimpleSym++tests :: [Benchmark]+tests = [ bgroup "Tests"+  [ symTests+  , lambdaTests+  , simpleSymTests+  , invariants+  ] ]++main :: IO ()+main = defaultMain tests
test/Invariants.hs view
@@ -25,6 +25,7 @@ import qualified Data.IntMap.Strict as IM  import Data.Equality.Graph.Monad as GM+import Data.Equality.Graph.Lens import Data.Equality.Graph import Data.Equality.Analysis import Data.Equality.Extraction@@ -52,12 +53,12 @@ patFoldAllClasses :: forall l. (Language l, Num (Pattern l))                   => Fix l -> Integer -> Bool patFoldAllClasses expr i =-    case IM.toList $ classes eg of+    case IM.toList $ (eg^._classes) of         [_] -> True         _   -> False     where         eg :: EGraph l-        eg = snd $ equalitySaturation expr [VariablePattern 1:=fromInteger i] (error "Cost function shouldn't be used")+        eg = snd $ equalitySaturation expr [VariablePattern 1:=fromInteger i] (error "Cost function shouldn't be used" :: CostFunction l Int)  -- | Test 'compileToQuery'. --@@ -97,7 +98,7 @@     let         db = eGraphToDatabase eg         matches = S.fromList $ map matchClassId $ ematch db (VariablePattern v)-        eclasses = S.fromList $ map fst $ IM.toList $ classes eg+        eclasses = S.fromList $ map fst $ IM.toList (eg^._classes)     in         matches == eclasses  @@ -122,18 +123,18 @@ -- ROMES:TODO Should I rebuild it here? Then the property test is that after rebuilding ...HashConsInvariant hashConsInvariant :: forall l. Language l                   => EGraph l -> Bool-hashConsInvariant eg@EGraph{..} =-    all f (IM.toList classes)+hashConsInvariant eg =+    all f (IM.toList (eg^._classes))     where       -- e-node 𝑛 ∈ 𝑀 [𝑎] ⇐⇒ 𝐻 [canonicalize(𝑛)] = find(𝑎)-      f (i, EClass _ nodes _ _) = all g nodes+      f (i, EClass{eClassNodes=nodes}) = all g nodes         where-          g en = case lookupNM (canonicalize en eg) memo of+          g en = case lookupNM (canonicalize en eg) (eg^._memo) of             Nothing -> error "how can we not find canonical thing in map? :)" -- False             Just i' -> i' == find i eg   benchSaturate :: forall l. Language l-              => [Rewrite l] -> (l Cost -> Cost) -> Fix l -> Bool+              => [Rewrite l] -> (l Int -> Int) -> Fix l -> Bool benchSaturate rws cost expr =     equalitySaturation expr rws cost `seq` True 
test/SimpleSym.hs view
@@ -38,7 +38,7 @@  instance Language SymExpr -cost :: CostFunction SymExpr+cost :: CostFunction SymExpr Int cost = \case   Const  _ -> 1   Symbol _ -> 1
test/Sym.hs view
@@ -20,6 +20,8 @@ import Data.Ord.Deriving import Text.Show.Deriving +import qualified Data.Foldable as F+ import Control.Applicative (liftA2) import Control.Monad (unless) @@ -76,7 +78,7 @@     (/) a b = Fix (BinOp Div a b)     fromRational = Fix . Const . fromRational -symCost :: Expr Cost -> Cost+symCost :: CostFunction Expr Int symCost = \case     BinOp Pow e1 e2 -> e1 + e2 + 6     BinOp Div e1 e2 -> e1 + e2 + 5@@ -110,11 +112,9 @@ instance Analysis Expr where     type Domain Expr = Maybe Double -    {-# SCC makeA #-}     makeA (Node e) egr = evalConstant ((\c -> egr^._class c._data) <$> e)      -- joinA = (<|>)-    {-# SCC joinA #-}     joinA ma mb = do         a <- ma         b <- mb@@ -124,7 +124,6 @@         !_ <- unless (a == b || (a == 0 && b == (-0)) || (a == (-0) && b == 0)) (error "Merged non-equal constants!")         return a -    {-# SCC modifyA #-}     modifyA i egr =         case egr ^._class i._data of           Nothing -> egr@@ -135,7 +134,7 @@             _     <- GM.merge i new_c              -- Prune all except leaf e-nodes-            modify (_class i._nodes %~ S.filter (null . children))+            modify (_class i._nodes %~ S.filter (F.null . unNode))   @@ -343,10 +342,16 @@     , testCase "i6" $         rewrite (_i (_ln "x") "x") @?= "x"*(_ln "x" + fromInteger(-1)) +    -- TODO: Require ability to fine tune parameters+    -- , testCase "diff_power_harder" $+    --     rewrite (_d "x" ((_pow "x" 3) - 7*(_pow "x" 2))) @?= "x"*(3*"x"-14)+     ] -_i :: Fix Expr -> Fix Expr -> Fix Expr+_i, _d, _pow :: Fix Expr -> Fix Expr -> Fix Expr _i a b = Fix (BinOp Integral a b)+_d a b = Fix (BinOp Diff a b)+_pow a b = Fix (BinOp Pow a b) _ln, _cos, _sin :: Fix Expr -> Fix Expr _ln a = Fix (UnOp Ln a) _cos a = Fix (UnOp Cos a)