Lawrence Wagerfield Lawrence Wagerfield - 11 months ago 106
Scala Question

F-Bounded polymorphism with abstract types in Scala

I have read several articles expressing that abstract types should be used to achieve f-bounded polymorphism in Scala. This is primarily to alleviate type inference issues, but also to remove the quadratic growth that type parameters seem to introduce when defining recursive types.

These are defined as so:

trait EventSourced[E] {
self =>

type FBound <: EventSourced[E] { type FBound <: self.FBound }

def apply(event: E): FBound

However, this appears to introduce two issues:

1) Each time a user wants to reference an object of this type, they must also refer to the
type parameter. This feels like a code smell:

def mapToSomething[ES <: EventSourced[E], E](eventSourced: ES#FBound): Something[ES, E] = ...

2) The compiler is now unable to infer type parameters for methods such as the above, failing with the message:

Type mismatch, expected: NotInferredES#FBound, actual: MyImpl#FBound

Is anyone out there using a successful implementation of f-bounded polymorphism in their solution, whereby the compiler is still able to infer types?

Answer Source

I've since realised that f-bounded polymorphism should be avoided in most cases - or rather - there's usually an alternative design that you should opt for. To understand how to avoid it, we first need to know what makes us require it:

F-bounded polymorphism occurs when a type expects important interface changes to be introduced in derived types.

This is avoided by composing the expected areas of change instead of attempting to support them via inheritance. This actually comes back to Gang of Four design patterns:

Favor 'object composition' over 'class inheritance'

-- (Gang of Four, 1995)

For example:

trait Vehicle[V <: Vehicle[V, W], W] {
    def replaceWheels(wheels: W): V


trait Vehicle[T, W] {
    val vehicleType: T
    def replaceWheels(wheels: W): Vehicle[T, W]

Here, the 'expected change' is the vehicle type (e.g Bike, Car, Lorry). The previous example assumed this would be added through inheritance, requiring an f-bounded type that made inference of W impossible for any function using Vehicle. The new method, which uses composition, does not exhibit this problem.