Refactoring to
Immutability
@KevlinHenney
Functional programming
Functional programming
typically avoids using
mutable state.
https://wiki.haskell.org/Functional_programming
https://xkcd.com/1270/
Functional programming
combines the flexibility
and power of abstract
mathematics with the
intuitive clarity of
abstract mathematics.
Rien n'est plus
dangereux qu'une
idée, quand on n'a
qu'une idée.
Émile-Auguste Chartier
Nothing is more
dangerous than an
idea, when you
have only one idea.
Émile-Auguste Chartier
String
Asking a question
should not change
the answer.
To keep our C++ API boundary simple, we [...] adopted
one-way data flow. The API consists of methods to
perform fire-and-forget mutations and methods to
compute view models required by specific views.
To keep the code understandable, we write functional
style code converting raw data objects into immutable
view models by default. As we identified performance
bottlenecks through profiling, we added caches to avoid
recomputing unchanged intermediate results.
The resulting functional code is easy to maintain,
without sacrificing performance.
https://code.facebook.com/posts/498597036962415/under-the-hood-building-moments/
Excel is the world's
most popular
functional language
Simon Peyton-Jones
I want code to
be reasonable
As a programmer
I want code to
be reasonable
reasonable
raisonnable
raison
raisonner
As a programmer
I want code to
be reasonable
so that I can
reason about it
A large fraction of the flaws in software development
are due to programmers not fully understanding all
the possible states their code may execute in.
In a multithreaded environment, the lack of
understanding and the resulting problems are
greatly amplified, almost to the point of panic if you
are paying attention.
John Carmack
http://www.gamasutra.com/view/news/169296/Indepth_Functional_programming_in_C.php
Mutable
Immutable
Unshared Shared
Unshared mutable
data needs no
synchronisation
Unshared immutable
data needs no
synchronisation
Shared mutable
data needs
synchronisation
Shared immutable
data needs no
synchronisation
Mutable
Immutable
Unshared Shared
Unshared mutable
data needs no
synchronisation
Unshared immutable
data needs no
synchronisation
Shared mutable
data needs
synchronisation
Shared immutable
data needs no
synchronisation
The Synchronisation Quadrant
Architecture represents the
significant design decisions
that shape a system, where
significant is measured by
cost of change.
Grady Booch
µονόλιθος
There is a beautiful angel in that block
of marble, and I am going to find it.
All I have to do is to knock off the
outside pieces of marble, and be very
careful not to cut into the angel with
my chisel.
George F Pentecost
"The Angel in the Marble"
Functional
Operational
Developmental
Functional
Operational
Developmental
Nothing is more
dangerous than an
idea, when you
have only one idea.
Émile-Auguste Chartier
Nothing is more
dangerous than
OO, when you have
only one object.
public class Clock
{
public static Clock Instance => ...
public TimeOfDay Now => ...
...
}
public void Example()
{
var now = Clock.Instance.Now;
...
}
public void Example(Clock clock)
{
var now = clock.Now;
...
}
public interface Clock
{
TimeOfDay Now => ...
}
public class ClockImpl : Clock
{
public static Clock Instance => ...
...
}
public interface IClock
{
TimeOfDay Now => ...
}
public class Clock : IClock
{
public static IClock Instance => ...
...
}
public interface IClock
{
TimeOfDay Now => ...
}
public class LocalClock : IClock
{
public static IClock Instance => ...
...
}
public interface Clock
{
TimeOfDay Now => ...
}
public class LocalClock : Clock
{
public static Clock Instance => ...
...
}
public void Example(Func<TimeOfDay> timeOfDay)
{
var now = timeOfDay();
...
}
public void Example(TimeOfDay now)
{
...
}
public class TimeOfDay
{
...
public int Hour
{
get ...
set ...
}
public int Minute
{
get ...
set ...
}
public void NextHour() ...
public void NextMinute() ...
}
public class TimeOfDay
{
private int minutes;
public const int minutesInHour = 60;
public const int hoursInDay = 24;
public const int minutesInDay = hoursInDay * minutesInHour;
private static int Wrap(int minutes)
{
return minutes % minutesInDay;
}
public int Hour
{
get
{
return minutes / minutesInHour;
}
set
{
if (value < 0 || value >= hoursInDay)
throw new ArgumentException();
minutes = Wrap(value * minutesInHour + Minute);
}
}
public int Minute
{
get
{
return minutes % minutesInHour;
}
set
{
if (value < 0 || value >= minutesInHour)
throw new ArgumentException();
minutes = Wrap(Hour * minutesInHour + value);
}
}
public void NextHour()
{
minutes = Wrap(minutes + minutesInHour);
}
public void NextMinute()
{
minutes = Wrap(minutes + 1);
}
}
public class TimeOfDay
{
...
public int Hour
{
get ...
set ...
}
public int Minute
{
get ...
set ...
}
public void NextHour() ...
public void NextMinute() ...
}
public class TimeOfDay
{
...
public int Hour
{
get ...
set ...
}
public int Minute
{
get ...
set ...
}
}
public class TimeOfDay
{
...
public int Hour
{
get ...
}
public int Minute
{
get ...
}
}
public class TimeOfDay
{
...
public int Hour => ...
public int Minute => ...
}
public class TimeOfDay
{
...
public TimeOfDay(int hour, int minute) ...
public int Hour => ...
public int Minute => ...
}
public class TimeOfDay
{
...
public TimeOfDay(int hour, int minute) ...
public int Hour => ...
public int Minute => ...
public TimeOfDay WithHour(int newHour) ...
public TimeOfDay WithMinute(int newMinute) ...
}
public class TimeOfDay
{
...
public TimeOfDay(int hour, int minute) ...
public int Hour => ...
public int Minute => ...
public TimeOfDay WithHour(int newHour) ...
public TimeOfDay WithMinute(int newMinute) ...
public TimeOfDay NextHour() ...
public TimeOfDay NextMinute() ...
}
public class TimeOfDay
{
...
public TimeOfDay(int hour, int minute) ...
public int Hour => ...
public int Minute => ...
public TimeOfDay WithHour(int newHour) ...
public TimeOfDay WithMinute(int newMinute) ...
public TimeOfDay NextHour() ...
public TimeOfDay NextMinute() ...
public class Builder
{
...
}
}
public class TimeOfDay
{
private readonly int minutes;
public const int minutesInHour = 60;
public const int hoursInDay = 24;
public const int minutesInDay = hoursInDay * minutesInHour;
private TimeOfDay(int minutes)
{
this.minutes = minutes % minutesInDay;
}
public TimeOfDay(int hour, int minute)
{
if (hour < 0 || hour >= hoursInDay || minute < 0 || minute >= minutesInHour)
throw new ArgumentException();
minutes = hour * minutesInHour + minute;
}
public int Hour => minutes / minutesInHour;
public int Minute => minutes % minutesInHour;
public TimeOfDay WithHour(int newHour) => new TimeOfDay(newHour, Minute);
public TimeOfDay WithMinute(int newMinute) => new TimeOfDay(Hour, newMinute);
public TimeOfDay NextHour() => new TimeOfDay(minutes + minutesInHour);
public TimeOfDay NextMinute() => new TimeOfDay(minutes + 1);
...
}
try {
Integer.parseInt(time.substring(0, 2));
}
catch (Exception x) {
return false;
}
if (Integer.parseInt(time.substring(0, 2)) > 12) {
return false;
}
...
if (!time.substring(9, 11).equals("AM") &
!time.substring(9, 11).equals("PM")) {
return false;
}
Burk Hufnagel
"Put the Mouse Down and Step Away from the Keyboard"
Burk Hufnagel
"Put the Mouse Down and Step Away from the Keyboard"
return time.matches("(0[1-9]|1[0-2]):[0-5][0-9]:[0-5][0-9] ([AP]M)");
Je n'ai fait celle-ci plus
longue que parce que je
n'ai pas eu le loisir de la
faire plus courte.
Blaise Pascal
I have made this [letter]
longer than usual
because I have not had
time to make it shorter.
Blaise Pascal
// Get the unique surnames in uppercase of the
// first 15 book authors that are 50 years old
// or older?
library.stream()
.map(book -> book.getAuthor())
.filter(author -> author.getAge() >= 50)
.limit(15)
.map(Author::getSurname)
.map(String::toUpperCase)
.distinct()
.collect(toList())
// Get the first 15 unique surnames in
// uppercase of the book authors that are 50
// years old or older.
library.stream()
.map(book -> book.getAuthor())
.filter(author -> author.getAge() >= 50)
.map(Author::getSurname)
.map(String::toUpperCase)
.distinct()
.limit(15)
.collect(toList())
// Get the unique surnames in uppercase of the
// first 15 book authors that are 50 years old
// or older.
library.stream()
.map(book -> book.getAuthor())
.filter(author -> author.getAge() >= 50)
.distinct()
.limit(15)
.map(Author::getSurname)
.map(String::toUpperCase)
.distinct()
.collect(toList())
// Get the unique surnames in uppercase of the
// first 15 book authors that are 50 years old
// or older.
library.stream()
.map(book -> book.getAuthor())
.distinct()
.filter(author -> author.getAge() >= 50)
.limit(15)
.map(Author::getSurname)
.map(String::toUpperCase)
.distinct()
.collect(toList())
// Get the unique surnames in uppercase of the
// first 15 book authors that are 50 years old
// or older?
List<Author> authors = new ArrayList<Author>();
for (Book book : library)
{
Author author = book.getAuthor();
if (author.getAge() >= 50)
{
authors.add(author);
if (authors.size() == 15)
break;
}
// Get the unique surnames in uppercase of the
// first 15 book authors that are 50 years old
// or older?
List<Author> authors = new ArrayList<Author>();
for (Book book : library)
{
Author author = book.getAuthor();
if (author.getAge() >= 50)
{
authors.add(author);
if (authors.size() == 15)
break;
}
}
List<String> result = new ArrayList<String>();
for(Author author : authors)
{
String name = author.getSurname().toUpperCase();
if (!result.contains(name))
result.add(name);
}
// Get the first 15 unique surnames in
// uppercase of the book authors that are 50
// years old or older.
List<String> result = new ArrayList<String>();
for (Book book : library)
{
Author author = book.getAuthor();
if (author.getAge() >= 50)
{
String name = author.getSurname().toUpperCase();
if (!result.contains(name))
{
result.add(name);
if (result.size() == 15)
break;
}
}
}
// Get the unique surnames in uppercase of the
// first 15 book authors that are 50 years old
// or older.
List<Author> authors = new ArrayList<Author>();
for (Book book : library)
{
Author author = book.getAuthor();
if (author.getAge() >= 50 && !authors.contains(author))
{
authors.add(author);
if (authors.size() == 15)
break;
}
}
List<String> result = new ArrayList<String>();
for(Author author : authors)
{
String name = author.getSurname().toUpperCase();
if (!result.contains(name))
result.add(name);
}
Try to leave out the part
that readers tend to skip.
Elmore Leonard
When it is not necessary
to change, it is necessary
not to change.
Lucius Cary

Refactoring to Immutability

  • 1.
  • 3.
  • 4.
    Functional programming typically avoidsusing mutable state. https://wiki.haskell.org/Functional_programming
  • 5.
    https://xkcd.com/1270/ Functional programming combines theflexibility and power of abstract mathematics with the intuitive clarity of abstract mathematics.
  • 6.
    Rien n'est plus dangereuxqu'une idée, quand on n'a qu'une idée. Émile-Auguste Chartier
  • 7.
    Nothing is more dangerousthan an idea, when you have only one idea. Émile-Auguste Chartier
  • 8.
  • 10.
    Asking a question shouldnot change the answer.
  • 11.
    To keep ourC++ API boundary simple, we [...] adopted one-way data flow. The API consists of methods to perform fire-and-forget mutations and methods to compute view models required by specific views. To keep the code understandable, we write functional style code converting raw data objects into immutable view models by default. As we identified performance bottlenecks through profiling, we added caches to avoid recomputing unchanged intermediate results. The resulting functional code is easy to maintain, without sacrificing performance. https://code.facebook.com/posts/498597036962415/under-the-hood-building-moments/
  • 15.
    Excel is theworld's most popular functional language Simon Peyton-Jones
  • 16.
    I want codeto be reasonable
  • 17.
    As a programmer Iwant code to be reasonable
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
    As a programmer Iwant code to be reasonable so that I can reason about it
  • 23.
    A large fractionof the flaws in software development are due to programmers not fully understanding all the possible states their code may execute in. In a multithreaded environment, the lack of understanding and the resulting problems are greatly amplified, almost to the point of panic if you are paying attention. John Carmack http://www.gamasutra.com/view/news/169296/Indepth_Functional_programming_in_C.php
  • 24.
    Mutable Immutable Unshared Shared Unshared mutable dataneeds no synchronisation Unshared immutable data needs no synchronisation Shared mutable data needs synchronisation Shared immutable data needs no synchronisation
  • 25.
    Mutable Immutable Unshared Shared Unshared mutable dataneeds no synchronisation Unshared immutable data needs no synchronisation Shared mutable data needs synchronisation Shared immutable data needs no synchronisation The Synchronisation Quadrant
  • 27.
    Architecture represents the significantdesign decisions that shape a system, where significant is measured by cost of change. Grady Booch
  • 29.
  • 30.
    There is abeautiful angel in that block of marble, and I am going to find it. All I have to do is to knock off the outside pieces of marble, and be very careful not to cut into the angel with my chisel. George F Pentecost "The Angel in the Marble"
  • 33.
  • 34.
  • 36.
    Nothing is more dangerousthan an idea, when you have only one idea. Émile-Auguste Chartier
  • 37.
    Nothing is more dangerousthan OO, when you have only one object.
  • 39.
    public class Clock { publicstatic Clock Instance => ... public TimeOfDay Now => ... ... }
  • 40.
    public void Example() { varnow = Clock.Instance.Now; ... }
  • 41.
    public void Example(Clockclock) { var now = clock.Now; ... }
  • 42.
    public interface Clock { TimeOfDayNow => ... } public class ClockImpl : Clock { public static Clock Instance => ... ... }
  • 43.
    public interface IClock { TimeOfDayNow => ... } public class Clock : IClock { public static IClock Instance => ... ... }
  • 44.
    public interface IClock { TimeOfDayNow => ... } public class LocalClock : IClock { public static IClock Instance => ... ... }
  • 45.
    public interface Clock { TimeOfDayNow => ... } public class LocalClock : Clock { public static Clock Instance => ... ... }
  • 46.
    public void Example(Func<TimeOfDay>timeOfDay) { var now = timeOfDay(); ... }
  • 47.
  • 48.
    public class TimeOfDay { ... publicint Hour { get ... set ... } public int Minute { get ... set ... } public void NextHour() ... public void NextMinute() ... }
  • 49.
    public class TimeOfDay { privateint minutes; public const int minutesInHour = 60; public const int hoursInDay = 24; public const int minutesInDay = hoursInDay * minutesInHour; private static int Wrap(int minutes) { return minutes % minutesInDay; } public int Hour { get { return minutes / minutesInHour; } set { if (value < 0 || value >= hoursInDay) throw new ArgumentException(); minutes = Wrap(value * minutesInHour + Minute); } } public int Minute { get { return minutes % minutesInHour; } set { if (value < 0 || value >= minutesInHour) throw new ArgumentException(); minutes = Wrap(Hour * minutesInHour + value); } } public void NextHour() { minutes = Wrap(minutes + minutesInHour); } public void NextMinute() { minutes = Wrap(minutes + 1); } }
  • 50.
    public class TimeOfDay { ... publicint Hour { get ... set ... } public int Minute { get ... set ... } public void NextHour() ... public void NextMinute() ... }
  • 51.
    public class TimeOfDay { ... publicint Hour { get ... set ... } public int Minute { get ... set ... } }
  • 52.
    public class TimeOfDay { ... publicint Hour { get ... } public int Minute { get ... } }
  • 53.
    public class TimeOfDay { ... publicint Hour => ... public int Minute => ... }
  • 54.
    public class TimeOfDay { ... publicTimeOfDay(int hour, int minute) ... public int Hour => ... public int Minute => ... }
  • 55.
    public class TimeOfDay { ... publicTimeOfDay(int hour, int minute) ... public int Hour => ... public int Minute => ... public TimeOfDay WithHour(int newHour) ... public TimeOfDay WithMinute(int newMinute) ... }
  • 56.
    public class TimeOfDay { ... publicTimeOfDay(int hour, int minute) ... public int Hour => ... public int Minute => ... public TimeOfDay WithHour(int newHour) ... public TimeOfDay WithMinute(int newMinute) ... public TimeOfDay NextHour() ... public TimeOfDay NextMinute() ... }
  • 57.
    public class TimeOfDay { ... publicTimeOfDay(int hour, int minute) ... public int Hour => ... public int Minute => ... public TimeOfDay WithHour(int newHour) ... public TimeOfDay WithMinute(int newMinute) ... public TimeOfDay NextHour() ... public TimeOfDay NextMinute() ... public class Builder { ... } }
  • 58.
    public class TimeOfDay { privatereadonly int minutes; public const int minutesInHour = 60; public const int hoursInDay = 24; public const int minutesInDay = hoursInDay * minutesInHour; private TimeOfDay(int minutes) { this.minutes = minutes % minutesInDay; } public TimeOfDay(int hour, int minute) { if (hour < 0 || hour >= hoursInDay || minute < 0 || minute >= minutesInHour) throw new ArgumentException(); minutes = hour * minutesInHour + minute; } public int Hour => minutes / minutesInHour; public int Minute => minutes % minutesInHour; public TimeOfDay WithHour(int newHour) => new TimeOfDay(newHour, Minute); public TimeOfDay WithMinute(int newMinute) => new TimeOfDay(Hour, newMinute); public TimeOfDay NextHour() => new TimeOfDay(minutes + minutesInHour); public TimeOfDay NextMinute() => new TimeOfDay(minutes + 1); ... }
  • 60.
    try { Integer.parseInt(time.substring(0, 2)); } catch(Exception x) { return false; } if (Integer.parseInt(time.substring(0, 2)) > 12) { return false; } ... if (!time.substring(9, 11).equals("AM") & !time.substring(9, 11).equals("PM")) { return false; } Burk Hufnagel "Put the Mouse Down and Step Away from the Keyboard"
  • 61.
    Burk Hufnagel "Put theMouse Down and Step Away from the Keyboard" return time.matches("(0[1-9]|1[0-2]):[0-5][0-9]:[0-5][0-9] ([AP]M)");
  • 62.
    Je n'ai faitcelle-ci plus longue que parce que je n'ai pas eu le loisir de la faire plus courte. Blaise Pascal
  • 63.
    I have madethis [letter] longer than usual because I have not had time to make it shorter. Blaise Pascal
  • 66.
    // Get theunique surnames in uppercase of the // first 15 book authors that are 50 years old // or older? library.stream() .map(book -> book.getAuthor()) .filter(author -> author.getAge() >= 50) .limit(15) .map(Author::getSurname) .map(String::toUpperCase) .distinct() .collect(toList())
  • 67.
    // Get thefirst 15 unique surnames in // uppercase of the book authors that are 50 // years old or older. library.stream() .map(book -> book.getAuthor()) .filter(author -> author.getAge() >= 50) .map(Author::getSurname) .map(String::toUpperCase) .distinct() .limit(15) .collect(toList())
  • 68.
    // Get theunique surnames in uppercase of the // first 15 book authors that are 50 years old // or older. library.stream() .map(book -> book.getAuthor()) .filter(author -> author.getAge() >= 50) .distinct() .limit(15) .map(Author::getSurname) .map(String::toUpperCase) .distinct() .collect(toList())
  • 69.
    // Get theunique surnames in uppercase of the // first 15 book authors that are 50 years old // or older. library.stream() .map(book -> book.getAuthor()) .distinct() .filter(author -> author.getAge() >= 50) .limit(15) .map(Author::getSurname) .map(String::toUpperCase) .distinct() .collect(toList())
  • 71.
    // Get theunique surnames in uppercase of the // first 15 book authors that are 50 years old // or older? List<Author> authors = new ArrayList<Author>(); for (Book book : library) { Author author = book.getAuthor(); if (author.getAge() >= 50) { authors.add(author); if (authors.size() == 15) break; }
  • 72.
    // Get theunique surnames in uppercase of the // first 15 book authors that are 50 years old // or older? List<Author> authors = new ArrayList<Author>(); for (Book book : library) { Author author = book.getAuthor(); if (author.getAge() >= 50) { authors.add(author); if (authors.size() == 15) break; } } List<String> result = new ArrayList<String>(); for(Author author : authors) { String name = author.getSurname().toUpperCase(); if (!result.contains(name)) result.add(name); }
  • 73.
    // Get thefirst 15 unique surnames in // uppercase of the book authors that are 50 // years old or older. List<String> result = new ArrayList<String>(); for (Book book : library) { Author author = book.getAuthor(); if (author.getAge() >= 50) { String name = author.getSurname().toUpperCase(); if (!result.contains(name)) { result.add(name); if (result.size() == 15) break; } } }
  • 74.
    // Get theunique surnames in uppercase of the // first 15 book authors that are 50 years old // or older. List<Author> authors = new ArrayList<Author>(); for (Book book : library) { Author author = book.getAuthor(); if (author.getAge() >= 50 && !authors.contains(author)) { authors.add(author); if (authors.size() == 15) break; } } List<String> result = new ArrayList<String>(); for(Author author : authors) { String name = author.getSurname().toUpperCase(); if (!result.contains(name)) result.add(name); }
  • 75.
    Try to leaveout the part that readers tend to skip. Elmore Leonard
  • 76.
    When it isnot necessary to change, it is necessary not to change. Lucius Cary