DEV Community

Cover image for Embrace immutability (Programming & Infrastructure)
Jorge Tovar
Jorge Tovar

Posted on • Updated on

Embrace immutability (Programming & Infrastructure)

The future of Software programming is immutable code.

Mutability 🚨

If you are a programmer, and you have some experience with Languages like Java, Python, and Go likely make you aware of the benefits and drawbacks of mutability. The problems associated with mutability are numerous, including:

Increasing Complexity: Which function changed the value? Why is this property null?
Concurrency: Multiple threads modifying the same variable simultaneously
With immutable data structures, it's possible to share data without concerns between threads and functions. Even in the infrastructure world, the norm used to involve deploying servers and constantly updating and modifying them in place. Consequently, it became challenging to keep track of all the changes and create new environments from scratch.

Immutable infrastructure represents another paradigm shift in infrastructure where servers are never modified. If an update is required, new servers are built and provisioned to replace the old ones.

In the book 'Effective Java,' the author advocates using the final keyword in methods and classes. Why? The final keyword is a non-access modifier used for classes, attributes, and methods, which renders them non-changeable (impossible to inherit or override). It proves useful when you want a variable to always store the same value, such as PI.

Languages like Java, Go, and Python default to having mutable objects, making it much harder to reason about the problem at hand. The same scenario applies in the infrastructure world, with configuration management tools like Chef, Puppet, and Ansible that typically default to a mutable infrastructure paradigm.

Immutability comes to the rescue, inspired by functional programming, where variables are immutable. Once you set a variable to a value, it can never be changed. If an update is needed, a new variable is created.

When values don't change, it’s easier to reason about your code.

Code example (Go/Clojure)

The good news is that is possible to overcome this problem! 😎

Go Pointers

  • Pass values instead of references
package main

import (
    "fmt"
    "math/rand"
    "strconv"
    "strings"
)

type Book struct {
    Author string
    Title  string
    Pages  int
}

func (book Book) GetPagesWithCode() string {
    var n = rand.Intn(200)
    book.Pages -= n
    return "The " + strings.ToUpper(book.Title) + " book has " + strconv.Itoa(book.Pages) + " pages"
}

func main() {

    var book = Book{Author: "Rich Hickey", Title: "Clojure for the Brave & True", Pages: 232}
    fmt.Printf("1) %+v\n", book)
    changeAuthor(&book, "Daniel Higginbotham")
    fmt.Printf("2) %+v\n", book)
    fmt.Println(book.GetPagesWithCode())
    fmt.Printf("3) %+v\n", book)

}

func changeAuthor(book *Book, author string) {
    book.Author = author
}

Enter fullscreen mode Exit fullscreen mode

Output:
Even when we updated the pages in the method, the data remain the same.

1) {Author:Rich Hickey Title:Clojure for the Brave & True Pages:232}
2) {Author:Daniel Higginbotham Title:Clojure for the Brave & True Pages:232}
The CLOJURE FOR THE BRAVE & TRUE book has 151 pages
3) {Author:Daniel Higginbotham Title:Clojure for the Brave & True Pages:232}

Clojure

  • Create functions that return new values
(ns main.core
  (:require [clojure.string :as s])
  (:gen-class)
  )

(defn get-pages-with-code [book]
  (str "The " (s/upper-case (:title book)) " book has " (:pages book) " pages")
  )

(defn change-author [book author]
  (assoc book :author author))


(defn -main
  [& args]
  (let [book {:author "Rich Hickey", :title "Clojure for the Brave & True", :pages 232}
        new-book (change-author book "Daniel Higginbotham")]
    (println "1)" book)
    (println "2)" new-book)
    (println "3)" (get-pages-with-code book))
    )
  )
Enter fullscreen mode Exit fullscreen mode

Output:
The data is never mutated, we just create new values

1) {:author Rich Hickey, :title Clojure for the Brave & True, :pages 232}
2) {:author Daniel Higginbotham, :title Clojure for the Brave & True, :pages 232}
3) The CLOJURE FOR THE BRAVE & TRUE book has 232 pages

Terraform

  • Deploy new servers instead of updating current ones
resource "aws_instance" "george_server" {
  ami           = "ami-0fb653ca2d3203ac1"
  instance_type = "t2.micro"
}
Enter fullscreen mode Exit fullscreen mode

A completely new server

resource "aws_instance" "george_server" {
  ami           = "ami-0CCC3ca2d32CCC1"
  instance_type = "t2.micro"
}
Enter fullscreen mode Exit fullscreen mode

Because servers never change, it’s a lot easier to reason about what’s deployed.

Conclusion

Controlling which parts of your program can make changes to variables and objects will allow you to write more robust and predictable software.

Embrace immutability πŸ”₯

severity = :mild
error_message = "OH GOD! IT'S A DISASTER! WE'RE "
if severity == :mild
  error_message = error_message + "MILDLY INCONVENIENCED!"
else
  error_message = error_message + "DOOOOOOOMED!"
end
You might be tempted to do something similar in Clojure:
Enter fullscreen mode Exit fullscreen mode
(def severity :mild)
(def error-message "OH GOD! IT'S A DISASTER! WE'RE ")
(if (= severity :mild)
  (def error-message (str error-message "MILDLY INCONVENIENCED!"))
  (def error-message (str error-message "DOOOOOOOMED!")))
Enter fullscreen mode Exit fullscreen mode

Top comments (0)