1 {-# LANGUAGE NoRebindableSyntax #-}
2 {-# OPTIONS_GHC -Wno-orphans #-}
4 module Literate.Accounting.Chart where
6 import Control.Applicative (Applicative (..))
7 import Control.DeepSeq (NFData (..))
8 import Control.Monad (Monad (..))
9 import Control.Monad.Trans.Writer qualified as MT
11 import Data.Eq (Eq (..))
12 import Data.Foldable (Foldable (..), all)
13 import Data.Function (const, flip, ($), (.))
14 import Data.Functor (Functor (..), (<$>))
15 import Data.List qualified as List
16 import Data.List.NonEmpty (NonEmpty (..))
17 import Data.List.NonEmpty qualified as NonEmpty
18 import Data.Map.Strict (Map)
19 import Data.Map.Strict qualified as Map
20 import Data.Maybe (Maybe (..), isNothing)
21 import Data.Monoid (Monoid (..))
22 import Data.Ord (Ord (..))
23 import Data.Semigroup (Semigroup (..))
24 import Data.String (String)
25 import Data.Traversable (Traversable (..))
26 import Data.Tuple (fst, snd)
27 import GHC.Exts qualified as GHC
28 import Text.Show (Show (..))
30 import Literate.Accounting.Math
33 newtype Chart k a = Chart {unChart :: Map.Map k (a, Chart k a)}
34 deriving newtype (Eq, NFData)
35 instance (Show k, Show a) => Show (Chart k a) where
36 show = List.unlines . drawMap
38 drawNode :: (k, (a, Chart k a)) -> [String]
39 drawNode (k, (a, ts0)) =
40 List.zipWith (<>) (List.lines (show k)) (" " <> show a : List.repeat "")
42 drawMap = go . Map.toList . unChart
45 go [t] = shift "` " " " (drawNode t)
46 go (t : ts) = shift "+ " "| " (drawNode t) <> go ts
47 shift ind0 ind = List.zipWith (<>) (ind0 : List.repeat ind)
48 instance Functor (Chart k) where
49 fmap f = Chart . fmap (\(a, ch) -> (f a, fmap f ch)) . unChart
50 instance Foldable (Chart k) where
51 foldMap f = foldMap (\(a, ch) -> f a <> foldMap f ch) . unChart
52 instance Traversable (Chart k) where
55 . traverse (\(a, ch) -> (,) <$> f a <*> traverse f ch)
57 instance (Semigroup a, Ord k) => Semigroup (Chart k a) where
61 (\new old -> (fst old <> fst new, snd old <> snd new))
64 instance (Semigroup a, Ord k) => Monoid (Chart k a) where
65 mempty = Chart Map.empty
66 instance (Ord k, Addable a) => Addable (Chart k a) where
70 (\(ym, ya) (xm, xa) -> (xm + ym, xa + ya))
73 instance (Ord k, Subable a) => Subable (Chart k a) where
77 (\(ym, ya) (xm, xa) -> (xm - ym, xa - ya))
81 -- ** Type 'ChartPath'
82 type ChartPath = NonEmpty.NonEmpty
85 type Account = ChartPath
87 --newtype Account acct = Account (NonEmpty acct)
89 {- | @('insert' merge path value chart)@
90 returns the given @chart@ with a mapping from @path@ to @value@,
91 using @merge value oldValue@ in case @path@ already map to an @oldValue@.
93 insert :: Ord k => a -> (a -> a -> a) -> ChartPath k -> a -> Chart k a -> Chart k a
94 insert init merge p a ch = go ch p
100 (\_new (old, c) -> (merge a old, c))
107 (\_new (old, c) -> (old, go c (k1 :| ks)))
109 (init, go empty (k1 :| ks))
112 -- | Return the value (if any) associated with the given 'Path'.
113 lookup :: Ord k => ChartPath k -> Chart k a -> Maybe a
114 lookup (k :| ks) (Chart m) = do
115 (a, ms) <- Map.lookup k m
118 (k' : ks') -> lookup (k' :| ks') ms
120 {- | @('filter' predicate chart)@ returns the given @chart@
121 with only the paths whose mappings satisfied the given @predicate@.
123 filter :: Ord k => (a -> Bool) -> Chart k a -> Chart k (Maybe a)
129 in let fm = filter f m
130 in if not fx && all isNothing fm
132 else Just (if fx then Just x else Nothing, fm)
136 -- | The empty 'Chart'.
138 empty = Chart Map.empty
140 {- | @('singleton' ancestorsValue path leafValue)@
141 returns a 'Chart' mapping @path@ to @leafValue@
142 and mapping all its ancestors paths to @ancestorsValue@.
144 singleton :: Ord k => a -> ChartPath k -> a -> Chart k a
145 singleton init ks a = insert init const ks a empty
147 {- | Return a 'Map' associating each 'ChartPath' in the given 'Chart',
148 with its value mapped by the given function.
150 flatten :: Ord k => (x -> y) -> Chart k x -> Map (ChartPath k) y
151 flatten = flattenWithPath . const
153 flattenWithPath :: Ord k => ([k] -> x -> y) -> Chart k x -> Map (ChartPath k) y
154 flattenWithPath = go []
159 (NonEmpty.reverse . flip (:|) p)
161 ( \k (a, _children) ->
162 f (List.reverse (k : p)) a
167 (\k (_a, children) -> (go (k : p) f children :))
171 mapByDepthFirst :: Ord k => (Chart k b -> a -> b) -> Chart k a -> Chart k b
175 (\(a, ch) -> let m = mapByDepthFirst f ch in (f m a, m))
178 foldrPath :: Ord k => (ChartPath k -> a -> acc -> acc) -> ChartPath k -> Chart k a -> acc -> acc
179 foldrPath f = go [] . NonEmpty.toList
182 go p (k : ks) (Chart m) acc =
183 case Map.lookup k m of
184 Just (a, ch) -> f (NonEmpty.reverse (k :| p)) a $ go (k : p) ks ch acc
187 foldrWithPath :: Ord k => (ChartPath k -> a -> acc -> acc) -> acc -> Chart k a -> acc
188 foldrWithPath f = go []
194 (NonEmpty.reverse (k :| p))
202 -- | A 'Monad' to construct a 'Chart'.
203 newtype ChartM k v m a = ChartM
205 MT.WriterT (v, Chart k v) m a
207 deriving newtype (Functor, Applicative, Monad)
209 runChartM :: Monad m => ChartM k v m a -> m (Chart k v)
211 (_a, (_v, ch)) <- MT.runWriterT (unChartM chM)
214 instance (Ord k, Monoid v, Monad m) => Semigroup (ChartM k v m a) where
215 (<>) = (Control.Monad.>>)
216 instance (Ord k, Monoid v, Monad m, Monoid a) => Monoid (ChartM k v m a) where
217 mempty = return mempty
224 GHC.IsList (ChartM k v m a)
226 type Item (ChartM k v m a) = ChartM k v m a
228 fromListN _n = mconcat