packages feed

dual-tree 0.2.3.1 → 0.2.3.2

raw patch · 3 files changed

+206/−114 lines, 3 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Data.Tree.DUAL.Internal: instance (GHC.Base.Semigroup u, Data.Monoid.Action.Action d u) => GHC.Base.Semigroup (Data.Tree.DUAL.Internal.DUALTree d u a l)
- Data.Tree.DUAL.Internal: instance (GHC.Base.Semigroup u, Data.Monoid.Action.Action d u) => GHC.Base.Semigroup (Data.Tree.DUAL.Internal.DUALTreeU d u a l)
- Data.Tree.DUAL.Internal: instance (GHC.Classes.Eq u, GHC.Classes.Eq l, GHC.Classes.Eq d, GHC.Classes.Eq a) => GHC.Classes.Eq (Data.Tree.DUAL.Internal.DUALTreeNE d u a l)
- Data.Tree.DUAL.Internal: instance (GHC.Show.Show u, GHC.Show.Show l, GHC.Show.Show d, GHC.Show.Show a) => GHC.Show.Show (Data.Tree.DUAL.Internal.DUALTreeNE d u a l)
+ Data.Tree.DUAL.Internal: instance (Data.Monoid.Action.Action d u, GHC.Base.Semigroup u) => GHC.Base.Semigroup (Data.Tree.DUAL.Internal.DUALTree d u a l)
+ Data.Tree.DUAL.Internal: instance (Data.Monoid.Action.Action d u, GHC.Base.Semigroup u) => GHC.Base.Semigroup (Data.Tree.DUAL.Internal.DUALTreeU d u a l)
+ Data.Tree.DUAL.Internal: instance (GHC.Classes.Eq l, GHC.Classes.Eq u, GHC.Classes.Eq d, GHC.Classes.Eq a) => GHC.Classes.Eq (Data.Tree.DUAL.Internal.DUALTreeNE d u a l)
+ Data.Tree.DUAL.Internal: instance (GHC.Show.Show l, GHC.Show.Show u, GHC.Show.Show d, GHC.Show.Show a) => GHC.Show.Show (Data.Tree.DUAL.Internal.DUALTreeNE d u a l)

Files

