0 | ||| Deriving foldable instances using reflection
1 | ||| You can for instance define:
2 | ||| ```
3 | ||| data Tree a = Leaf a | Node (Tree a) (Tree a)
4 | ||| treeFoldable : Foldable Tree
5 | ||| treeFoldable = %runElab derive
6 | ||| ```
20 | %language ElabReflection
29 | foldr
30 | foldl
34 | fm
36 | where
44 | ------------------------------------------------------------------------------
45 | -- Errors
47 | ||| Possible errors for the functor-deriving machinery.
60 | -- Contextual information
64 | export
71 | go acc (NotAFoldable s) = acc <>> ["Couldn't find a `Foldable' instance for the type constructor \{show s}"]
72 | go acc (NotABifoldable s) = acc <>> ["Couldn't find a `Bifoldable' instance for the type constructor \{show s}"]
79 | go acc (WhenCheckingConstructor nm err) = go (acc :< "When checking constructor \{show nm}") err
95 | ------------------------------------------------------------------------------
96 | -- Core machinery: being foldable
98 | -- Not meant to be re-exported as it's using the internal notion of error
108 | ||| IsFoldableIn is parametrised by
109 | ||| @ t the name of the data type whose constructors are being analysed
110 | ||| @ x the name of the type variable that the foldable action will act on
111 | ||| @ ty the type being analysed
112 | ||| The inductive type delivers a proof that x can be folded over in ty,
113 | ||| assuming that t also is foldable.
116 | ||| The type variable x occurs alone
118 | ||| There is a recursive subtree of type (t a1 ... an u) and u is Foldable in x.
119 | ||| We do not insist that u is exactly x so that we can deal with nested types
120 | ||| like the following:
121 | ||| data Full a = Leaf a | Node (Full (a, a))
122 | ||| data Term a = Var a | App (Term a) (Term a) | Lam (Term (Maybe a))
125 | ||| The subterm is delayed (Lazy only, we can't fold over infinite structures)
127 | ||| There are nested subtrees somewhere inside a 3rd party type constructor
128 | ||| which satisfies the Bifoldable interface
132 | ||| There are nested subtrees somewhere inside a 3rd party type constructor
133 | ||| which satisfies the Foldable interface
136 | ||| A type free of x is trivially Foldable in it
139 | parameters
148 | ||| When analysing the type of a constructor for the type family t,
149 | ||| we hope to observe
150 | ||| 1. either that it is foldable in x
151 | ||| 2. or that it is free of x
152 | ||| If it is not the case, we will use the `MonadError Error` constraint
153 | ||| to fail with an informative message.
158 | export
163 | ||| Hoping to observe that ty is foldable
164 | export
167 | ||| To avoid code duplication in typeView, we have an auxiliary function
168 | ||| specifically to handle the application case
178 | -- if x is present in the argument then the function better be:
179 | -- 1. free of x
180 | -- 2. either an occurrence of t i.e. a subterm
181 | -- or a type constructor already known to be functorial
191 | -- record that the nth parameter should be functorial
195 | -- and happily succeed
200 | -- Otherwise it better be the case that f is also free of x so that
201 | -- we can mark the whole type as being x-free.
224 | -- record that the nth parameter should be bifoldable
228 | -- and happily succeed
247 | ------------------------------------------------------------------------------
248 | -- Core machinery: building the foldMap function from an IsFoldableIn proof
252 | ||| foldMapFun takes
253 | ||| @ mutualWith a list of mutually defined type constructors. Calls to their
254 | ||| respective mapping functions typically need an assert_total because the
255 | ||| termination checker is not doing enough inlining to see that things are
256 | ||| terminating
257 | ||| @ assert records whether we should mark recursive calls as total because
258 | ||| we are currently constructing the argument to a higher order function
259 | ||| which will obscure the termination argument. Starts as `Nothing`, becomes
260 | ||| `Just False` if an `assert_total` has already been inserted.
261 | ||| @ ty the type being transformed by the mapping function
262 | ||| @ rec the name of the mapping function being defined (used for recursive calls)
263 | ||| @ f the name of the function we're mapping
264 | ||| @ arg the (optional) name of the argument being mapped over. This lets us use
265 | ||| Nothing when generating arguments to higher order functions so that we generate
266 | ||| the eta contracted `map (mapTree f)` instead of `map (\ ts => mapTree f ts)`.
271 | -- only add assert_total if it is declared to be needed
275 | -- here we need to eta-expand to avoid "Lazy t does not unify with t" errors
282 | -- only add assert_total if we are calling a mutually defined Foldable implementation.
283 | = let isMutual = fromMaybe False (appView ty >>= \ v => pure (snd v.head `elem` mutualWith)) in
299 | ------------------------------------------------------------------------------
300 | -- User-facing: Foldable deriving
311 | -- expand the mutualwith names to have the internal, fully qualified, names
316 | -- The goal should have the shape (Foldable t)
322 | -- t should be a type constructor
326 | joinBy "\n" $ "" :: map (\ (n, ty) => " \{showPrefix True $ dropNS n} : \{show $ mapTTImp cleanup ty}") cs
328 | -- Generate a clause for each data constructor
336 | -- Grab the types of the constructor's explicit arguments
341 | "\{showPrefix True (dropNS cName)} (\{joinBy ", " (map (showPrec Dollar . mapTTImp cleanup . unArg . snd) args)})"
355 | -- a covering/partial definition
367 | -- Generate the type of the mapping function
381 | -- Define the instance
387 | ||| Derive an implementation of Foldable for a type constructor.
388 | ||| This can be used like so:
389 | ||| ```
390 | ||| data Tree a = Leaf a | Node (Tree a) (Tree a)
391 | ||| treeFoldable : Foldable Tree
392 | ||| treeFoldable = %runElab derive
393 | ||| ```
394 | export