App 2: Using Either Instead of Try (Scala 3 Video)
The next step on your learning path is that we’ll now replace all of the Try
instances in the TDL app with Either
instances.
Actually, I don’t think it’s worth doing all of that. Instead, all I’m going to do is to replace them in the Database
class, and then I think you can imagine how to use those in the rest of the application.
So, let’s get started, recalling that all of the following functions are inside the Database
class:
class Database(dbFilename: String):
// all functions are in here
end Database
The insert database function
The first function we wrote was the insert
function, and it currently looks like this:
def insert(record: String): Try[Unit] =
writeToFile(List(record), true)
So our first task is to replace that Try
with an Either
. So we start by updating insert
’s function signature:
def insert(record: String): Either[Throwable, Boolean] =
writeToFile(List(record), true)
As a refresher, recall that Either
returns a Left
or a Right
, and by convention the Left
holds the error, so I write insert
’s return type like this:
Either[Throwable, Boolean]
--------- -------
^ ^
Possible Success
Error Value
In In
Left Right
Because this function relies on writeToFile
, I need to update it next.
writeToFile
The writeToFile
function signature currently looks like this:
private def writeToFile(lines: Seq[String], append: Boolean): Try[Unit] =
and we know that we want it to look like this:
private def writeToFile(lines: Seq[String], append: Boolean):
Either[Throwable, Boolean] =
That updates its function signature, and now a simple way to convert the function body is to convert the Success
to a Right
, and the Failure
to a Left
:
private def writeToFile(lines: Seq[String], append: Boolean):
Either[Throwable, Boolean] =
var bw: BufferedWriter = null
try
bw = BufferedWriter(FileWriter(File(dbFilename), append))
for line <- lines do bw.write(s"$line\n")
Right(true)
catch
case e: Throwable => Left(e)
finally
if bw != null then bw.close
So that’s a quick update.
selectAll
Similarly, selectAll
’s signature currently looks like this:
def selectAll(): Try[Seq[String]] =
and we again replace Try
with Either
:
def selectAll(): Either[Throwable, Seq[String]] =
After that, in the function body, we again replace Success
with Right
and Failure
with Left
:
def selectAll(): Try[Seq[String]] =
var bufferedSource: scala.io.BufferedSource = null
try
bufferedSource = Source.fromFile(dbFilename)
val lines = for line <- bufferedSource.getLines yield line
Right(lines.toList)
catch
case t: Throwable => Left(t)
finally
if bufferedSource != null then bufferedSource.close
And that’s all there is to that.
One thing to observe here is that selectAll
’s signature is a bit more complicated, because it has a list of strings as the Right
parameter, but by now I suspect that you may be getting used to seeing these types (and the value of them). Here’s a breakdown of those types:
Either[Throwable, Seq[String]]
--------- -----------
^ ^
Possible Success
Error Value
In In
Left Right
If you can read that, you’re doing just fine.
delete
Lastly, the delete
function currently returns this Try
:
def delete(indexToDelete: Int): Try[Int] = ...
--------
and we want to replace it with this Either
:
def delete(indexToDelete: Int): Either[Throwable, Int] = ...
----------------------
For the purpose of converting delete
so it returns an Either
, a simple solution is to convert maybeNumRowsDeleted
— which is currently a Try
— to an Either
. You do that by calling toEither
on maybeNumRowsDeleted
at the end of the function:
def delete(indexToDelete: Int): Either[Throwable, Int] =
val maybeNumRowsDeleted = for
rows <- selectAll()
newRows = CollectionUtils
.removeElementByIndex(rows, indexToDelete)
numRowsDeleted = rows.size - newRows.size
_ <- writeToFile(newRows, false)
yield
numRowsDeleted
maybeNumRowsDeleted.toEither // <== THE CHANGE
Fortunately Try
types have this toEither
method, which makes the conversion simple.
Keys
So there you have it, we rewrote the database functions to use Either
instead of Try
. Because Either
requires that you declare both types the function can return, its signatures are a little longer to read, but they’re also very explicit, and more flexible in how they work.
As a reminder of what that last point means, in all of the database functions I declared the Left
type as an Throwable
, but they could have returned a String
, an enum
, or any other data type that makes the most sense for the current problem.
And this flexibility is very important for where we’re going next.
Update: All of my new videos are now on
LearnScala.dev