Skip to content

Commit 06d2d37

Browse files
authored
Document deriving Functor, Foldable, Traversable (#446)
* Document deriving Functor, Foldable, Traversable * Cover the enhancements in PureScript PR #4420
1 parent 84e5574 commit 06d2d37

File tree

2 files changed

+134
-0
lines changed

2 files changed

+134
-0
lines changed

language/Differences-from-Haskell.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,8 +321,10 @@ The PureScript compiler does not support GHC-like language extensions. However,
321321
* [ApplicativeDo](https://downloads.haskell.org/ghc/latest/docs/html/users_guide/exts/applicative_do.html#extension-ApplicativeDo)
322322
* [BlockArguments](https://downloads.haskell.org/ghc/latest/docs/html/users_guide/exts/block_arguments.html#extension-BlockArguments)
323323
* [DataKinds](https://downloads.haskell.org/ghc/latest/docs/html/users_guide/exts/data_kinds.html#extension-DataKinds) (see note below)
324+
* [DeriveFoldable](https://downloads.haskell.org/ghc/latest/docs/users_guide/exts/deriving_extra.html#extension-DeriveFoldable)
324325
* [DeriveFunctor](https://downloads.haskell.org/ghc/latest/docs/html/users_guide/exts/deriving_extra.html#extension-DeriveFunctor)
325326
* [DeriveGeneric](https://downloads.haskell.org/ghc/latest/docs/html/users_guide/exts/generics.html#extension-DeriveGeneric)
327+
* [DeriveTraversable](https://downloads.haskell.org/ghc/latest/docs/users_guide/exts/deriving_extra.html#extension-DeriveTraversable)
326328
* [EmptyDataDecls](https://downloads.haskell.org/ghc/latest/docs/html/users_guide/exts/nullary_types.html#extension-EmptyDataDecls)
327329
* [ExplicitForAll](https://downloads.haskell.org/ghc/latest/docs/html/users_guide/exts/explicit_forall.html#extension-ExplicitForAll)
328330
* [FlexibleContexts](https://downloads.haskell.org/ghc/latest/docs/html/users_guide/exts/flexible_contexts.html#extension-FlexibleContexts)

language/Type-Classes.md

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,142 @@ nub [Some, Arbitrary 1, Some, Some] == [Some, Arbitrary 1]
181181

182182
Currently, instances for the following classes can be derived by the compiler:
183183
- Data.Generic.Rep (class Generic)
184+
- [Data.Bifoldable (class Bifoldable)](https://pursuit.purescript.org/packages/purescript-foldable-traversable/docs/Data.Bifoldable#t:Bifoldable)
185+
- [Data.Bifunctor (class Bifunctor)](https://pursuit.purescript.org/packages/purescript-bifunctors/docs/Data.Bifunctor#t:Bifunctor)
186+
- [Data.Bitraversable (class Bitraversable)](https://pursuit.purescript.org/packages/purescript-foldable-traversable/docs/Data.Bitraversable#t:Bitraversable)
184187
- [Data.Eq (class Eq)](https://pursuit.purescript.org/packages/purescript-prelude/docs/Data.Eq#t:Eq)
185188
- [Data.Ord (class Ord)](https://pursuit.purescript.org/packages/purescript-prelude/docs/Data.Ord#t:Ord)
189+
- [Data.Foldable (class Foldable)](https://pursuit.purescript.org/packages/purescript-foldable-traversable/docs/Data.Foldable#t:Foldable)
186190
- [Data.Functor (class Functor)](https://pursuit.purescript.org/packages/purescript-prelude/docs/Data.Functor#t:Functor)
191+
- [Data.Functor.Contravariant (class Contravariant)](https://pursuit.purescript.org/packages/purescript-contravariant/docs/Data.Functor.Contravariant#t:Contravariant)
187192
- [Data.Newtype (class Newtype)](https://pursuit.purescript.org/packages/purescript-newtype/docs/Data.Newtype#t:Newtype)
193+
- [Data.Profunctor (class Profunctor)](https://pursuit.purescript.org/packages/purescript-profunctor/docs/Data.Profunctor#t:Profunctor)
194+
- [Data.Traversable (class Traversable)](https://pursuit.purescript.org/packages/purescript-foldable-traversable/docs/Data.Traversable#t:Traversable)
195+
196+
#### `Functor`, `Foldable`, and `Traversable`
197+
198+
All three of these classes (and variations on them, like `Bifunctor`) can be derived for certain data types. The full table of supported classes is below:
199+
200+
<table>
201+
<thead>
202+
<tr><th colspan="2">monoparametric</th><th colspan="2">biparametric</th></tr>
203+
</thead>
204+
<tbody>
205+
<tr><td><code>Functor</code></td><td><code>Contravariant</code></td><td><code>Bifunctor</code></td><td><code>Profunctor</code></td></tr>
206+
<tr><td><code>Foldable</code></td><td>—</td><td><code>Bifoldable</code></td><td>—</td></tr>
207+
<tr><td><code>Traversable</code></td><td>—</td><td><code>Bitraversable</code></td><td>—</td></tr></tbody>
208+
</table>
209+
210+
Each row of this table defines a set of **compatible** classes; for the remainder of this section, a `Functor`-compatible class is any class in the top row of the table, etc.
211+
212+
The compiler can derive an instance of any of the above classes for your data type if the following requirements are met:
213+
214+
1. The data type must have at least one type parameter (or at least two type parameters, if a biparametric class is being derived).
215+
216+
Occurrences of the final parameter (if deriving a monoparametric class) or final two parameters (if biparametric) in the constructors of your data type will be referred to below as **relevant variables**.
217+
218+
2. Each relevant variable must be in a legal position in the data type's definitions. Legal positions are:
219+
* Data constructor arguments
220+
* A field in a record type that itself appears in a legal position
221+
* An argument to a type constructor that itself appears in a legal position, *only if* one of the following holds:
222+
* If the constructed type is of the form `f a b ... x y`, and `f a b ... x` has an instance of a monoparametric class compatible with the class being derived, then `y` is a legal position.
223+
* If the constructed type is of the form `f a b ... x y z`, and `f a b ... x` has an instance of a biparametric class compatible with the class being derived, then `y` and `z` are legal positions.
224+
225+
(For the highly detail-oriented: if both conditions are met, and the penultimate argument doesn't contain any relevant variables, the monoparametric instance will be preferred. This shouldn't ever cause problems if all instances are well-behaved; for example, `rmap` and `map` are expected to be equivalent when both are available.)
226+
227+
3. If deriving a `Functor`-compatible class, each relevant variable must be used with its expected **variance** (covariant or contravariant). When deriving `Functor` or `Bifunctor`, all variables are expected to be covariant. When deriving `Contravariant`, the variable is expected to be contravariant. When deriving `Profunctor`, the first variable is expected to be contravariant and the second covariant.
228+
229+
A type variable is used covariantly if it occurs exclusively in positive positions in the data type's definition, and contravariantly if it occurs exclusively in negative positions. Positive and negative positions are defined similarly to legal positions:
230+
* Data constructor arguments are positive positions.
231+
* A field in a record type in a positive (resp. negative) position is itself a positive (resp. negative) position.
232+
* An argument to a type constructor may keep or invert the sign of the (constructed) type's position, depending on the variance of the `Functor`-compatible instance chosen for that type when determining if the argument is a legal position. An argument corresponding to a covariant (resp. contravariant) parameter keeps (resp. inverts) the sign.
233+
234+
(Another remark for the highly detail-oriented: in the rare case that both `Functor` and `Contravariant`, or both `Bifunctor` and `Profunctor`, instances are available for a type constructor such that a position could be either positive or negative, the compiler chooses `Functor` or `Bifunctor`. This case is rare because only phantom parameters can be both covariant and contravariant.)
235+
236+
(In most cases, the lack of orphan instances in PureScript means that the compiler will be able to automatically find any instances that might be relevant for the above requirements if they exist. However, a small number of `Functor`-compatible instances are defined in packages that may not already be included in your project if you are attempting to derive a different `Functor`-compatible class. In that case, if you are expecting to use the `Bifunctor`, `Contravariant`, or `Profunctor` instances for any of `Const`, `Either`, `Function`, or `Tuple`, you will have to add `purescript-bifunctors`, `purescript-contravariant`, or `purescript-profunctor` as appropriate to the dependencies of your project before the compiler will proceed with deriving the class. This list of exceptional instances may change as the PureScript standard libraries evolve.)
237+
238+
The examples below indicate valid usages (via ``) and invalid usages (via ``) of a type parameter `a` used in various places below.
239+
If the invalid usages are removed from the data constructors, the compiler can derive `Functor`, `Foldable`, and `Traversable` instances for it.
240+
241+
```purs
242+
data X f a
243+
= X0 Int
244+
-- - since no `a` appears in this data constructor
245+
-- it doesn't break any of the rules above
246+
| X1 a a a
247+
-- ✓ ✓ ✓ - data constructor arguments are legal positions for `a`
248+
| X2 (f a)
249+
-- ✓ - because the `a` is the rightmost argument to `f`
250+
-- `f` will be required to be a `Functor`, etc.
251+
| X3 (Tuple a Int)
252+
-- ✓ - `Tuple` needs to be a `Bifunctor`, etc. (which it is, if `purescript-bifunctors` is in your project's dependencies)
253+
| X4 (Tuple a a)
254+
-- ✓ ✓
255+
| X5 { foo :: a, bar :: f a, baz :: Tuple Int a }
256+
-- ✓ ✓ ✓ - records are supported
257+
| X6 { one :: { two :: { three :: a } } }
258+
-- ✓ - even nested ones
259+
| X7 (Foo a Int Int)
260+
-- ⨯ - this `a` is the third-to-last argument and can't be legal no matter what `Foo` implements
261+
| X8 (Variant (left :: a, right :: Int))
262+
-- ⨯ - row types aren't included in the definition of legal positions
263+
```
264+
265+
For `Foldable` and `Traversable` (and compatible classes), records are folded and traversed in the alphabetical ordering of their labels, not their definition order.
266+
For example, a record defined like
267+
```purs
268+
type Foo a =
269+
{ m :: a
270+
, f :: a
271+
, c :: a
272+
, g :: a
273+
}
274+
```
275+
276+
will be traversed in a `cfgm` order, not the `mfcg` order.
277+
278+
Given a data type like the following...
279+
280+
```purs
281+
data M f a
282+
= M0
283+
| M1 a (Array a)
284+
| M2 Int
285+
| M3 (f a)
286+
| M4 { a :: a, x :: Array a, fa :: f a
287+
, ignore :: Int, no :: Array Int, nope :: f Int
288+
, nested :: { anotherOne :: a }
289+
}
290+
291+
derive instance Foldable f => Foldable (M f)
292+
```
293+
294+
Something like the following will be generated:
295+
```purs
296+
instance Foldable f => Foldable (M f) where
297+
foldl f z = case _ of
298+
M0 -> z
299+
M1 a arr -> foldl f (f z a) arr
300+
M2 _ -> z
301+
M3 fa -> foldl f z fa
302+
M4 rec ->
303+
foldl f (foldl f (foldl f (f z rec.a) rec.fa) rec.nested.anotherOne) rec.x
304+
foldr f z = case _ of
305+
M0 -> z
306+
M1 a arr -> f a (foldr f z arr)
307+
M2 _ -> z
308+
M3 fa -> foldr f z fa
309+
M4 rec ->
310+
f (foldr f (foldr f (f z rec.x) rec.nested.anotherOne) rec.fa) rec.a
311+
foldMap f = case _ of
312+
M0 -> mempty
313+
M1 a arr -> f a <> foldMap f arr
314+
M2 _ -> mempty
315+
M3 fa -> foldMap f fa
316+
M4 rec -> f rec.a <> foldMap f rec.fa <> foldMap f rec.nested.anotherOne <> foldMap rec.x
317+
```
318+
319+
Finally, note that superclasses are not automatically derived; if you derive `Traversable`, you will also need explicit or derived instances of `Functor` and `Foldable`.
188320

189321
### Derive from `newtype`
190322

0 commit comments

Comments
 (0)