I heard Go had late-bound inheritance and was excited to see how that would effect my coding. Instead I found that I already achieve the same kind of value in F#, just in a different way.
Late-bound Inheritance
By late-bound inheritance, I mean that a class can inherit an interface after the class is defined.
This contrasts with many popular static languages that require inheritance relationships to be defined at the same time as the class is defined.
For example, in C#
|
|
Bar
must implement IFoo
when it is defined or it can’t be treated like an IFoo later.
Only the owner of Bar can decide if it is an IFoo. Inheritance can’t be decided by a later
user of Bar and IFoo without making a derivative type of Bar.
In practice, this means it’s expensive to adapt arbitrary types into an interface, so coders often choose a different route.
However, late-bound inheritance means that interfaces can be inherited outside the class declaration, making it much easier to implement interfaces on arbitrary types.
Simpler interface inheritance also simplifies Interface-segregation where interfaces are broken out into minimal groups of behavior, thus avoiding unintended coupling.
Go Limitations
Unfortunately, it turns out Go does not support late-bound inheritance between types in different packages. It is only possible if the type and the inheritance are defined in the same package. This significantly limits the utility of adding interfaces to types, since we can only do it when we already have access to directly extend the type’s declaration.
Example
Go’s limitations aside, let’s consider an example of how late-bound inheritance might be useful. Imagine an interface IConsolePrint
. The interface is used to get the console representation of a type.
3rd-party types clearly won’t come with this custom functionality. With late-bound inheritance, we could retrofit types to satisfy IConsolePrint
.
|
|
Why this doesn’t impact my thinking
Go’s late-bound inheritance is interesting, but it didn’t significantly change my thinking. Instead, I realized that I effectively already achieve the same value in F# through composition.
F# can pass and store functions as values. Any function that fits the expected type signature can be assigned to that parameter or symbol.
For example
|
|
This means that function values act like single-method interfaces.
For example, here’s the previous Go sample translated to F#
|
|
But that only demonstrates a single method. Interfaces with multiple methods can be approximated using records types. For example
|
|
We can then compose an “implementation” of ThingCRUD
simply by assigning functions with the right type signatures to an instance of ThingCRUD
.
Here’s a really simple in-memory implementation of ThingCRUD
|
|
Thus, we can satisfy the same need as late-bound interfaces, which allows us to treat arbitrary objects the same under a given abstraction. And the interface “implementations” are lightweight since mapping functions into such parameters is quite simple and terse. Unlike Go, this approach is not effected by package boundaries.
For those who want to dig deeper, this F# approach is secretely a reader monad.