To Transfer Fields Directly from One Type to Another without Composition
“REMOVE ALL THOUGHTS OF INHERITANCE! THIS IS FUNCTIONAL PROGRAMMING!”
Context: While working with a set of API contracts, we found multiple types that had a lot of common fields. To reduce code duplication, we wanted to explore the usefulness of and ways to extract the common fields into a single common type (that could then be used within other types).
Technically, this is what Composition is intended for. The way it works is to create a single common type with all of the common fields and compose this “common” type into the others.
And the above code worked great for our model.
But then, we realized that the original types might change in the newer versions or sub-types of the model. For example, maybe for
type A, we might decide that the “id” attribute needs to be an
int. Well, the fix for that was easy, we’d override the type in the sub-type type definition.
But, then we saw that we needed to create a specific function to access the specific field for
A, as the default type didn’t work. Unfortunately, this undid the point of using a common type in the first place. Further, in a complex version of this problem, (much like the one we’re actually implementing) creating individual field value access functions for each modified type would quickly become ungainly.
Now, like many of us, my background was OOP, so my first thought following the above discussion was… why not just have a class we inherit the value from and just override the value? This led to the (sub) titular exclamation above.
Fine, let’s be a bit sneaky and see what it looks like with inheritance…
which imo, works quite simply! (Editor’s note: It is worth noting some of the resulting caveats. For example, depending on the type of reference to an instance of
A you might see id as a string or an int. It isn’t an override per-se but rather a shadow field.)
Okay, so no inheritance, and composition is kind of cumbersome in this instance.
That finally brought us to the problem at hand. Was there a way to do inheritance of fields without inheriting fields? We would like a way for a set of common fields to be shared among a set of sub-types (to reduce code complexity), and also have the field-types to be uniquely configured in these sub-types (but without needing a series of new getters and setters). The following solutions were recommended and explored:
- Establishing a sensible data model before trying any form of abstraction, i.e. question those above needs. Perhaps, types that share fields should all just be the same type, with optional fields. Or perhaps, each type needs to be defined as unique, and some code duplication is fine. However, in spite of being our ultimate approach, these do not fully address how to functionally compose common properties.
- Using object expressions. This fails our intent as the value of
a.idbelow in the case returns a string, not an int as intended. The overriding of the value thus doesn’t quite work in this case.
JsonValue. This approach won’t work for the particular issue at hand. To convert the original type to a
JsonObject, modifying and converting it back on every version change defeats the purpose of having a common unchanging type in the first place. However, this could be possible in other cases by using lenses. [Editor’s note: in this case, we would have a lens for the id field and could have different lenses for different types of IDs or one that returns a union.]
Finally, a few observations:
- The above information is a prime example of over-engineering too early. The solution we came to (to wait to have a better understanding of how the code will evolve), is a clear indication of why clever solutions are best applied when appropriate rather than everywhere possible.
- Some functional languages allow for very easy OOP features like inheritance. I was expecting inheritance to be impossible/extremely cumbersome in F#, but it turns out to be one of the easiest to implement.Thus, while the general ethos is anti-OOP features, I doubt they will be removed from F# anytime soon.
- The more we looked for methods to implement the above, the more we found. For the sake of time constraints, I stayed with the above 3 or 4 approaches, but if there are any that got missed, please let us know. (Editor’s note: like “ using a generic to parameterize the field type”)
- Also I recommend this wiki article
Good luck and happy transferring!