I would not classify cake as an anti-pattern per se. Like any scheme
it is good for something but can be over-used. In fact, cake and
inheritance are quite similar in their benefits and problems.
Here are some points I can think of (I'd be interested in other
arguments people might have).
The good:
- Lets you compose multiple components quickly and with minimal fuss
- no parameters, no forwarders, no imports, just mix slices together.
- Allows for mutual dependencies between slices.
- Is therefore very general: Any set of global definitions with
arbitrary dependencies between them can be lifted into a cake.
The bad:
- Because they are so convenient, cakes tend to get big.
- Dependencies are not declared, so are hard to track for big cakes.
- If cakes contain types, these can be accessed from outside the cake
only with path dependent types. Cakes with mutual type dependencies
between them (as they exist in nsc) are particularly hard to model.
In summary, cakes, like inheritance, are a useful tool to achieve
close coupling of components. It's very easy to link components in one
cake, and it is comparably much harder to access them from the
outside, in particular if types are involved. Cakes are misused in a
situation where weak coupling is preferable (and those situations are
in the majority).
My advice would be: Cakes are fine to model mutual dependencies
between traits. However, care must be exercised to keep them
reasonably small. Furthermore, one should think twice whether
embedding types in cakes is the right way to model things. Nowadays,
I'd do that only if the type was essentially private to the
participants of the cake, or if it was clear that every instance needs
its different opaque type. Otherwise one might end up with awkward
situations, where e.g. the type of symbols in the nsc compiler depends
on the cake that's used. Sometimes you want that isolation, but at
other times you want to communicate a symbol between different
compiler instances (i.e., cakes), and then all you can do is copy an
arbitrary complex graph of symbol dependencies (see the "Importers"
framework in scala.reflect). So, putting types in traits can be a
premature restriction of their usage.
The alternative to cakes is essentially functional abstraction. Have
components parameterized by others. Depending where you come from you
might call that "functors" or "constructor dependency injection".
Implicit parameters can help reduce the boilerplate in function
application.
The dotty compiler uses fine-grained functional abstraction
everywhere, using implicit context arguments. That has turned out to
work quite well so far. Contexts are still cakes, in that they mix
together slices that are modelled around different concerns. But they
are much smaller and tend not to contain types, just methods and
fields.
This writeup got much longer than I meant it to be when I started...
- Martin
> --
> You received this message because you are subscribed to the Google Groups
> "scala-language" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to
scala-languag...@googlegroups.com.
> For more options, visit
https://groups.google.com/d/optout.
--
Martin Odersky
EPFL