What would define FP in Java?

Question from Fast Q#2816

What would define FP in java? I usually hear it thrown around when talking about streams/ functional interfaces

Let me write out some examples let me write out some examples

public final class BankAccount {
    private int balance;
    
    public BankAccount() {
        this.balance = 0;
    }
    
    public void deposit(int amt) {
        if (amt < 0) {
            throw new IllegalArgumentException("amt must be non-negative");
        }
        else {
            this.balance += amt;
        }
    }

    public int withdraw(int amt) {
        if (amt < 0) {
            throw new IllegalArgumentException("amt must be non-negative");
        }
        else {
            if (this.balance < amt) {
                int balance = this.balance;
                this.balance = 0;
                return balance;
            }
            else {
                this.balance -= amt;
                return amt;
            }
        }
    }
    
    public int getBalance() {
        return this.balance;
    }
}

Okay real basic class here i just whipped up. Take a moment to read it and show me what an example usage might be.

(this isn't FP, this is normal)

(i'm also sure there is a bug, but ignore it)

So an example like:

BankAccount myTaxHaven = new BankAccount();
myTaxHaven.deposit(500);
myTaxHaven.withdraw(250);

?

sure.

Mutable bank account - you can take money in and out.

Now here is the challenge. We have a new requirement. We want to know the state of every bank account at every time.

So somewhere in the code there is a map of user id to a map of timestamp to bank account. (does that parse?)

transaction timestamp?

yeah

yep I'm tracking

But in order to support that use case we can't change bank accounts directly like that.

Or rather, one way to support it is to make the bank account immutable.

Wait, why can't we change bank accounts directly?

lets say we had this

Map<Instant, BankAccount> bankAccountAtTime = new HashMap<>();
BankAccount myTaxHaven = new BankAccount();
bankAccountAtTime.put(Instant.now(), myTaxHaven);
myTaxHaven.deposit(400); // oh no, our history is messed up

Why is our history messed up?

so the fact that we mutate our bank account after depositing? That makes me question the original claim that this is a solution

I mean yeah we're mapping to the bank accounts, not a specific balance of the bank account in that time

My framing here is a bit messed up. I guess I'm saying if they were immutable then this kind of solution would work.

If they were immutable, deposit wouldn't even do the same thing though

Thats correct.

So how would we re-write the code such that we support all the same use cases but our contract doesn't have any mutating methods.

Keep a list of <Instant, Balance> or <Instant, Transaction>(which contains before/after information) Or a map, but some data structure

I mean just the BankAccount class.

deposit/withdraw could return a Balance or Transaction object, but then I think our paradigm doesn't even make sense

public final class BankAccount {
    private final int balance;

    public BankAccount() {
        this(0);
    }

    private BankAccount(int balance) {
        this.balance = balance;
    }

    public BankAccount deposit(int amt) {
        if (amt < 0) {
            throw new IllegalArgumentException("amt must be non-negative");
        }
        else {
            return new BankAccount(this.balance - amt);
        }
    }

    record WithdrawalResult(BankAccount account, int withdrawn) {}

    public WithdrawalResult withdraw(int amt) {
        if (amt < 0) {
            throw new IllegalArgumentException("amt must be non-negative");
        }
        else {
            if (this.balance < amt) {
                return new WithdrawalResult(new BankAccount(0), this.balance);
            }
            else {
                return new WithdrawalResult(new BankAccount(this.balance - amt), amt);
            }
        }
    }

    public int getBalance() {
        return this.balance;
    }
}

A bank account is a full history - always. There is the current state of it and past states of it, but they are all equally real "accounts".

right, that's a fine way of viewing it yeah

It's like numbers. Imagine if this worked

Integer i = 5;
i.subtractOne(); // i is now 4

the value of 5 should be independent from the identity you assign that value. If that isn't the case - for numbers - it feels super wierd

Is the main point that mutability is inherently dangerous?

Kinda. there are a lot of downsides to it - it's a lot harder to multithread is a big one.

And you can always get it back if you need to.

Yeah this makes sense I definitely believe in keeping an immutable history for anything important


<- Index