App 2: Prompt the User

Introduction

Now that I’ve shown in the inside of the application — the functions that read-from and write-to the database file — I’ll switch gears and look at the outside of the function: the UI code that the user interacts with.

In this code we’ll continue to follow the rule that any function that interacts with the outside world must return Try. With the database code it helps to think that we were working with remote servers, and the same is true here: you can imagine that we’re doing something like writing a REST API, and getting input from remote sources.

NOTE: I started to create this as a REST example, but that gets into too many things that aren’t important to our primary objectives. So I’m using the following “command-line UI” approach to keep things as simple as possible while we work with functions that return Try values.

Our main function

As I mentioned earlier, TDL’s main method starts like this:

@main
def ToDoList() = 

and I save that code in a file named ToDoList.scala. After that, I add the call to my Database class:

@main
def ToDoList() = 
    val db = Database("./ToDoList.dat")

Now the next thing the application needs to do is prompt the user for their input. So we’ll need a promptUser function.

promptUser

Because promptUser reaches out into the outside world, it needs to return a Try. But because printing a couple of strings to STDOUT can’t really fail (unless there’s something seriously wrong with your computer), I won’t use the try/catch/finally construct.

Instead I’ll use the Try shortcut that I showed earlier, where you just pass your block of code into Try’s constructor. Including the necessary import statements, that code looks like this:

import scala.io.StdIn
import scala.util.{Try, Success, Failure}

def promptUser(): Try[Unit] = Try {
    println("\n(Commands: a \"task\", d 1, u 1, h, q, v)")
    print("Yo: ")
}

In that code:

  • The promptUser function requires the two import statements shown
  • promptUser doesn’t require any input parameters
  • Per the rule, it needs to return a Try
  • Because it can’t really fail, I declare that it returns Unit inside the Try
  • The opening Try {... code tells us the code that follows is passed into Try’s constructor
  • In that code block, I prompt the user with a println call, followed by a print
  • I use print in the second line because it doesn’t put a newline character after the string; that leaves the user’s prompt on the same line as the string

Organizing the code

That’s all that’s needed to prompt a user for their input. However, knowing that I’m going to need a few other I/O-related functions, I want to get this code out of my main file. Because these functions are all related to I/O, let’s create a new file named IOHelper.scala. Then we’ll create an IOHelper object in it, and that object will contain my functions:

import scala.io.StdIn
import scala.util.{Try, Success, Failure}

object IOHelper:

    def promptUser(): Try[Unit] = Try {
        println("\n(Commands: a \"task\", d 1, u 1, h, q, v)")
        print("Yo: ")
    }

end IOHelper        

Back to main

Now I can go back to my ToDoList.scala file and put this code in it:

import IOHelper.*

@main def ToDoList() = 
    val db = Database("./ToDoList.dat")
    promptUser()

At this point I’m ignoring the fact that promptUser returns a Try, but I’ll get back to that shortly. Before doing that, let’s write a function to read the user’s input.