App 2: Handling Delete (Scala 3 Video)
The “Delete” use case is very similar to the “Add” case, but instead of having a string after the a
command, there will be an integer after the d
command. To be clear, delete commands look like this:
d 1
d 3
Those commands mean, “Delete the first task” and “Delete the third task,” respectively.
Handling the input
Just like the last lesson, the first thing we need to do is to separate the delete command — the letter d
— from the rest of the string. Again we do this with the drop
method:
"d 1".drop(2) // "1"
"d 3".drop(2) // "3"
and then we use this approach to pass that string to a handleDelete
function:
case del if del.startsWith("d ") =>
handleDelete(del.drop(2))
handleDelete
Now I need to write a handleDelete
function, and I already know three things about:
- It’s going to take a
String
as input - It needs to attempt to delete that task from the database, using the task-id that the user sees
- Because it’s going to access the database, it probably needs to return a
Try
Therefore, I can start to sketch its signature like this:
def handleAdd(taskIdString: String): Try
Because this function takes a string that represents the task-id that the user sees — such as "1"
or "3"
— the function’s algorithm should be:
- Attempt to convert the given string to an integer.
- If that fails, the function should short-circuit at that time.
- If the conversion works, subtract
1
from that value to get the real index. This is because Scala lists are 0-based, just like arrays and lists in other programming languages. (What I mean by this is that the user sees the first task as number1
, so they type that. And that1
corresponds to index0
of a sequence.) - This function should return the number of rows that are deleted. Because I’m only letting users delete one task, this should always be
1
. - As usual, because we’re accessing the outside world, the function should return a
Try
that wraps that integer.
Here’s the implementation of that algorithm, with a few comments added:
def handleDelete(taskIdString: String): Try[Int] = Try {
// convert the input String to an Int. this attempt will throw
// an exception if this fails, so the code will short-circuit
// here, and a Failure will be returned, wrapped around the
// exception:
val taskId = taskIdString.toInt
// the task-id we show starts with 1, so subtract 1 here,
// because sequences are 0-based:
val maybeCount = db.delete(taskId - 1)
maybeCount.get
}
And to make it easier to read, here’s that function again without all those comments:
def handleDelete(taskIdString: String): Try[Int] = Try {
val taskId: Int = taskIdString.toInt
val maybeCount = db.delete(taskId - 1)
maybeCount.get
}
I almost never call the get
method on a Try
value, but I do it for two reasons here:
- The
handleDelete
function is wrapped in aTry
, so it will return aFailure
ifget
throws an exception. And if it throws an exception, that means something went wrong insidedelete
. - I believe the code is relatively easy to read for someone who’s new to Scala
As usual, you can shorten that function, if desired:
def handleDelete(taskIdString: String): Try[Int] = Try {
val taskId: Int = taskIdString.toInt
db.delete(taskId - 1).get
}
Back to the handleUserInput case
And with handleDelete
now working, its case
could be finished here:
case del if del.startsWith("d ") =>
handleDelete(del.drop(2))
But again, I know that I want to see my list of to-do items after a delete command, so I add the handleView
call here:
case del if del.startsWith("d ") =>
handleDelete(del.drop(2))
handleView()
And because handleView
returns Try[Unit]
, the return type of this case
match the return type of handleUserInput
. Now handleUserInput
’s match
expression has these cases:
def handleUserInput(input: String): Try[Unit] = input match
case "q" =>
Try(System.exit(0))
case "h" =>
IOHelper.showHelp()
case "v" | "l" =>
handleView()
case add if add.startsWith("a ") =>
handleAdd(add.drop(2))
handleView()
case del if del.startsWith("d ") => // <== THE NEW CODE
handleDelete(del.drop(2))
handleView()
// more cases here ...
Now we just have one more case
to handle.
Update: All of my new videos are now on
LearnScala.dev