在Scala中将一个monadic-for的结果包装在一个Option中的更好方法

时间:2014-04-29 11:54:56

标签: scala monads idiomatic

我想使用Scala" monadic-for"最后获得Some(x)或None。

这意味着,此monad中的第一个元素必须是选项。

问题是,有时候我没有选择开始。

所以我"假"一,使用虚拟值。

抽象形式如下所示:

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(...)
}

一个具体的例子可能是:

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

val p = Person("John", 32)

最后评估为Some("John")

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

而评估结果为None

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

虽然我得到了我想要的东西(monadic-for the evaluation to Option),但我感觉不好。我不得不误用&#34; monadic-for以&#34; dummy&#34;开头Option,只是为了最后获得Some / None

我的问题是:有没有更好的方法来实现这一点,而不创建虚拟值?

更新

考虑这个例子,它基于上面的例子:

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)
}

现在很明显&#34; for&#34;是很长的,使用过滤器,flatMaps等重写可能不太可行。

我最终的目标是获得Option[Session]。我&#34;强迫&#34;从虚拟Some值开始,以此为结果。

但正如Noel M所指出的那样,我可以重写:

  dummy <- Some("does not matter")

使用:

  usr <- Some(usr)

4 个答案:

答案 0 :(得分:4)

你不能这样做:

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

虽然我觉得我误解了你的问题。

或者您可以使用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)

答案 1 :(得分:2)

你可以混合for-comprehension,option-boxing和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

答案 2 :(得分:1)

Scalaz有一个很好的方法,如@Noel所提到的,但使用纯scala你可以使用:

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

或者,一体化:

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

它的优势在于pnull时也会有用。

答案 3 :(得分:1)

我不确定我是否理解这个问题。这有帮助吗?

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)