0 | ||| Deriving functor instances using reflection
1 | ||| You can for instance define:
2 | ||| ```
3 | ||| data Tree a = Leaf a | Node (Tree a) (Tree a)
4 | ||| treeFunctor : Functor Tree
5 | ||| treeFunctor = %runElab derive
6 | ||| ```
18 | %language ElabReflection
21 | ------------------------------------------------------------------------------
22 | -- Errors
24 | ||| Possible errors for the functor-deriving machinery.
36 | -- Contextual information
40 | export
48 | go acc (NotAFunctor s) = acc <>> ["Couldn't find a `Functor' instance for the type constructor \{show s}"]
49 | go acc (NotABifunctor s) = acc <>> ["Couldn't find a `Bifunctor' instance for the type constructor \{show s}"]
54 | go acc (WhenCheckingConstructor nm err) = go (acc :< "When checking constructor \{show nm}") err
57 | ------------------------------------------------------------------------------
58 | -- Core machinery: being functorial
60 | -- Not meant to be re-exported as it's using the internal notion of error
78 | ||| IsFunctorialIn is parametrised by
79 | ||| @ pol the polarity of the type being analysed. We start in positive polarity
80 | ||| where occurrences of x are allowed and negate the polarity every time
81 | ||| we step into the domain of a Pi type.
82 | ||| @ t the name of the data type whose constructors are being analysed
83 | ||| @ x the name of the type variable that the functioral action will change
84 | ||| @ ty the type being analysed
85 | ||| The inductive type delivers a proof that x occurs positively in ty,
86 | ||| assuming that t also is positive.
89 | ||| The type variable x occurs alone
91 | ||| There is a recursive subtree of type (t a1 ... an u) and u is functorial in x.
92 | ||| We do not insist that u is exactly x so that we can deal with nested types
93 | ||| like the following:
94 | ||| data Full a = Leaf a | Node (Full (a, a))
95 | ||| data Term a = Var a | App (Term a) (Term a) | Lam (Term (Maybe a))
98 | ||| The subterm is delayed (either Inf or Lazy)
100 | ||| There are nested subtrees somewhere inside a 3rd party type constructor
101 | ||| which satisfies the Bifunctor interface
105 | ||| There are nested subtrees somewhere inside a 3rd party type constructor
106 | ||| which satisfies the Functor interface
109 | ||| A pi type, with no negative occurrence of x in its domain
112 | ||| A type free of x is trivially Functorial in it
128 | parameters
137 | ||| When analysing the type of a constructor for the type family t,
138 | ||| we hope to observe
139 | ||| 1. either that it is functorial in x
140 | ||| 2. or that it is free of x
141 | ||| If if it is not the case, we will use the `MonadError Error` constraint
142 | ||| to fail with an informative message.
147 | export
152 | ||| Hoping to observe that ty is functorial
153 | export
156 | ||| To avoid code duplication in typeView, we have an auxiliary function
157 | ||| specifically to handle the application case
167 | -- if x is present in the argument then the function better be:
168 | -- 1. free of x
169 | -- 2. either an occurrence of t i.e. a subterm
170 | -- or a type constructor already known to be functorial
182 | -- record that the nth parameter should be functorial
186 | -- and happily succeed
191 | -- Otherwise it better be the case that f is also free of x so that
192 | -- we can mark the whole type as being x-free.
226 | -- record that the nth parameter should be bifunctorial
230 | -- and happily succeed
245 | ------------------------------------------------------------------------------
246 | -- Core machinery: building the mapping function from an IsFunctorialIn proof
250 | ||| functorFun takes
251 | ||| @ mutualWith a list of mutually defined type constructors. Calls to their
252 | ||| respective mapping functions typically need an assert_total because the
253 | ||| termination checker is not doing enough inlining to see that things are
254 | ||| terminating
255 | ||| @ assert records whether we should mark recursive calls as total because
256 | ||| we are currently constructing the argument to a higher order function
257 | ||| which will obscure the termination argument. Starts as `Nothing`, becomes
258 | ||| `Just False` if an `assert_total` has already been inserted.
259 | ||| @ ty the type being transformed by the mapping function
260 | ||| @ rec the name of the mapping function being defined (used for recursive calls)
261 | ||| @ f the name of the function we're mapping
262 | ||| @ arg the (optional) name of the argument being mapped over. This lets us use
263 | ||| Nothing when generating arguments to higher order functions so that we generate
264 | ||| the eta contracted `map (mapTree f)` instead of `map (\ ts => mapTree f ts)`.
269 | -- only add assert_total if it is declared to be needed
273 | -- 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 Functor implementation.
283 | = let isMutual = fromMaybe False (appView ty >>= \ v => pure (snd v.head `elem` mutualWith)) in
300 | -- /!\ We cannot use the type stored in FIPi here because it could just
301 | -- be a name that will happen to be different when bound on the LHS!
302 | -- Cf. the Free test case in reflection017
309 | ------------------------------------------------------------------------------
310 | -- User-facing: Functor deriving
321 | -- expand the mutualwith names to have the internal, fully qualified, names
326 | -- The goal should have the shape (Functor t)
332 | -- t should be a type constructor
336 | joinBy "\n" $ "" :: map (\ (n, ty) => " \{showPrefix True $ dropNS n} : \{show $ mapTTImp cleanup ty}") cs
338 | -- Generate a clause for each data constructor
346 | -- Grab the types of the constructor's explicit arguments
351 | "\{showPrefix True (dropNS cName)} (\{joinBy ", " (map (showPrec Dollar . mapTTImp cleanup . unArg . snd) args)})"
355 | -- only keep the arguments that are either:
356 | -- 1. modified by map
357 | -- 2. explicit
363 | -- a covering/partial definition
373 | -- Generate the type of the mapping function
383 | ]
392 | -- Define the instance
398 | ||| Derive an implementation of Functor for a type constructor.
399 | ||| This can be used like so:
400 | ||| ```
401 | ||| data Tree a = Leaf a | Node (Tree a) (Tree a)
402 | ||| treeFunctor : Functor Tree
403 | ||| treeFunctor = %runElab derive
404 | ||| ```
405 | export