kes
kes

Reputation: 6177

Mapping many Eithers to one Either with many

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

Answers (3)

kes
kes

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

Ben James
Ben James

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

Rex Kerr
Rex Kerr

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

Related Questions