on b97cd4e help sign in
rel8/src/Rel8/Tabulate.hs
231 lines 5.8 kB history raw
1{-# language DeriveFunctor #-}
2{-# language DerivingStrategies #-}
3{-# language FlexibleContexts #-}
4{-# language TupleSections #-}
6module Rel8.Tabulate
7 ( Tabulation
8 , runTabulation
9 , tabulate
10 , tabulateA
11 , fromQuery
12 , liftQuery
13 , prebind
14 , postbind
15 , indexed
16 , ifilter
17 , lookup
18 , align
19 , alignWith
20 , leftAlign
21 , leftAlignWith
22 , zip
23 , zipWith
24 , similarity
25 , difference
26 , aggregateTabulation
27 , optionalTabulation
28 )
29where
31-- base
32import Control.Applicative ( liftA2 )
33import Control.Monad ( join, liftM2 )
34import Data.Bifunctor ( bimap, first )
35import Data.Foldable ( traverse_ )
36import Data.Maybe ( fromMaybe )
37import Prelude hiding ( filter, lookup, undefined, zip, zipWith )
39-- rel8
40import Rel8.Aggregate ( Aggregate )
41import Rel8.Expr ( Expr )
42import Rel8.Expr.Bool ( not_, true )
43import Rel8.Query ( Query )
44import Rel8.Query.Aggregate ( aggregate )
45import Rel8.Query.Exists ( withBy, withoutBy )
46import Rel8.Query.Filter ( filter, where_ )
47import Rel8.Query.Maybe ( optional )
48import Rel8.Query.These ( alignBy )
49import Rel8.Table ( Table )
50import Rel8.Table.Aggregate ( groupBy )
51import Rel8.Table.Eq ( EqTable, (==:) )
52import Rel8.Table.Maybe ( MaybeTable )
53import Rel8.Table.These ( TheseTable, theseTable )
54import Rel8.Table.Undefined ( undefined )
56-- semigroupoids
57import Data.Functor.Apply ( Apply, liftF2 )
58import Data.Functor.Bind ( Bind )
59import qualified Data.Functor.Bind
60import Data.Semigroup.Traversable.Class ( bitraverse1 )
63newtype Tabulation k a = Tabulation (k -> Query (Maybe k, a))
64 deriving stock Functor
67instance EqTable k => Apply (Tabulation k) where
68 liftF2 = liftA2
71instance EqTable k => Applicative (Tabulation k) where
72 pure = liftQuery . pure
73 liftA2 = liftM2
76instance EqTable k => Bind (Tabulation k) where
77 join = join
80instance EqTable k => Monad (Tabulation k) where
81 Tabulation as >>= f = Tabulation $ \i -> do
82 (mk, a) <- as i
83 case mk of
84 Nothing -> case f a of
85 Tabulation bs -> bs i
86 Just k -> case f a of
87 Tabulation bs -> do
88 (mk', b) <- bs k
89 case mk' of
90 Nothing -> pure (mk, b)
91 Just k' -> do
92 where_ $ k ==: k'
93 pure (mk', b)
96runTabulation :: EqTable k => Tabulation k a -> Query (k, a)
97runTabulation (Tabulation f) = do
98 (mk, a) <- f i
99 case mk of
100 Nothing -> do
101 -- Opaleye tries to be too clever optimising "WHERE FALSE" and generates
102 -- invalid SQL, so we use "WHERE NOT TRUE" instead
103 where_ $ not_ true
104 pure (i, a)
105 Just k -> pure (k, a)
106 where
107 i = undefined
110liftQuery :: Query a -> Tabulation k a
111liftQuery query = Tabulation $ const $ fmap (Nothing,) query
114tabulate :: (a -> k) -> a -> Tabulation k a
115tabulate key a = fromQuery $ pure (key a, a)
118tabulateA :: (a -> Query k) -> a -> Tabulation k a
119tabulateA key a = fromQuery $ (,a) <$> key a
122fromQuery :: Query (k, a) -> Tabulation k a
123fromQuery = Tabulation . const . fmap (first Just)
126indexed :: Tabulation k a -> Tabulation k (k, a)
127indexed (Tabulation query) = Tabulation $ \i ->
128 (\(mk, a) -> (mk, (fromMaybe i mk, a))) <$> query i
131ifilter :: (k -> a -> Expr Bool) -> Tabulation k a -> Tabulation k a
132ifilter f tabulation = snd <$> do
133 filter (uncurry f) `postbind` indexed tabulation
136prebind :: (a -> Tabulation k b) -> Query a -> Tabulation k b
137prebind f as = Tabulation $ \k -> do
138 a <- as
139 case f a of
140 Tabulation query -> query k
141infixr 1 `prebind`
144postbind :: (a -> Query b) -> Tabulation k a -> Tabulation k b
145postbind f (Tabulation as) = Tabulation $ \i -> do
146 (k, a) <- as i
147 case f a of
148 bs -> do
149 b <- bs
150 pure (k, b)
151infixr 1 `postbind`
154lookup :: EqTable k => k -> Tabulation k a -> Query a
155lookup key (Tabulation query) = do
156 (mk, a) <- query key
157 traverse_ (where_ . (key ==:)) mk
158 pure a
161align :: (EqTable k, Table Expr a, Table Expr b)
162 => Tabulation k a -> Tabulation k b -> Tabulation k (TheseTable a b)
163align = alignWith id
166alignWith :: (EqTable k, Table Expr a, Table Expr b)
167 => (TheseTable a b -> c)
168 -> Tabulation k a -> Tabulation k b -> Tabulation k c
169alignWith f kas kbs = do
170 as <- toQuery kas
171 bs <- toQuery kbs
172 fromQuery $ do
173 tkab <- alignBy (\(l, _) (m, _) -> l ==: m) as bs
174 let
175 k' = theseTable fst fst (const fst) tkab
176 tab = bimap snd snd tkab
177 pure (k', f tab)
180leftAlign :: EqTable k
181 => Tabulation k a -> Tabulation k b -> Tabulation k (a, MaybeTable b)
182leftAlign = leftAlignWith (,)
185leftAlignWith :: EqTable k
186 => (a -> MaybeTable b -> c)
187 -> Tabulation k a -> Tabulation k b -> Tabulation k c
188leftAlignWith f left right = liftA2 f left (optionalTabulation right)
191zip :: EqTable k
192 => Tabulation k a -> Tabulation k b -> Tabulation k (a, b)
193zip = zipWith (,)
196zipWith :: EqTable k
197 => (a -> b -> c) -> Tabulation k a -> Tabulation k b -> Tabulation k c
198zipWith = liftA2
201similarity :: EqTable k => Tabulation k a -> Tabulation k b -> Tabulation k a
202similarity kas kbs = do
203 as <- toQuery kas
204 bs <- toQuery kbs
205 fromQuery $ as >>= withBy (\(k, _) (l, _) -> k ==: l) bs
208difference :: EqTable k => Tabulation k a -> Tabulation k b -> Tabulation k a
209difference kas kbs = do
210 as <- toQuery kas
211 bs <- toQuery kbs
212 fromQuery $ as >>= withoutBy (\(k, _) (l, _) -> k ==: l) bs
215aggregateTabulation :: EqTable k
216 => Tabulation k (Aggregate a) -> Tabulation k a
217aggregateTabulation kas = do
218 as <- toQuery kas
219 fromQuery $ aggregate $ bitraverse1 groupBy id <$> as
222optionalTabulation :: EqTable k
223 => Tabulation k a -> Tabulation k (MaybeTable a)
224optionalTabulation as = Tabulation $ \k -> do
225 ma <- optional $ lookup k as
226 pure (Just k, ma)
229toQuery :: Tabulation k a -> Tabulation k (Query (k, a))
230toQuery (Tabulation as) = Tabulation $ \k ->
231 pure (Nothing, first (fromMaybe k) <$> as k)