diff options
Diffstat (limited to 'provider')
-rw-r--r-- | provider/posts/beuteltier-2.lhs | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/provider/posts/beuteltier-2.lhs b/provider/posts/beuteltier-2.lhs new file mode 100644 index 0000000..bca4c86 --- /dev/null +++ b/provider/posts/beuteltier-2.lhs | |||
@@ -0,0 +1,173 @@ | |||
1 | --- | ||
2 | title: "Type level" utilities for an overly complicated feedreader | ||
3 | published: 2015-08-05 | ||
4 | tags: Beuteltier | ||
5 | --- | ||
6 | |||
7 | By popular (n=1) demand we will, in this post, be taking a look at | ||
8 | `beuteltier/Beuteltier/Types/Util.hs` the, creatively named, module providing some "type | ||
9 | level" utilities. | ||
10 | |||
11 | What I mean when I say "type level" is: additional instances (placed here when they | ||
12 | contain major design decisions and are not "Ord" or "Eq"), utilities not connected to | ||
13 | beuteltier itself (like the different flavours of `alter` below) | ||
14 | |||
15 | In contrast to the first, this post is straightforward enough to be read linearly. | ||
16 | |||
17 | > {-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-} | ||
18 | > | ||
19 | > module Beuteltier.Types.Util | ||
20 | > ( -- * Constructing structures | ||
21 | > construct | ||
22 | > , construct' | ||
23 | > , alter | ||
24 | > , alter' | ||
25 | > -- * Dealing with 'ObjectGen's (here be dragons) | ||
26 | > , generateObject | ||
27 | > , liftGen | ||
28 | > -- * Equivalence on 'Object's (for nubbing) | ||
29 | > , Equivalent(..) | ||
30 | > -- * Operations on 'SearchQuery's | ||
31 | > , runQuery | ||
32 | > -- , runExpr | ||
33 | > ) where | ||
34 | > | ||
35 | > import Beuteltier.Types | ||
36 | > import Beuteltier.Types.Lenses | ||
37 | |||
38 | We make use of lenses (as provided by [lens](http://hackage.haskell.org/package/lens)) | ||
39 | extensively. | ||
40 | We won´t dedicate a post to `beuteltier/Beuteltier/Types/Lenses.hs` because it consists | ||
41 | mostly of the canonical invocations of | ||
42 | [makeLenses](http://hackage.haskell.org/package/lens-4.12.3/docs/Control-Lens-TH.html#v:makeLenses). | ||
43 | |||
44 | > import Data.Default | ||
45 | > | ||
46 | > import Prelude hiding (sequence) | ||
47 | > import Data.Traversable (sequence) | ||
48 | > | ||
49 | > import Control.Lens | ||
50 | > | ||
51 | > import Control.Monad.State.Lazy hiding (sequence) -- Why is this exported? | ||
52 | > | ||
53 | > import Data.Map (Map) | ||
54 | > import qualified Data.Map as Map | ||
55 | > | ||
56 | > import Data.Hashable (Hashable(..), hashUsing) | ||
57 | > | ||
58 | > import Data.Monoid ((<>)) | ||
59 | > | ||
60 | > import Data.Function (on) | ||
61 | > import Data.Maybe (mapMaybe) | ||
62 | > | ||
63 | > import Data.BoolExpr | ||
64 | |||
65 | Quite often we find ourselves in the position that we want to alter some small parts of a | ||
66 | complicated structure. We would therefore like to write the following: | ||
67 | |||
68 | ~~~ {.haskell .numberLines} | ||
69 | updateFoo :: Monad m => Foo -> m Foo | ||
70 | updateFoo = alter $ do | ||
71 | bar <~ constructNewBarInM | ||
72 | buz .= makeConstantBuz | ||
73 | ~~~ | ||
74 | |||
75 | The definitions below allow us not only to do so, but also provide some convenience | ||
76 | functions for constructing entirely new values and performing both operations in a pure | ||
77 | context. | ||
78 | |||
79 | > alter :: Monad m => s -> StateT s m a -> m s | ||
80 | > -- ^ Alter a complex structure monodically | ||
81 | > alter = flip execStateT | ||
82 | > | ||
83 | > alter' :: s -> State s a -> s | ||
84 | > -- ^ Specialization of 'alter' to 'Identity' | ||
85 | > alter' s = runIdentity . alter s | ||
86 | > | ||
87 | > construct :: (Monad m, Default s) => StateT s m a -> m s | ||
88 | > -- ^ Compute a complex structure monadically | ||
89 | > construct = alter def | ||
90 | > | ||
91 | > construct' :: Default s => State s a -> s | ||
92 | > -- ^ Specialization of 'construct' to 'Identity' | ||
93 | > construct' = runIdentity . construct | ||
94 | |||
95 | Sometimes we just really want to translate an `ObjectGen` to an `Object`. | ||
96 | |||
97 | > generateObject :: Monad f => ObjectGen f -> f Object | ||
98 | > -- ^ Run an object generator. | ||
99 | > -- Use iff /all/ components of an object are needed /in RAM now/. | ||
100 | > generateObject gen = construct $ do | ||
101 | > content <- lift $ gen ^. oContent >>= sequence | ||
102 | > thunks <- lift $ gen ^. oThunks >>= sequence | ||
103 | > meta <- lift $ gen ^. oMeta | ||
104 | > oContent .= return (fmap return content) | ||
105 | > oThunks .= return (fmap return thunks) | ||
106 | > oMeta .= return meta | ||
107 | > | ||
108 | > liftGen :: Monad f => Object -> ObjectGen f | ||
109 | > -- ^ Lift an 'Object' to be an 'ObjectGen' in any 'Monad' by the power of 'return' | ||
110 | > liftGen obj = construct' $ do | ||
111 | > oContent .= return (Map.map return $ obj ^. oContent') | ||
112 | > oThunks .= return (map return $ obj ^. oThunks') | ||
113 | > oMeta .= return (obj ^. oMeta') | ||
114 | |||
115 | We expect implementations of `insert` to perform what we call nubbing. That is removal of | ||
116 | 'Object's that are, in some sense, `Equivalent` to the new one we´re currently | ||
117 | inserting. Thus we provide a definition of what we mean, when we say `Equivalent`. | ||
118 | |||
119 | > class Equivalent a where | ||
120 | > (~~) :: a -> a -> Bool | ||
121 | > | ||
122 | > -- | Two 'Object's are equivalent iff their content is identical as follows: | ||
123 | > -- the set of 'SubObjectName's both promised and actually occurring is identical | ||
124 | > -- and all 'SubObject's that actually occurr and share a 'SubObjectName' are | ||
125 | > -- identical (as per '(==)') | ||
126 | > -- | ||
127 | > -- Additionally we expect their 'Metadata' to be identical (as per '(==)') | ||
128 | > instance Equivalent Object where | ||
129 | > a ~~ b = (contentCompare `on` content) a b && ((==) `on` (^. oMeta')) a b | ||
130 | > where | ||
131 | > contentCompare :: (Ord k, Eq v) => Map k (Maybe v) -> Map k (Maybe v) -> Bool | ||
132 | > contentCompare a b = Map.foldl (&&) True $ Map.mergeWithKey combine setFalse setFalse a b | ||
133 | > combine _ a b = Just $ cmpMaybes a b | ||
134 | > setFalse = Map.map $ const False | ||
135 | > | ||
136 | > cmpMaybes Nothing _ = True | ||
137 | > cmpMaybes _ Nothing = True | ||
138 | > cmpMaybes (Just a) (Just b) = a == b | ||
139 | |||
140 | To speed up nubbing we also provide a quick way to "cache results". To make caching | ||
141 | meaningful we of course expect the following to hold: | ||
142 | |||
143 | ~~~ | ||
144 | a ~~ b ⇒ (hash a) == (hash b) | ||
145 | ~~~ | ||
146 | |||
147 | Note that we do not expect the converse to hold. We will thus require a second pass over | ||
148 | all objects sharing a hash to determine true equivalency. | ||
149 | |||
150 | > -- | Two 'Object's´ hashes are a first indication of whether they are 'Equivalent' | ||
151 | > instance Hashable Object where | ||
152 | > hashWithSalt = hashUsing $ \a -> (a ^. oMeta', Map.keys $ content a) | ||
153 | > | ||
154 | > content :: Object -> Map SubObjectName (Maybe SubObject) | ||
155 | > content obj = promised obj <> actual obj | ||
156 | > actual :: Object -> Map SubObjectName (Maybe SubObject) | ||
157 | > actual = fmap Just . (^. oContent') | ||
158 | > promised :: Object -> Map SubObjectName (Maybe SubObject) | ||
159 | > promised = Map.fromList . map (\n -> (n, Nothing)) . concat . promises | ||
160 | > promises :: Object -> [[SubObjectName]] | ||
161 | > promises = mapMaybe (^. tPromises) . (^. oThunks') | ||
162 | |||
163 | Evaluating a `SearchQuery` against an `ObjectGen` is, due to the structure of elementary | ||
164 | `SearchQuery`s quite straightforward. | ||
165 | |||
166 | > runQuery :: Monad f => SearchQuery f -> ObjectGen f -> f Bool | ||
167 | > -- ^ Run a 'SearchQuery' against an 'ObjectGen' | ||
168 | > runQuery query obj = liftM reduceBoolExpr $ sequence $ fmap ($ obj) query | ||
169 | > | ||
170 | > -- runExpr :: Monad f => ObjectGen f -> Predicate f -> f Bool | ||
171 | > -- -- ^ Run a 'Predicate' (»an atomic 'SearchQuery'«) against an 'ObjectGen' | ||
172 | > -- runExpr obj (Prim f) = f obj | ||
173 | > -- runExpr obj (Meta f) = liftM f (obj ^. oMeta) | ||