As a beginner developer with Elm, I quickly ended up both loving the union types and the _ operator but founded a tad bit irritating the absence of Multicase matching until … Until I saw the light!
TL;DR?
Use the following pattern whenever you have some behaviour shared by several constructor of your union types and stay away from the _ operator!
type alias Props = { start: Date, stop: Date, assignee: String} | |
type Item | |
= Task Props | |
| SubTask Props | |
| Bug Props | |
| Epic Props | |
| ... | |
assignTo: String -> Item -> Item | |
assignTo person item = | |
let | |
changeAssignee props = {props | assignee = person} | |
in | |
case item of | |
Task p -> Task (changeAssignee p) | |
SubTask p -> SubTask (changeAssigne p) | |
... |
The problem with single case matching
Let's say I want to create a task tracking software. I might end up with this Item type to describe my model:
type Item | |
= Task Props | |
| SubTask Props | |
| Bug Props | |
| Epic Props | |
| ... |
As you can guess, a lot of operations are gonna require the same treatment for most kind of Item : assigning the item to somebody, changing the start or end date, adding/editing a description, … while there will be some that will differ. So how can we deal with that?
Using the underscore matching
When I encountered this problem my first idea was to use the _ to match all the kind of Item that didn't require a specific treatment for any given case ... of . There are two main problems with that approach: we can't get the properties and it introduce potential BUGS! And by that I mean problems and unexpected behaviour that won't be caught up by the compiler. But first let's see how it looks:
-- make some treatment on the tasks | |
toString: Item -> String | |
toString person item = | |
case item of | |
Task p -> ("task assigned to " ++ p.assignee) | |
_ -> "default printing" | |
-- note that we cannot access the Prop record of our items in the _ case |
Let's take back the Item example and let's pretend that we want to create a new kind of Item : milestone. Say milestones do not have start date but are just used as deadlines. Say my app is pretty developed and I am doing a lot of things on my Item and use the underscore matching a lot. By adding a new kind of Item in my union type I'd expect the compiler to throw a bunch of errors at my face directly which will not happen in that case: my new kind Milestone would already be matched by the _ so there will not be anything wrong that will be caught by the compiler. Yet I probably don' t want my Milestone items to be dealt with like any other items… So I would have to resolve to looking up for any _ and make sure that I adapt the code manually.
The replacement
Use let … in! (or make another helper function)… It really is as simple as that… instead of making multicase matching, put the common treatment in a function within the let ... in or put it outside if the treatment is consequent and use it afterwards:
assignTo : String -> Item -> Item | |
assignTo user task = | |
let | |
updateAssignee taskProperties = | |
{ taskProperties | assignee = Just user } | |
in | |
case task of | |
Task props -> | |
Task (updateAssignee props) | |
SubTask props -> | |
SubTask (updateAssignee props) | |
Bug props -> | |
Bug (updateAssignee props) | |
Epic props -> | |
Epic props -- i.e. remain unchanged |
And voilà ! It sounds pretty stupid but I hope it helps!
Here is a small "project" showing this… nothing fancy, only one message and one button.
Top comments (0)