In a cryptocurrency application a transaction is a record of a transfer of an amount (in USD) from one account to another. Accounts are identified by their ID numbers. A ledger contains a sequence of transactions and a method for computing the balance of any account.
Here it is in UML:
And again, in Java:
And finally, in Scala:
The Java version should be straight forward. The Transaction class provides a constructor that initializes its fields, and getter methods for each field. Setter methods are not provided because we want transactions to be immutable. It shouldn't be possible for someone to change them. Note that the constructor may throw an exception if the amount isn't positive. This possibility must be declared.
The Ledger class uses a private array list to hold all of its transactions. Rather than providing a getter method, which would allow users to manipulate the list in any way they wanted, we only provide a delegator method that adds transactions to the list. The getBalance method uses an enhanced for-loop to iterate through all of the transactions. Finally, a static main method provides a simple test harness that adds several transactions to a ledger and prints the balance of one account.
Here's the output produced (by both versions):
Error: Amounts must be positive
balance of account 10 = $105.0
Notice that the last transaction was not added to the ledger because the previous line threw an exception.
Also notice that all classes other than Ledger have package scope since they are declared in a file called Ledger.java.
// Java version
public Double getBalance(Integer accountNum) {
Double result = 0.0;
for(Transaction trans: transactions) {
if(trans.getToAccount() ==
accountNum) result += trans.getAmount();
if(trans.getFromAccount() ==
accountNum) result -= trans.getAmount();
}
return result;
}
// Scala version
def getBalance(accountNum: Int) =
var result = 0.0
for(trans <- transactions)
if(trans.toAccount == accountNum)
result += trans.amount
if(trans.fromAccount == accountNum)
result -= trans.amount
result
Here are some differences in the Scala version:
1. The scope of getBalance is public by default, so no need to declare it public.
2. Use def to define methods, var to define variables, and val to define constants. These should all be followed by "=":
def one = 1 // one = a
function that always returns 1
val two = 2 // two = another name for
2
var ten = 10 // ten = a variable that
currently contains 10
3. The type of the accountNum parameter comes after the name and a colon.
4. The return type of getBalance could've be specified by:
def getBalance(accountNum: Int): Double = ...
In the case of recursive functions this is mandatory but for all other functions Scala's type checker is able to infer the return type by examining the body.
5. Curly braces aren't needed. The parser infers the scope of a declaration from its indentation. (Starting in version 3.0.)
6. Semicolons aren't needed either. Thanks parser.
7. The local variable, result, is declared using var so that we can update its contents in the for-loop.
8. No need to declare the type of trans, the loop control variable. The type checker will infer this, too.
9. Scala uses "<-" instead of ":".
10. Instead of calling trans.getAmount() it appears that we can simply access trans.amount directly. Is it public? No! More below.
11. No return statement required for result. Scala infers this from its position.
class Transaction(val amount: Double, val fromAccount: Int, val
toAccount: Int):
if (amount <= 0) throw
IllegalAmountException()
val date: LocalDate = LocalDate.now
override def toString = "$" +
amount + " from " + fromAccount + " to " + toAccount +
" on " + date
1. It's very short!
2. Again, no semicolons or curly braces.
3. In Scala the constructor declaration and the class declaration can be merged, with the parameter declarations doubling as field declarations.
4. Each time we create an instance of Transaction, the body will be executed, just as the body of a constructor would be in Java. This means we can do error checking inline.
5. Shouldn't the fields be declared private with getter and setter methods? They are private-- in the byte code. Scala generates getter methods with the same name. If declared as var fields, then Scala also generates setter methods that can be called using assignment command syntax. If declared private, these methods aren't generated.
6. We don't need the "new" to create an exception. (Starting in Scala 3.0.) Nor do we need to declare that we might throw an exception.
7. Since LocalDate.now is parameterless, we don't need to call it with empty parentheses as we do in Java. (However empty parentheses do seem to be required when calling a parameterless constructor like IllegalAmountException.)
class Ledger:
private val transactions:
ListBuffer[Transaction] = ListBuffer[Transaction]()
def add(trans: Transaction) =
transactions += trans
// etc.
1. Scala distinguishes between mutable and immutable collections. We can add and remove elements with a list buffer, but not with a list.
2. Scala uses square braces instead of angle braces when declaring or using generics.
3. We could have added trans to transactions using the add method, but sometimes it's more fun to use the dizzying array of operators Scala provides. ** sarcasm**
object Ledger:
def main(args: Array[String]): Unit =
val block = Ledger()
try
block.add(Transaction(500.0, 0,
10))
block.add(Transaction(200.0, 10,
20))
block.add(Transaction(300.0, 10,
30))
block.add(Transaction(75.0, 20,
10))
block.add(Transaction(30.0, 30,
10))
block.add(Transaction(-30.0, 30,
10))
block.add(Transaction(900.0, 30,
10))
catch
case e: IllegalAmountException
=> println("Error: " + e.getMessage)
case e: Throwable =>
println("Unknown error: " + e)
finally
println(" balance of account
10 = $" + block.getBalance(10))// prints $105
1. We can declare singleton objects in Scala! These objects are created static. That means they are created when your program starts and are globally visible. The only way to declare static fields and methods in Scala is to declare them inside a singleton.
2. An object with the same name as a class is called the class's companion object.
3. Unit is Scala's version of void.
4. Scala allows programmers to define different branches of a conditional expression using case clauses. They can appear in match or catch blocks.
5. Scala doesn't make us write "System.out.println." every time we want to print something. (Thanks, Scala.)