... | @@ -40,6 +40,55 @@ As a rule of thumb, when writing monomorphic code, prefer using a monomorphic fu |
... | @@ -40,6 +40,55 @@ As a rule of thumb, when writing monomorphic code, prefer using a monomorphic fu |
|
|
|
|
|
Even when one has genuinely polymorphic code, it can be better to write out the code longhand than to reuse a generic abstraction (not always, of course). Sometimes it's better to duplicate some similar code than to try to construct an elaborate generalisation with only two instances. Remember: other people have to be able to quickly understand what you've done, and overuse of abstractions just serves to obscure the *really* tricky stuff, and there's no shortage of that in GHC.
|
|
Even when one has genuinely polymorphic code, it can be better to write out the code longhand than to reuse a generic abstraction (not always, of course). Sometimes it's better to duplicate some similar code than to try to construct an elaborate generalisation with only two instances. Remember: other people have to be able to quickly understand what you've done, and overuse of abstractions just serves to obscure the *really* tricky stuff, and there's no shortage of that in GHC.
|
|
|
|
|
|
|
|
Here's a real-life example (!9037). Here are two functions that do the same thing:
|
|
|
|
```
|
|
|
|
f1, f2, f3 :: [a] -> [a] -> [[a]]
|
|
|
|
f1 as = sequenceA $ (:) <$> as
|
|
|
|
f2 as bs = [a:bs | a <- as]
|
|
|
|
f3 as bs = map (\a -> a:bs) as
|
|
|
|
```
|
|
|
|
For GHC we much prefer `f2` or `f3`. The former uses a list comprehension; the latter uses (list) map. Both are easy to grok.
|
|
|
|
|
|
|
|
Understanding `f1` is quite a bit more demanding. To be clear, **there is nothing wrong with `f1`** for people who have `sequenceA` and friends paged into their mental cache. But for those who don't, here's the journey of at least one reader.
|
|
|
|
|
|
|
|
Let's start with type checking. For `f1` we must look up the type of `sequenceA`:
|
|
|
|
```
|
|
|
|
sequenceA :: forall t f b. (Traversable t, Applicative f) => t (f b) -> f (t b)
|
|
|
|
```
|
|
|
|
Now we must figure out how `t` and `f` are instantiated. Let's figure out the type of `(:) <$> as`. Well, `as :: [a]`, so `<$> :: Functor g => (p->q) -> g p -> g q` is being used with `g=[]` and `q = [a] -> [a]`. So we have
|
|
|
|
```
|
|
|
|
(:) <$> as :: [ [a] -> [a] ]
|
|
|
|
```
|
|
|
|
Now that type must match the argument of `sequenceA`, which has type `t (f b)`,
|
|
|
|
so we must have `t = []` and `f = (->) [a]` and `b = [a]`.
|
|
|
|
|
|
|
|
Right, so the result of `sequenceA` has type `f (t b)` which is `[a] -> [[a]]`. That matches the type signature for `f1`, so the program is type correct.
|
|
|
|
|
|
|
|
Now we need to work out what `sequenceA @[] @((->) a)` actually *does*. It's a method of the `Traversable` class, so we need the `Traversable []` instance. Instances aren't all that easy to find (you can't grep for them the way you can grep for a function name), but it's in `Data.Traversable`:
|
|
|
|
```
|
|
|
|
instance Traversable [] where
|
|
|
|
traverse f = List.foldr cons_f (pure [])
|
|
|
|
where cons_f x ys = liftA2 (:) (f x) ys
|
|
|
|
```
|
|
|
|
Oh. No actual definition of `sequenceA`. Let's look at the default method:
|
|
|
|
```
|
|
|
|
class (Functor t, Foldable t) => Traversable t where
|
|
|
|
sequenceA :: Applicative f => t (f a) -> f (t a)
|
|
|
|
sequenceA = traverse id
|
|
|
|
```
|
|
|
|
OK so in our call of `sequenceA` it really means
|
|
|
|
```
|
|
|
|
sequenceA @[] @((->) a)
|
|
|
|
= traverse @[] @((->) a) id
|
|
|
|
= List.foldr cons_f (pure [])
|
|
|
|
where cons_f x ys = liftA2 (:) (f x) ys
|
|
|
|
```
|
|
|
|
Now `liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c`, so again we have to
|
|
|
|
work out what `f` is, and thus what code is executed by that `liftA2`.
|
|
|
|
|
|
|
|
None of this is easy. We prefer `f2` or `f3`. **General rule: use monomorphic functions
|
|
|
|
if you can**.
|
|
|
|
|
|
### 1.4 INLINE and SPECIALISE pragmas
|
|
### 1.4 INLINE and SPECIALISE pragmas
|
|
|
|
|
|
If inlining or specialisation is important for performance, use pragmas to say so. Consider
|
|
If inlining or specialisation is important for performance, use pragmas to say so. Consider
|
... | | ... | |