While Scala's Option
will work here, as two other answers have pointed out, a more idiomatic functional approach would be to use a "lazy list"—or a Stream
, in Scala—to represent the set of solutions.
I've found myself writing code like this, for example:
trait Node[A] {
def children: Stream[A with Node[A]]
def dfs(f: A => Boolean): Stream[A] = this.children.flatMap {
child => if (f(child)) Stream(child) else child.dfs(f)
}
}
Now suppose I have a Board
class that extends Node[Board]
and that has an implementation of the children
method that returns all valid boards with one additional piece. Suppose it also has some other useful stuff, like a size
method, a companion object with an empty
, etc.
I can then write the following to get a Stream
of solutions:
val solutions = Board.empty.dfs(_.size == 8)
A Stream
is lazy and only evaluates its head, so right now we've only searched the tree far enough to find the first solution. We can get this solution using head
:
scala> solutions.head
res1: Board =
o . . . . . . .
. . . . o . . .
. . . . . . . o
. . . . . o . .
. . o . . . . .
. . . . . . o .
. o . . . . . .
. . . o . . . .
Or whatever. But I can also get other results if I want them:
scala> solutions(10)
res2: Board =
. o . . . . . .
. . . . . . o .
. . . . o . . .
. . . . . . . o
o . . . . . . .
. . . o . . . .
. . . . . o . .
. . o . . . . .
This searches just enough more of the tree to find the tenth solution, and then stops.
The big advantage of Stream
over the Option
approach is that I can get additional results if I need them, without paying more for the first.