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 )
From a functional point of view, recursion is used instead of loops with var
s, 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 var
s in a larger scope, e.g. class fields, and feel free to use var
s 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