Code, code, code it’s everywhere! As developers we have to read, understand and maintain it. Code that is predictable is code that can be understood just by reading it. On the contrast unpredictable code is code that is either ambiguous or misleading; in any case it will need to be executed somehow to see what it does. This post will show you how to write predictable code.
- Assumed knowledge of an OOP language like C# or similar
- A simple understanding of a functional programming language. (Code examples are in F#)
Problem: mutating state
An interface with some math operations for a list. I’ve modeled with an interface since the premise of OOP is about abstractions and encapsulation.
1: 2: 3: 4: 5:
First cut of the tests (for using the Math implementation of IMath)
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12:
After code refactor
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
Clearly this is some unpredictable code; the refactor should have worked. At first glance it appears that the interface for IMath is the problem. It will return you a new list. Unfortunately, after the refactor, it is clear that it modifies the list. The problem is the List class. It is mutable in every way. The size can be changed, the elements can be changed. It’s open for everyone.
Mutability leads to unpredictable code
- Mutation/Mutability (definition): when a variable or object inside an instance can be changed/replaced
- Mutation makes it hard to model time
- Mutation makes it hard to keep constraints with polymorphism (see wikipedia link below)
- Liskov substitution principle is followed by using immutable classes and is even stated on wikipeida
Mutability is a key issue here. If Square and Rectangle had only getter methods (i.e. they were immutable objects), then no violation of LSP could occur.
Liskov substitution principle
What to do then
- Avoid mutable classes. Copy the data and change the values during the copy, returning a new instance.
- To make a class immutable in C#: GetHashCode and Equals must be overriden
- Use a Functional language (F#), you get this all for free while still being on .NET
How does F# do this
Functional programming is about using functions + data to model the domain/behaviour. Encapsulation and abstractions are not goal. Common patterns are still factored out but these a for a another post. Our problem above written in F# looks like this (with the implementation):
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17:
Why does this work
- The list type in F# (which is different to C#’s list) is immutable so the result is copied and changed.
- It’s harder to create mutable objects:
- F# creates constants by default (these are called bindings, and means the reference can’t be changed. If the object is not immutable then elements inside the object can be change eg. an array)
- A variable in F# requires the
mutablekeyword (It’s more work, and some IDEs highlight the keyword to be explicit)
What’s the point
- immutability makes the code predictable. It can understood without executing it.
Mutability and unpredictable code requires you execute the code somehow to check it does what you think.
~~ Use F# if you don’t like writing boilerplate code and/or want your code to just work ~~ Use F#
NB: I claimed that FP does not have abstractions as goal. FP makes great leaps to reduce boilerplate code and encourage code reuse, but takes a mathematical approach to do this. Higher order functions and Monads (eg. State Monad) are examples of these.
val int : value:'T -> int (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.int
type int = int32
Full name: Microsoft.FSharp.Core.int
type int<'Measure> = int
Full name: Microsoft.FSharp.Core.int<_>
Full name: Microsoft.FSharp.Collections.list<_>
type List<'T> =
| (  )
| ( :: ) of Head: 'T * Tail: 'T list
member GetSlice : startIndex:int option * endIndex:int option -> 'T list
member Head : 'T
member IsEmpty : bool
member Item : index:int -> 'T with get
member Length : int
member Tail : 'T list
static member Cons : head:'T * tail:'T list -> 'T list
Full name: Microsoft.FSharp.Collections.List<_>
Full name: Microsoft.FSharp.Collections.List.map