CHANGES view
@@ -1,3 +1,36 @@+* 0.2.3.2: 28 March 2026++- Fix [#16](https://github.com/diagrams/dual-tree/issues/16): Contrary+  to the documentation, `sconcat` often produced deeply nested trees,+  rather than flat ones; the problem is that `sconcat` in auto-derived+  `Semigroup` instances performs a fold with `(<>)`.  This version+  manually reimplements the relevant `Semigroup` instances so that+  `sconcat` now results in flat trees.  There is no semantic+  difference, but this could conceivably lead to better performance in+  cases where `sconcat` is being called on large lists of+  trees. Thanks to [John Z.](https://github.com/machinedgod) for+  reporting the issue.++* 0.2.3.1-r5: 13 May 2025++- Allow monoid-extras-0.7++* 0.2.3.1-r4: 29 Jan 2025++- Allow base-4.21 and test with GHC 9.12++* 0.2.3.1-r3: 18 May 2023++- Allow base-4.20 and test with GHC 9.10++* 0.2.3.1-r2: 21 Oct 2023++- Allow base-4.19 and test with GHC 9.8++* 0.2.3.1-r1: 8 May 2023++- Allow base-4.18 and test with GHC 9.6+ * 0.2.3.1: 17 Aug 2022  - Allow base-4.17 and test with GHC 9.4
dual-tree.cabal view
@@ -1,5 +1,5 @@ name:                dual-tree-version:             0.2.3.1+version:             0.2.3.2 synopsis:            Rose trees with cached and accumulating monoidal annotations description:         Rose (n-ary) trees with both upwards- (/i.e./                      cached) and downwards-traveling (/i.e./@@ -31,7 +31,7 @@ build-type:          Simple cabal-version:       >=1.10 bug-reports:         https://github.com/diagrams/dual-tree/issues-tested-with:         GHC ==8.4.4 || ==8.6.5 || ==8.8.4 || ==8.10.7 || ==9.0.2 || ==9.2.4 || ==9.4.1+tested-with:         GHC ==8.4.4 || ==8.6.5 || ==8.8.4 || ==8.10.7 || ==9.0.2 || ==9.2.8 || ==9.4.8 || ==9.6.5 || ==9.8.2 || ==9.10.1 || ==9.12.1 source-repository head   type: git   location: https://github.com/diagrams/dual-tree.git@@ -40,9 +40,9 @@   default-language:  Haskell2010   exposed-modules:   Data.Tree.DUAL                      Data.Tree.DUAL.Internal-  build-depends:     base >= 4.3 && < 4.18,+  build-depends:     base >= 4.3 && < 4.22,                      semigroups >= 0.8 && < 0.21,-                     monoid-extras >= 0.6 && < 0.7+                     monoid-extras >= 0.6 && < 0.8   hs-source-dirs:    src   other-extensions:  GeneralizedNewtypeDeriving,                      MultiParamTypeClasses,
src/Data/Tree/DUAL/Internal.hs view
@@ -1,12 +1,11 @@-{-# LANGUAGE DeriveDataTypeable         #-}-{-# LANGUAGE DeriveFunctor              #-}-{-# LANGUAGE FlexibleContexts           #-}-{-# LANGUAGE FlexibleInstances          #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE MultiParamTypeClasses      #-}-{-# LANGUAGE TypeFamilies               #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeFamilies #-} ------------------------------------------------------------------------------ -- | -- Module      :  Data.Tree.DUAL.Internal -- Copyright   :  (c) 2011-2012 Brent Yorgey@@ -30,33 +29,42 @@ -- type system enforces the invariant that there is only one -- representation for the empty tree.  It also allows us to get away -- with only 'Semigroup' constraints in many places.-----------------------------------------------------------------------------------module Data.Tree.DUAL.Internal-       (-         -- * DUAL-trees-         DUALTreeNE(..), DUALTreeU(..), DUALTree(..)--         -- * Constructing DUAL-trees-       , empty, leaf, leafU, annot, applyD+module Data.Tree.DUAL.Internal (+  -- * DUAL-trees+  DUALTreeNE (..),+  DUALTreeU (..),+  DUALTree (..), -         -- * Modifying DUAL-trees-       , applyUpre, applyUpost-       , mapUNE, mapUU, mapU+  -- * Constructing DUAL-trees+  empty,+  leaf,+  leafU,+  annot,+  applyD, -         -- * Accessors and eliminators-       , nonEmpty, getU, foldDUALNE, foldDUAL, flatten+  -- * Modifying DUAL-trees+  applyUpre,+  applyUpost,+  mapUNE,+  mapUU,+  mapU, -       ) where+  -- * Accessors and eliminators+  nonEmpty,+  getU,+  foldDUALNE,+  foldDUAL,+  flatten,+) where -import           Control.Arrow      ((***))-import           Data.List.NonEmpty (NonEmpty (..))+import Control.Arrow ((***))+import qualified Data.Functor as F+import Data.List.NonEmpty (NonEmpty (..)) import qualified Data.List.NonEmpty as NEL-import           Data.Maybe         (fromMaybe)-import           Data.Monoid.Action-import           Data.Semigroup-import           Data.Typeable+import Data.Maybe (catMaybes, fromMaybe)+import Data.Monoid.Action+import Data.Semigroup+import Data.Typeable  ------------------------------------------------------------ -- DUALTreeNE@@ -64,27 +72,33 @@  -- | /Non-empty/ DUAL-trees. data DUALTreeNE d u a l-  = Leaf   u l        -- ^ Leaf with data value and @u@ annotation-  | LeafU  u          -- ^ Leaf with only @u@ annotation-  | Concat (NonEmpty (DUALTreeU d u a l))-                      -- ^ n-way branch, containing a /non-empty/ list-                      --   of subtrees.-  | Act    d (DUALTreeU d u a l)-                      -- ^ @d@ annotation-  | Annot  a (DUALTreeU d u a l)-                      -- ^ Internal data value+  = -- | Leaf with data value and @u@ annotation+    Leaf u l+  | -- | Leaf with only @u@ annotation+    LeafU u+  | -- | n-way branch, containing a /non-empty/ list+    --   of subtrees.+    Concat (NonEmpty (DUALTreeU d u a l))+  | -- | @d@ annotation+    Act d (DUALTreeU d u a l)+  | -- | Internal data value+    Annot a (DUALTreeU d u a l)   deriving (Functor, Typeable, Show, Eq) +-- | The @Semigroup@ instance is carefully crafted so that 'sconcat'+--   creates a flat, rather than deeply nested, tree. instance (Action d u, Semigroup u) => Semigroup (DUALTreeNE d u a l) where-  t1 <> t2   = sconcat (NEL.fromList [t1,t2])-  sconcat    = Concat . NEL.map pullU+  t1 <> t2 = sconcat (NEL.fromList [t1, t2])+  sconcat = Concat . NEL.map pullU -newtype DAct d = DAct { unDAct :: d }+newtype DAct d = DAct {unDAct :: d} -instance (Semigroup d, Semigroup u, Action d u)-    => Action (DAct d) (DUALTreeNE d u a l) where+instance+  (Semigroup d, Semigroup u, Action d u) =>+  Action (DAct d) (DUALTreeNE d u a l)+  where   act (DAct d) (Act d' t) = Act (d <> d') t-  act (DAct d) t          = Act d (pullU t)+  act (DAct d) t = Act d (pullU t)  ------------------------------------------------------------ -- DUALTreeU@@ -92,24 +106,39 @@  -- | A non-empty DUAL-tree paired with a cached @u@ value.  These --   should never be constructed directly; instead, use 'pullU'.-newtype DUALTreeU d u a l = DUALTreeU { unDUALTreeU :: (u, DUALTreeNE d u a l) }-  deriving (Functor, Semigroup, Typeable, Show, Eq)+newtype DUALTreeU d u a l = DUALTreeU {unDUALTreeU :: (u, DUALTreeNE d u a l)}+  deriving (Functor, Typeable, Show, Eq) -overDUALTreeU-  :: ((u, DUALTreeNE d u a l) -> (u', DUALTreeNE d u' a l))-  -> DUALTreeU d u a l -> DUALTreeU d u' a l+-- | The @Semigroup@ instance is carefully crafted so that 'sconcat'+--   creates a flat, rather than deeply nested, tree.  Simply+--   auto-deriving the Semigroup instance produces an 'sconcat'+--   implementation which calls '(<>)' repeatedly, which is not what+--   we want.  Frustratingly, manually defining @sconcat =+--   DUALTreeU . sconcat . fmap unDUALTreeU@ does not work either,+--   since it ends up using 'sconcat' for pairs, which still turns+--   into nested calls to '(<>)'.+instance (Action d u, Semigroup u) => Semigroup (DUALTreeU d u a l) where+  DUALTreeU t1 <> DUALTreeU t2 = DUALTreeU (t1 <> t2)+  sconcat = DUALTreeU . (sconcat *** sconcat) . F.unzip . fmap unDUALTreeU++overDUALTreeU ::+  ((u, DUALTreeNE d u a l) -> (u', DUALTreeNE d u' a l)) ->+  DUALTreeU d u a l ->+  DUALTreeU d u' a l overDUALTreeU f = DUALTreeU . f . unDUALTreeU -instance (Semigroup d, Semigroup u, Action d u)-    => Action (DAct d) (DUALTreeU d u a l) where+instance+  (Semigroup d, Semigroup u, Action d u) =>+  Action (DAct d) (DUALTreeU d u a l)+  where   act d = overDUALTreeU (act (unDAct d) *** act d)  -- | \"Pull\" the root @u@ annotation out into a tuple. pullU :: (Semigroup u, Action d u) => DUALTreeNE d u a l -> DUALTreeU d u a l-pullU t@(Leaf u _)                   = DUALTreeU (u, t)-pullU t@(LeafU u)                    = DUALTreeU (u, t)-pullU t@(Concat ts)                  = DUALTreeU (sconcat . NEL.map (fst . unDUALTreeU) $ ts, t)-pullU t@(Act d (DUALTreeU (u,_)))    = DUALTreeU (act d u, t)+pullU t@(Leaf u _) = DUALTreeU (u, t)+pullU t@(LeafU u) = DUALTreeU (u, t)+pullU t@(Concat ts) = DUALTreeU (sconcat . NEL.map (fst . unDUALTreeU) $ ts, t)+pullU t@(Act d (DUALTreeU (u, _))) = DUALTreeU (act d u, t) pullU t@(Annot _ (DUALTreeU (u, _))) = DUALTreeU (u, t)  ------------------------------------------------------------@@ -140,26 +169,41 @@ --     of subtrees \"does not matter\". -- --   * 'Monoid'. The identity is the empty tree.+newtype DUALTree d u a l = DUALTree {unDUALTree :: Maybe (DUALTreeU d u a l)}+  deriving (Functor, Typeable, Show, Eq) -newtype DUALTree d u a l = DUALTree { unDUALTree :: Maybe (DUALTreeU d u a l) }-  deriving ( Functor, Semigroup, Typeable, Show, Eq )+-- | The @Semigroup@ instance is carefully crafted so that 'sconcat'+--   creates a flat, rather than deeply nested, tree.  Simply+--   auto-deriving the Semigroup instance produces an 'sconcat'+--   implementation which calls '(<>)' repeatedly, which is not what+--   we want.  Manually defining @sconcat = DUALTree . sconcat . fmap+--   unDUALTreeU@ does not even work either, since it ends up using+--   'sconcat' for 'Maybe', which still turns into nested calls to+--   '(<>)'.+instance (Action d u, Semigroup u) => Semigroup (DUALTree d u a l) where+  DUALTree t1 <> DUALTree t2 = DUALTree (t1 <> t2) -overDUALTree-  :: (Maybe (DUALTreeU d u a l) -> Maybe (DUALTreeU d u' a l))-  -> DUALTree d u a l -> DUALTree d u' a l+  sconcat = DUALTree . fmap sconcat . NEL.nonEmpty . catMaybes . NEL.toList . fmap unDUALTree++overDUALTree ::+  (Maybe (DUALTreeU d u a l) -> Maybe (DUALTreeU d u' a l)) ->+  DUALTree d u a l ->+  DUALTree d u' a l overDUALTree f = DUALTree . f . unDUALTree  instance (Semigroup u, Action d u) => Monoid (DUALTree d u a l) where-  mempty  = DUALTree mempty+  mempty = DUALTree mempty   mappend = (<>)-  mconcat []     = mempty-  mconcat (x:xs) = sconcat (x :| xs)+  mconcat [] = mempty+  mconcat (x : xs) = sconcat (x :| xs)  -- | Apply a @d@ annotation at the root of a tree.  Semantically, all --   @u@ annotations are transformed by the action of @d@, although --   operationally @act@ incurs only a constant amount of work.-instance (Semigroup d, Semigroup u, Action d u)-    => Action (DAct d) (DUALTree d u a l) where+instance+  (Semigroup d, Semigroup u, Action d u) =>+  Action (DAct d) (DUALTree d u a l)+  where   act = overDUALTree . fmap . act  ------------------------------------------------------------@@ -200,8 +244,9 @@  -- | Apply a @d@ annotation at the root of a tree, transforming all --   @u@ annotations by the action of @d@.-applyD :: (Semigroup d, Semigroup u, Action d u)-       => d -> DUALTree d u a l -> DUALTree d u a l+applyD ::+  (Semigroup d, Semigroup u, Action d u) =>+  d -> DUALTree d u a l -> DUALTree d u a l applyD = act . DAct  -- | Decompose a DUAL-tree into either @Nothing@ (if empty) or a@@ -226,10 +271,10 @@ --   with the action of @d@) over all the @u@ annotations in a non-empty --   DUAL-tree. mapUNE :: (u -> u') -> DUALTreeNE d u a l -> DUALTreeNE d u' a l-mapUNE f (Leaf u l)  = Leaf (f u) l-mapUNE f (LeafU u)   = LeafU (f u)+mapUNE f (Leaf u l) = Leaf (f u) l+mapUNE f (LeafU u) = LeafU (f u) mapUNE f (Concat ts) = Concat ((NEL.map . mapUU) f ts)-mapUNE f (Act d t)   = Act d (mapUU f t)+mapUNE f (Act d t) = Act d (mapUU f t) mapUNE f (Annot a t) = Annot a (mapUU f t)  -- | Map a function (which must be a monoid homomorphism, and commute@@ -248,7 +293,6 @@ --     * @f (u1 \<\> u2) == f u1 \<\> f u2@ -- --     * @f (act d u) == act d (f u)@--- mapU :: (u -> u') -> DUALTree d u a l -> DUALTree d u' a l mapU = overDUALTree . fmap . mapUU @@ -257,25 +301,32 @@ ------------------------------------------------------------  -- | Fold for non-empty DUAL-trees.-foldDUALNE :: (Semigroup d, Monoid d)-           => (d -> l -> r) -- ^ Process a leaf datum along with the-                            --   accumulation of @d@ values along the-                            --   path from the root-           -> r             -- ^ Replace @LeafU@ nodes-           -> (NonEmpty r -> r)  -- ^ Combine results at a branch node-           -> (d -> r -> r)      -- ^ Process an internal d node-           -> (a -> r -> r)      -- ^ Process an internal datum-           -> DUALTreeNE d u a l -> r-foldDUALNE  = foldDUALNE' Nothing-  where-    foldDUALNE' dacc lf _   _   _    _   (Leaf _ l)  = lf (fromMaybe mempty dacc) l-    foldDUALNE' _    _  lfU _   _    _   (LeafU _)   = lfU-    foldDUALNE' dacc lf lfU con down ann (Concat ts)-      = con (NEL.map (foldDUALNE' dacc lf lfU con down ann . snd . unDUALTreeU) ts)-    foldDUALNE' dacc lf lfU con down ann (Act d t)-      = down d (foldDUALNE' (dacc <> Just d) lf lfU con down ann . snd . unDUALTreeU $ t)-    foldDUALNE' dacc lf lfU con down ann (Annot a t)-      = ann a (foldDUALNE' dacc lf lfU con down ann . snd . unDUALTreeU $ t)+foldDUALNE ::+  (Semigroup d, Monoid d) =>+  -- | Process a leaf datum along with the+  --   accumulation of @d@ values along the+  --   path from the root+  (d -> l -> r) ->+  -- | Replace @LeafU@ nodes+  r ->+  -- | Combine results at a branch node+  (NonEmpty r -> r) ->+  -- | Process an internal d node+  (d -> r -> r) ->+  -- | Process an internal datum+  (a -> r -> r) ->+  DUALTreeNE d u a l ->+  r+foldDUALNE = foldDUALNE' Nothing+ where+  foldDUALNE' dacc lf _ _ _ _ (Leaf _ l) = lf (fromMaybe mempty dacc) l+  foldDUALNE' _ _ lfU _ _ _ (LeafU _) = lfU+  foldDUALNE' dacc lf lfU con down ann (Concat ts) =+    con (NEL.map (foldDUALNE' dacc lf lfU con down ann . snd . unDUALTreeU) ts)+  foldDUALNE' dacc lf lfU con down ann (Act d t) =+    down d (foldDUALNE' (dacc <> Just d) lf lfU con down ann . snd . unDUALTreeU $ t)+  foldDUALNE' dacc lf lfU con down ann (Annot a t) =+    ann a (foldDUALNE' dacc lf lfU con down ann . snd . unDUALTreeU $ t)  -- | Fold for DUAL-trees. It is given access to the internal and leaf --   data, internal @d@ values, and the accumulated @d@ values at each@@ -292,28 +343,36 @@ --   along the path from the root to the leaf. -- --   The result is @Nothing@ if and only if the tree is empty.-foldDUAL :: (Semigroup d, Monoid d)-         => (d -> l -> r)          -- ^ Process a leaf datum along with the-                                   --   accumulation of @d@ values along the-                                   --   path from the root-         -> r                      -- ^ Replace @u@-only nodes-         -> (NonEmpty r -> r)      -- ^ Combine results at a branch node-         -> (d -> r -> r)          -- ^ Process an internal d node-         -> (a -> r -> r)          -- ^ Process an internal datum-         -> DUALTree d u a l -> Maybe r-foldDUAL _ _ _ _ _ (DUALTree Nothing)-  = Nothing-foldDUAL l u c d a (DUALTree (Just (DUALTreeU (_, t))))-  = Just $ foldDUALNE l u c d a t+foldDUAL ::+  (Semigroup d, Monoid d) =>+  -- | Process a leaf datum along with the+  --   accumulation of @d@ values along the+  --   path from the root+  (d -> l -> r) ->+  -- | Replace @u@-only nodes+  r ->+  -- | Combine results at a branch node+  (NonEmpty r -> r) ->+  -- | Process an internal d node+  (d -> r -> r) ->+  -- | Process an internal datum+  (a -> r -> r) ->+  DUALTree d u a l ->+  Maybe r+foldDUAL _ _ _ _ _ (DUALTree Nothing) =+  Nothing+foldDUAL l u c d a (DUALTree (Just (DUALTreeU (_, t)))) =+  Just $ foldDUALNE l u c d a t  -- | A specialized fold provided for convenience: flatten a tree into --   a list of leaves along with their @d@ annotations, ignoring --   internal data values. flatten :: (Semigroup d, Monoid d) => DUALTree d u a l -> [(l, d)]-flatten = fromMaybe []-        . foldDUAL-            (\d l -> [(l, d)])-            []-            (concat . NEL.toList)-            (\_ x -> x)-            (const id)+flatten =+  fromMaybe []+    . foldDUAL+      (\d l -> [(l, d)])+      []+      (concat . NEL.toList)+      (\_ x -> x)+      (const id)