Number guessing in scala


#1

Hi community,

I am new to scala and I am trying to get used to various concepts of scala. One of them seems to be to always prefer val over var. This seems to imply that loops are replaced by recursions. I have written a simple game of guessing numbers. I have written it using two attempts: attempt1 is how I would have written it in a language such as Java and attempt2 is the way I would do it in Scala. Now I am asking some people with more experience than me: is attempt2 the scala way to do it? Or am I missing some beautiful concepts here? :wink:

import scala.io.StdIn._
import scala.util.Random

object Main extends App {
  val TRIALS = 3

  def attempt1(): Unit = {
    val numberToGuess = new Random().nextInt(100)
    var guessedNumber = -1
    var trials = 0

    println("Guess a number between 0 and 100")
    // println(numberToGuess)
    while (trials < TRIALS && guessedNumber != numberToGuess) {
      print("Your guess?: ")
      guessedNumber = readInt()
      if (guessedNumber > numberToGuess) println("Too large!")
      else if (guessedNumber < numberToGuess) println("Too small!")
      else {
        println("You got it!")
        return
      }
      trials += 1
    }
    println("You lost")
  }

  def attempt2(): Unit = {
    def singleGuess(numberToGuess: Int, trials: Int = TRIALS): Boolean = {
      if (trials == 0) return false

      val guessed = { print("Your guess?: "); readInt() }

      if (guessed > numberToGuess) { println("Too large!"); singleGuess(numberToGuess, trials-1) }
      else if (guessed < numberToGuess) { println("Too small!"); singleGuess(numberToGuess, trials-1) }
      else { println("You got it!"); return true }
    }

    val numberToGuess = new Random().nextInt(100)
    println("Guess a number between 0 and 100")
    // println(numberToGuess)
    if (!singleGuess(numberToGuess)) {
      println("You lost")
    }
  }

  // attempt1()
  attempt2()

}

#2

I wouldn’t say there is one Scala way to do things like this. It mostly comes down to how much of a functional programming style you want to adopt. Not using var is about programming in a functional style, and although Scala definitely aims to support that and encourages immutability, it does not enforce it like some pure functional languages (e.g. Haskell).

Therefore the rest of my answer will be about how to adapt a more functional style (
NB: I lecture a functional programming course, so I’m biased :wink: )

From a functional point of view, recursion is used instead of loops with vars, because assigning a new value to a var is a side effect. But so are user input and console output, which you both use in your recursive function. As your program is pretty small and mostly consists of user interaction (= I/O, so, a side effect), there is not much to separate out into pure (= side effect free) code. I’d say both are valid solutions.

Also your loop is kind of a special case, as it is a “run n times or until a condition”. In my experience, most loops in imperative code are over some kind of collection, like a List. Those kinds of loops are different, because they often don’t really care about the index variable, but about the element at that index. If you come from a Java background, you’ll probably already have seen foreach loops (e.g. for(Elem x : collection) ...). This is already no longer using a variable, as x is scoped to the method body, and practically each run of the body gets its own x, not a reassigned x. The scala equivalent to that are for-comprehensions, which under the hood use functions on the collection like map and filter, to run operations on each element.

One thing that can easily be improved in your second attempt is not using return. An explanation of why it is bad for FP would be longer, but regardless of that, it is not that fast. A return in Scala is actually implemented as throwing a special exception. You can always replace an if(x) return foo by if(x) foo else {...}. Remember that if in Scala is an expression and returns a value, and a function returns the value of its last expression. Your method would then look like:

def singleGuess(numberToGuess: Int, trials: Int = TRIALS): Boolean = {
  if (trials == 0)  false
  else {
    val guessed = { print("Your guess?: "); readInt() }
 
    if (guessed > numberToGuess) { println("Too large!"); singleGuess(numberToGuess, trials-1) }
    else if (guessed < numberToGuess) { println("Too small!"); singleGuess(numberToGuess, trials-1) }
    else { println("You got it!"); true }
  }
}

Other than that, your solution is fine for beginning with Scala. Applying a functional style to IO is possible with advanced concepts (IO monads) to encapsulate IO in a functional way, but lots of Scala code is not built in such a strictly functional way. As Scala allows for pure and non-pure code, you don’t have to fully commit to “do everything in a FP way” and still have benefits from the parts where it is easier to apply. A few tips to get started:

  • You can start with replacing loops over collections with map (apply a function to each element of a collection and return a new collection) and filter (remove elements not satisfying a condition), where you know how. When you encounter loops, that you can not replace that way, look at the other functions your collection provides. You will develop a feeling for when which function is appropriate over time. Some of those are explained in Oderskys FP course on Coursera, which I can recommend as an Intro to Scala in general.

  • Start with avoid vars in a larger scope, e.g. class fields, and feel free to use vars local to a single function, until you have some experience in using immutable data structures.

  • Look for parts of your code, that do not directly need to do IO, but can be put into a function receiving the input as a parameter and returning output. For example when reading in a file, separate the interpretation of the file contents into a separate function that gets passed the contents, so it does not care about them being from a file.

Most of these are not scala-specific, but can be applied to a lot of programming languages.

Regards
Alexander Gehrke


#3

Just one small thing I’d add to @crater2150’s excellent answer:

val TRIALS = 3

When something is intended to be a constant like this, I’d typically declare it as final val. That means that it can’t be overridden in a child class, and allows the compiler to use it more efficiently. (In Scala 3, this looks likely to become especially powerful for some use cases.)

Also, I’d like to underline this point:

I think the average Scala programmer is fairly sanguine about using a local var in a smaller function: so long as the context is small enough to easily reason about what’s going on accurately, it doesn’t usually do much harm. I don’t do it often, but once in a while it’s the clearest way to express what I want to do.

What you want to avoid is letting vars leak into the larger world, which can quickly make things much harder to reason about. That’s why var fields are a fairly strong anti-pattern, while local function vars are rather less dangerous.