Question

I want to use Scala's "monadic-for" to obtain, at the end, either Some(x) or None.

This means, that the first element in this monad has to be an Option.

The problem is, that sometimes I don't have an Option to start with.

So I "fake" one, using a dummy value.

The abstract form looks like this:

for {
  // only used to yield Some/None at the end
  dummyVal <- Some("dummy value")
  // ...
  // ... other monadic expressions
  // ...
} yield {
  // result which will be wrapped into Some(...)
}

A concrete example could be:

case class Person(name:String, age:Int)

val p = Person("John", 32)

At the end this evaluates to Some("John"):

for {
  dummy <- Some("dummy")
  matchedPerson = p
  if matchedPerson.age > 30
} yield {
  matchedPerson.name
}

Whereas this evaluates to None:

for {
  dummy <- Some("dummy")
  matchedPerson = p
  if matchedPerson.age > 55
} yield {
  matchedPerson.name
}

Though I get what I want (a monadic-for that evaluates to Option), I have a bad feeling.I had to "misuse" the monadic-for by starting with a "dummy" Option, just for the sake of getting Some/None at the end.

My question is: is there a better way to achieve this, without creating a dummy value?

UPDATE:

Consider this example, which builds on the one above:

case class Person(name: String, username: String, age: Int)
case class Session(loggedInPerson: Person)

def isValidUser(username: String):Boolean = ???
def isValidPassword(username: String, password: String):Boolean = ???
def readPassword():Option[String] = ???

val usr = Person("John Wayne", "jwayne", 55)

for {
  dummy <- Some("does not matter")
  username = usr.username
  if isValidUser(username)
  pass <- readPassword()
  if isValidPassword(username, pass)
} yield {
  Session(usr)
}

Now it's clear that the "for" is pretty long and probably not practicable to re-write using filters, flatMaps, etc.

My goal at the end is to get an Option[Session]. I "force" the for to this outcome by starting with a dummy Some value.

But as has been pointed out by Noel M, I can just rewrite:

  dummy <- Some("does not matter")

with:

  usr <- Some(usr)
Was it helpful?

Solution 2

You can mix for-comprehension, option-boxing and pattern-matching:

scala> case class Person(name: String, age:Int)
defined class Person

scala> val p = Person("John", 32)
p: Person = Person(John,32)

scala> for { Person(name, age) <- Option(p); if age > 30} yield name
res0: Option[String] = Some(John)

scala> for { Person(name, age) <- Option(p); if age > 55} yield name
res1: Option[String] = None

OTHER TIPS

Can't you just do:

for {
  person <- Some(p)
  if person.age > 55
} yield person.name

?

Though I feel I've misunderstood your question.

Alternatively you could use Scalaz:

scala> import scalaz._, Scalaz._
import scalaz._
import Scalaz._

scala> case class Person(name:String, age:Int)
defined class Person

scala> val p = Person("John", 32)
p: Person = Person(John,32)

scala> (p.age > 55) option p.name
res0: Option[String] = None

scala> (p.age > 30) option p.name
res1: Option[String] = Some(John)

Scalaz has a nice approach as @Noel mentioned, but using pure scala you can use:

Option(p).filter( _.age > 55).map(_.name)   // None
Option(p).filter( _.age > 30).map(_.name)   // Some(john)

or, all in one:

Option(p).collectFirst{ case Person(name, age) if age > 55 => name}

It has the advantage that'll also work when p is null.

I'm not sure I understand this question. Does this help ?

scala> case class Person(name: String, age:Int)
defined class Person

scala> val p = Person("John", 32)
p: Person = Person(John,32)

scala> PartialFunction.condOpt(p){case Person(name, age) if age > 55 => name}
res0: Option[String] = None

scala> PartialFunction.condOpt(p){case Person(name, age) if age > 30 => name}
res1: Option[String] = Some(John)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top