DEV Community

Richard Wood
Richard Wood

Posted on

Immutability - something worth striving for

When I worked in C# at PartsTrader I started to see the similarities between what Domain Driven Design is looking for and what Functional Programming does. Or rather what FP has to do to be able to be useful in the real world.

I currently develop in Javascript for one client and functional language Elm for another. I've previously worked for two .Net companies. One of them - PartsTrader - was dead keen on DDD.

So I'm talking about separating out pure functions from side effects. In DDD the idea is to circumscribe business logic, keeping all the IO and external interfaces outside the circle.

Whamo, when you look at a functional environment such as Elm, you have all the pure functions segregated away from the messy IO and external javascript functions.

The difference is that in Elm this split is mandatory. In DDD and object oriented languages it's a voluntary design decision with some serious book reading available to convince you that you are doing the right thing lol.

However, it still comes back to immutability. Functional programming gives you this off the bat. In non-functional languages it is still a great idea, but you have to choose to do it. The benefits are that your code is easier to debug as what goes in and comes out remains constant at every level.

In Elm the entire code is immutable - think of it as one big function that is called as needed. Anything side-effecty will be done by the runtime and then the function called again.

This has some interesting benefits. If you want to see what your programme is doing, just look at the big 'update' function that is at the bottom of this tree and anything it delegates it to. With the strict typing on top of that, if you make any changes that disrupt things you find out very quickly on compile, and the error messages just 'know' so much about what you are doing.

Which is not to disparage .Net's Visual Studio when using C#. It 'knows' a hell of a lot before you even get to compile thanks to the work that has gone into it over the years from some very smart people.

In non-functional languages it is also good practice to pass in any values that may change randomly so that you still have unit-testable functions - dependency injection. In Elm, functions that call side effects don't return things back into the code and so don't impact the return value. They go off to the runtime, which then returns values through the message update channel as if some magic fairy generated them.

Anyway, developing in Javascript is where the rubber hits the road for this discussion. On the one hand it is a functional language in that functions are first class and references to them can be passed around. On the other hand it is so loose that you can have side effects wherever you like. You really don't know with some functions what might come out the other end. Tightening that up requires a fair bit of overhead construction.

I am working on an old JS code base that I've largely converted to ES6. While I've brought in Webpack, I've shied away from introducing any of the new frameworks like React and Angular - both of which I have used before. I use a bit of native JSX as a short cut for templating forms and menus, but that's another story.

With ordinary JS you can still take a strategy of making things as immutable as possible. Again it means segregating out anything that is a side effect until your functions become pure.

In my case I'd like to start to reorganise the codebase so that, well, it looks more like an Elm structure with a tree of update logic updating a model and a set of view functions that simply reflect the changes in the model - all as pure as possible. I'm still working through how best to do that in combination with the heavy use of Mapbox and Leaflet in the app.

There are also times in the nitty gritty when imperative code in Javascript is just easier to understand and quicker to achieve, and being pragmatic is certainly a good quality. Perhaps best to leave a comment in such functions that they should not be extended into incorporating side effects and make sure not to pass anything external in.

I believe that immutability and segregation of side effects is one of the core goals worth striving for in software design. If it can't be achieved immediately it should at least be regarded as the preferred direction.

I am confident that by taking this approach something easier to read, maintain and add to will come of it.

Top comments (3)

Collapse
 
kspeakman profile image
Kasey Speakman • Edited

Thanks for the article!

I recently discovered a couple of interesting organizational tidbits for MVU projects that might be of interest to you.

For cases where you want to use MVU in a side-effectful language, I stumbled upon a strategy to isolate side effects. This allows init and update to stay pure/testable and just declare what side effects they need to run. Then a separate function is responsible for performing the declared side effects.

The other discovery for me was about the organization of MVU projects. When I started using Elm, it was espousing The Elm Architecture. Which basically packaged Msg/Model/View/Update for each small piece of the app. However, this quickly proved problematic as there are UI actions that sometimes cross different pieces. Such as switching pages in a SPA. This gave rise to an OutMsg pattern to send messages to different parts, but ultimately was a bit tedious to use. So the last advice I heard is "flat is better than nested". We did this and ended up with giant Msg types and giant update statements. It works fine, but it feels a bit disorganized, especially when you consider swathes of messages start with the same prefix: CourseSearchInput, CourseSearchSubmit, CourseSearchReset, etc. And having to Ctrl-F to find a specific one.

So it finally hit me that Model/View/Update actually has no problem being composed from pieces. It is Msgs that put a wrinkle in things, because any Msg might need to be sent from anywhere else. (Including from external effects.) So Msgs interact with but are independent of the MVU structure. (This also makes it more clear why Msg isn't part of the MVU acronym, which always bothered me.)

So with my latest project, I have started to go back to The Elm Architecture, but only for the MVU parts, not for Msg. All Msgs are defined first and every other part of the app can access all of them. Optionally, you can also choose to organize Msgs hierarchically instead of putting them in one big flat list. Since they are independently defined from the MVU parts, it is no longer a problem to do this.

Not sure if that helps, but I thought I would share what I learned. Cheers!

Collapse
 
rwoodnz profile image
Richard Wood

Hi Kasey,

My apology I didn't reply at the time as I wasn't across the OutMsg pattern and had less experience with the issues you describe. Now I re-read your comment and it makes sense to me.

I attended Elm Europe again this year and saw a couple of presentations where it came up, including an attempt to provide an actor approach to component structure and messages.

My concern with various attempts is when we get scaffolding code doing delegation and tricks that have no meaning otherwise in the application, making it an issue for future users of the code. The Elm language appears to be simply missing some useful structures built-in that would help componentise.

I agree with keeping all messages in one place. I recently did a project where I have them in the same file but broke them up by using a msg wrapper and msg types for each component. This works well for both bug finding and readability.

You can see my blog post about it here: Keeping it simple beyond a basic Elm app

Collapse
 
kspeakman profile image
Kasey Speakman • Edited

Thanks for the link to the article. We use some of those same patterns.

Lately, we have started to match both model and msg in our update function. Since messages are top level, you can technically receive a message which is not valid for the current view. This accounts for that case and also nicely unwraps the page-level model and msg all at once.

// F#, using Elmish
match model.Page, msg with
...
| CoursePage pageModel, CourseMsg pageMsg ->
    let nPageModel, effects = Course.update pageMsg pageModel
    { model with Page = CoursePage nPageModel }, effects
| CoursePage pageModel, badMsg ->
    // log?
    model, []

We handle page changes within the same structure.

match model.Page, msg with
| _, OpenCoursePage courseId ->
    let nPageModel, effects = Course.init courseId
    { model with Page = CoursePage nPageModel }, effects
...

This works with navigation. For example, it is pretty easy to parse the URL /course/abc123 into OpenCoursePage "abc123". We ended up with too many OpenXX messages, so they were organized under a wrapper message tag like OpenPage .... Then a central function is responsible for calling the correct page init.

Since these are recurring patterns, I've tried to parameterize it different ways. But that requires some extra abstraction/scaffolding. And then that abstraction must be learned by other devs (including Future Me when I've been away from the code for 6 months). The team ultimately deemed it not worth it compared to an extra line or 2 of very clear (and copy/paste-able) boilerplate.

We will continue to try different things and refine how we organize our MVU code. But this is where we are for now. We have used it for top-level pages and for 2nd level sub-pages.