3

Given the following types:

sealed trait Pet { 
  val name: String
}
case class Dog(override val name: String) extends Pet 
case class Cat(override val name: String) extends Pet 

sealed trait Error
case object DBConnection extends Error
case object NoResults extends Error

We write a function that searches for a pet by its name.

def foo(petName: String): Either[Error, Pet] = {
  val results: Either[Error, List[Pet]] = ??? // does not matter
  val foundPet: Option[Pet] = results match {
     case left @ Left(_) => None
     case Right(ps)      => ps.find(_.name == petName)
  }
  foundPet match { 
    case None    => Left(NoResults)
    case Some(p) => Right(p) 
  }
}

Please ignore any improvements to the above code with respect to the database call.

Ideally, I'd prefer to write the above code as a simple for comprehension, taking advantage of the Either monad. The pattern matching is easy to read, I believe, but the for alternative would be more concise, I suspect.

How would I re-write the above code with a for-comprehension? I suppose that I could just make methods that match the return type of Either[Error, Pet], but was not sure.

Kevin Meredith
  • 41,036
  • 63
  • 209
  • 384
  • 1
    Your code doesn't compile. `foundPet` is an `Option[Pet]` but one of the cases tries to assign a `Left[Error]`. Also, `Either` is not a monad. – vptheron Apr 09 '15 at 15:29
  • Good points, @vptheron. I made a last minute fix that broke the compile. Also, I should have pointed out that Either is [not a monad](http://eed3si9n.com/learning-scalaz-day7) – Kevin Meredith Apr 09 '15 at 15:34

4 Answers4

6

Problem is Scala Either isn't a monad and it's not biased hence you can't use it in a for-comprehension: you have to get a LeftProject or RightProjection first as other poster mentioned.

If you're open for little scalaz. scalaz disjunction (\/) is right biased and follows all monad laws. when you map over it it gives you right value.

so your type will become

 val results : \/[Error,List[Pet]]

and results.map will give you List[Pet] because scalaz disjunction is right biased.

this may be helpful as well

Community
  • 1
  • 1
Vikas Pandya
  • 1,998
  • 1
  • 15
  • 32
3

You can put the find into the for-comprehension and use toRight to convert it to Either. You also must convert these to RightProjections in order for it to work in the for-comprehension (Either does not have flatMap and map on its own).

def foo(petName: String): Either[Error, Pet] = {
    val results: Either[Error, List[Pet]] = ???
    for {
        pets <- results.right
        pet  <- pets.find(_.name == petName).toRight(NoResults).right
    } yield pet
}
Michael Zajac
  • 55,144
  • 7
  • 113
  • 138
2

The problem with Scala's Either[+A, +B] type is that you have to do right or left projections to get a monad (respectively right or left biased). On the other hand, scalaz's \/[+A, +B] is monadic by default. To get something really concise, with \/[+A, +B] the solution would look like this:

def foo(petName: String): \/[Error, Pet] = {
  val results: \/[Error, List[Pet]] = ???

  for {
    pets <- results
    results <- pets.find(_ == petName) \/> NoResults
  } yield results
}

But then again, it's an example where using for {} yield ... isn't necessarily the shortest solution, this flatMap gives the same result:

results.flatMap(
  _.find(_.name == petName) \/> NoResults
)
mutantacule
  • 6,913
  • 1
  • 25
  • 39
0

Either.fold is a nice and readable way. And you don't need scalaz to do that. Here is the code snippet:

results.fold(
  err => Left(err),
  lst => lst.find(_.name == petName).map(Right(_)).getOrElse(Left(NoResults))
)
tuxdna
  • 8,257
  • 4
  • 43
  • 61