Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a class fully immutable in Scala

I am trying to make the following class immutable. I know the theory of how to do this but I think my implementation is wrong. Can you help?

Thanks

Mutable class:

class BankAccount {
private var balance = 0
def deposit(amount: Int) {
    if (amount > 0)
    balance += amount
}
def withdraw(amount: Int): Int =
if (0 < amount && amount <= balance) {
    balance -= amount
    balance        
} else {
    error("insufficient funds")
}

Immutable Class

case class BankAccount(b:Int) {

private def deposit(amount: Int):BankAccount {
    if (amount > 0)
    {
        return BankAccount(amount)
    }

}
private def withdraw(amount: Int): BankAccount ={
    if (0 < amount && amount <= balance) {
        return BankAccount(b-amount)       
    } else {
        error("insufficient funds")
    }
}  

}

like image 580
user3704648 Avatar asked Dec 30 '16 17:12

user3704648


People also ask

How do you make a class immutable in Scala?

Instead of mutating the state, create new state with computed value and return it to the user. In the same way, check for funds and create new state and return it to the user. In functional programming it is very common to create new state instead of trying to mutate the old state.

Which is immutable in Scala?

In Scala, all number types, strings, and tuples are immutable.

What is an immutable variable in Scala?

In pure functional programming, only immutable values are used. In Scala this means: Only immutable collections classes are used, such as List, Vector, and the immutable Map and Set classes Using only immutable variables raises an interesting question: If everything is immutable, how does anything ever change?

How to use classes and objects in Scala programming?

This chapter takes you through how to use classes and objects in Scala programming. A class is a blueprint for objects. Once you define a class, you can create objects from the class blueprint with the keyword new.

What is immutable class in Java?

Immutable class in java means that once an object is created, we cannot change its content. In Java, all the wrapper classes (like Integer, Boolean, Byte, Short) and String class is immutable. We can create our own immutable class as well.

What is the difference between superclass and subclass in Scala?

So here the Point class is called superclass and the class Location is called subclass. Extending a class and inheriting all the features of a parent class is called inheritance but Scala allows the inheritance from just one class only.


2 Answers

First, the good news: your objects are almost immutable. Now, the bad news: they don't work.

The are only "almost" immutable because your class isn't final: I can extend it and override the methods to mutate some state.

Now, why doesn't it work? The most obvious bug is that in your deposit method, you return a new BankAccount that has its balance set to the amount that was deposited. So, you lose all the money that was in there before the deposit! You need to add the deposit to the balance, not replace the balance with the deposit.

There are also other problems: your deposit method has a return type of BankAccount, but it doesn't always return a BankAccount: if the amount is less than or equal to zero, it returns Unit. The most specific common supertype of BankAccount and Unit is Any, so your method actually returns Any. There are multiple ways to fix this, e.g. returning an Option[BankAccount], a Try[BankAccount], or an Either[SomeErrorType, BankAccount], or just throwing an exception. For my example, I'm simply going to ignore the validation altogether. (A similar problem exists in withdraw.)

Something like this:

final case class BankAccount(balance: Int) {
  private def deposit(amount: Int) = copy(balance = balance + amount)
  private def withdraw(amount: Int) = copy(balance = balance - amount)       
}

Note I am using the compiler-generated copy method for case classes that allows you to create a copy of an instance with only one field changed. In your particular case, you have only one field, but it's a good practice to get into.

So, that works. Or … does it? Well, no, actually, it doesn't! The problem is that we are creating new bank accounts … with money in them … we are creating new money out of thin air! If I have 100 dollars in my account, I can withdraw 90 of them, and I get returned a new bank account object with 10 dollars in it. But I still have access to the old bank account object with 100 dollars in it! So, I have two bank accounts with a total of 110 dollars plus the 90 I withdrew; I now have 200 dollars!

Solving this is non-trivial, and I will leave it for now.

In closing, I wanted to show you something that is a little bit close to how real-world banking systems actually work, by which I both mean "banking systems in the real-world, as in, before the invention of electronic banking", as well as "electronic banking systems as they are actually used", because surprisingly (or not), they actually work the same.

In your system, the balance is data and depositing and withdrawing are operations. But in the real world, it's exactly the dual: deposits and withdrawals are data, and computing the balance is an operation. Before we hat computers, bank tellers would write transaction slips for every transaction, then those transaction slips would be collected at the end of the day, and all the money movements added up. And electronic banking systems work the same, roughly like this:

final case class TransactionSlip(source: BankAccount, destination: BankAccount, amount: BigDecimal)

final case class BankAccount {
  def balance =
    TransactionLog.filter(slip.destination == this).map(_.amount).reduce(_ + _) - 
    TransactionLog.filter(slip.source == this).map(_.amount).reduce(_ + _)
}

So, the individual transactions are recorded in a log, and the balance is computed by adding up the amount of all transactions that have the account as a destination and subtracting from that the sum of the amount of all transactions that have the account as a source. There are obviously a lot of implementation details I haven't shown you, e.g. how the transaction log works, and there should probably be some caching of the balance so that you don't need to compute it over and over again. Also, I ignored validation (which also requires computing the balance).

I added this example to show you that the same problem can be solved by very different designs, and that some designs lend themselves more naturally to a functional approach. Note that this second system is the way banking was done for decades, long before computers even existed, and it lends itself very naturally towards functional programming.

like image 164
Jörg W Mittag Avatar answered Sep 28 '22 11:09

Jörg W Mittag


In functional programming you do not change the state in place, instead you create new state and return it.

Here is how your use case can be solved using functional programming.

case class BankAccount(val money: Int)

The above case class represents BankAccount

Instead of mutating the state, create new state with computed value and return it to the user.

def deposit(bankAccount: BankAccount, money: Int): BankAccount = {
  BankAccount(money + backAccount.money)
}

In the same way, check for funds and create new state and return it to the user.

def withDraw(bankAccount: BankAccount, money: Int): BankAccount = {
  if (money >= 0 && bankAccount.money >= money) {
    BankAccount(bankAccount.money - money)
  } else error("in sufficient funds")
}

In functional programming it is very common to create new state instead of trying to mutate the old state.

Create new state and return, thats it !!!

like image 32
pamu Avatar answered Sep 28 '22 12:09

pamu