Suppose I have a type like this:
type Season =
| Spring
| Summer
| Autumn
| Winter
I want to have a function next
that returns the next season:
let next s =
match s with
| Spring -> Summer
| Summer -> Autumn
| Autumn -> Winter
| Winter -> Spring
There are two places I can put this function.
In a named module:
module Season =
let next s =
match s with
| Spring -> Summer
| Summer -> Autumn
| Autumn -> Winter
| Winter -> Spring
Or as a static member on the type:
type Season =
| Spring
| Summer
| Autumn
| Winter
with
static member next s =
match s with
| Spring -> Summer
| Summer -> Autumn
| Autumn -> Winter
| Winter -> Spring
What are the reasons to favour each approach?
We can access the static member function using the class name or class' objects. If the static member function accesses any non-static data member or non-static member function, it throws an error. Here, the class_name is the name of the class.
A static member function can be called even if no objects of the class exist and the static functions are accessed using only the class name and the scope resolution operator ::. A static member function can only access static data member, other static member functions and any other functions from outside the class.
Quick A: Yes, and there are no ambiguity with static members.
A static member function is a special member function, which is used to access only static data members, any other normal data member cannot be accessed through static member function. Just like static data member, static member function is also a class function; it is not associated with any class object.
Ultimately that's a domain modelling judgement call and there's no one definite correct answer here. What matters is how each choice affects readability and maintainability of the code.
I tend to favor static members for functionality that is highly "cohesive" with the type, more so than any particular piece of business logic code. Think Parse
functions or smart constructors/factory methods. The general approach being that if I were to refactor the code by moving the type somewhere else, those would be the functions I'd definitely want to move together with it. Having them as static members also helps discoverability through intellisense, as you only need to know the name of the type to find them.
On the other hand, I'd use a module to house business logic that represents some abstract process, and if the function in question is somehow specific to that business logic and unlikely to be useful outside of it, then I'd go with a function in a module even if it's still somewhat type specific. For instance, a very purpose-specific parser that's only useful as part of this one workflow for legacy reasons would be a let-bound function rather than a static member, because other clients that use that type generally shouldn't even know about that function.
In your case, I'd go with the static member Next
if it makes sense for it to be used in multiple different modules in your context - if being able to cycle through Seasons
is a fundamental quality that defines what a Season
is.
Otherwise if you just have a single module, let's say WeatherPatterns
, that adjusts rainfall based on season changes, and that's the only part of your code where you care about cycling through Seasons
, then I'd put it as a function in that module.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With