Reputation: 6177
Say I have a monadic function in called processOne
defined like this:
def processOne(input: Input): Either[ErrorType, Output] = ...
Given a list of "Inputs
", I would like to return a corresponding list of "Outputs
" wrapped in an Either
:
def processMany(inputs: Seq[Input]): Either[ErrorType, Seq[Output]] = ...
processMany
will call processOne
for each input it has, however, I would like it to terminate the first time (if any) that processOne
returns a Left
, and return that Left
, otherwise return a Right
with a list of the outputs.
My question: what is the best way to implement processMany
? Is it possible to accomplish this behavior using a for
expression, or is it going to be necessary for me to iterate the list myself recursively?
Upvotes: 2
Views: 172
Reputation: 6177
For now, I've decided to just solve this using recursion, as I am reluctant to add a dependency to a library (Scalaz).
(Types and names in my application have been changed here in order to appear more generic)
def processMany(inputs: Seq[Input]): Either[ErrorType, Seq[Output]] = {
import scala.annotation.tailrec
@tailrec
def traverse(acc: Vector[Output], inputs: List[Input]): Either[ErrorType, Seq[Output]] = {
inputs match {
case Nil => Right(acc)
case input :: more =>
processOne(input) match {
case Right(output) => traverse(acc :+ output, more)
case Left(e) => Left(e)
}
}
}
traverse(Vector[Output](), inputs.toList)
}
Upvotes: 0
Reputation: 125307
With Scalaz 7:
def processMany(inputs: Seq[Input]): Either[ErrorType, Seq[Output]] =
inputs.toStream traverseU processOne
Converting inputs
to a Stream[Input]
takes advantage of the non-strict traverse
implementation for Stream
, i.e. gives you the short-circuiting behaviour you want.
By the way, you tagged this "monads", but traversal requires only an applicative functor (which, as it happens, is probably defined in terms of the monad for Either
). For further reference, see the paper The Essence of the Iterator Pattern, or, for a Scala-based interpretation, Eric Torreborre's blog post on the subject.
Upvotes: 5
Reputation: 167911
The easiest with standard Scala, which doesn't evaluate more than is necessary, would probably be
def processMany(inputs: Seq[Input]): Either[ErrorType, Seq[Output]] = {
Right(inputs.map{ x =>
processOne(x) match {
case Right(r) => r
case Left(l) => return Left(l)
}
})
}
A fold would be more compact, but wouldn't short-circuit when it hit a left (it'd just keep carrying it along while you iterated through the entire input).
Upvotes: 2