Reputation: 1520
I am writing a F# program to manage a list of items. All items have a name and a price. There are food items with an expiration date and non-food items with a warranty. My first attempt at modeling this domain looks like so:
type Info = {Name : string; Price : decimal;}
type Item =
| Food of Info * DateTime
| NonFood of Info * TimeSpan
let increasePrice item diff =
let modifyPrice info =
{info with Price = info.Price + diff}
match item with
| Food (info,expirationDate) -> Food ( modifyPrice info,expirationDate)
| NonFood (info,warranty) -> NonFood ( modifyPrice info,warranty)
But I am not sure if this is the right way of doing it. As you may see this was modeled with the OO concept of one (abstract) base class and two derived subclasses in mind. I am especially not happy with the fact that the function increasePrice
has to distinguish between the two types of Item
s even though it does only modfiy a general attribute of an Item
.
Is there maybe a better way to design such a list of items using another approach ?
Upvotes: 1
Views: 89
Reputation: 3470
Another way is simply to deliberately duplicate fields, like so. It is an approach that is not that uncommon.
type Food = { Name: string; Price: decimal; ExpirationDate: DateTime }
type NonFood = { Name: string; Price: decimal; Warranty: TimeSpan }
type Item =
| Food of Food
| NonFood of NonFood
Generally there doesn't have to be anything wrong with duplicating fields like this, even if every kind of item has some fields in common. If it leads to code duplication or heavy duplication in types due to many DU cases and/or many equal fields, then it's time to consider another approach. Otherwise, this approach can actually contribute to considerably simpler code - easier to reason about, easier to read.
It frequently actually doesn't matter whether fields Name and Price are the same fields in a common structure, or distinct fields with the same name in separate DUs. Only if some form of code duplication arises too frequently do you have to consider whether another approach would be better.
So this is not necessarily a good solution in your particular case, where I assume the number of item kinds and/or fields will perhaps not be that limited.
You will simply have to judge case by case.
Upvotes: 1
Reputation: 80744
This approach is generally ok. Functional programming usually invites much less ceremony and dogmatic thinking than OO. Concerns like "function has to distinguish ... even though ..." are normally not taken as worthy of attention, unless there is some actual practical reason.
Still, if (for some practical reasons) you'd like your food and non-food items to look similar, except in situations where their difference actually matters, one way to achieve that is to nest your data structures the other way around:
type Item = { Name: string; Price: decimal; kind: ItemKind }
type ItemKind = Food of DateTime | NonFood of TimeSpan
let increasePrice item diff = { item with Price = item.Price + diff }
Which approach is "better" is unclear. Both of them have pros and cons, and the choice depends on how you're going to use these types (and the function) down the line.
Upvotes: 4