【问题标题】:Better way of wrapping the result of a monadic-for in an Option, in Scala在 Scala 中将 monadic-for 的结果包装在 Option 中的更好方法
【发布时间】:2014-04-29 11:54:55
【问题描述】:

我想使用 Scala 的“monadic-for”在最后获得 Some(x) 或 None。

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

问题是,有时我没有选项可以开始。

所以我“伪造”了一个,使用一个虚拟值。

抽象形式如下:

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
}

虽然我得到了我想要的(一个评估为 Option 的 monadic-for),但我有一种不好的感觉。我不得不从一个“虚拟”Option 开始“滥用”monadic-for,只是为了最后得到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)
}

现在很明显,“for”很长,使用过滤器、flatMaps 等重写可能不切实际。

最后我的目标是获得Option[Session]。我从一个虚拟的Some 值开始“强制”这个结果。

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

  dummy <- Some("does not matter")

与:

  usr <- Some(usr)

【问题讨论】:

    标签: scala monads idioms


    【解决方案1】:

    你不能这样做:

    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)
    

    【讨论】:

    • 好的,谢谢。将对象包装在 Some() 中并继续这样做似乎是一种更好的方法(比包装虚拟值)。
    • 我推荐使用Option(p)而不是Some(p),这样可以保护你免受p为null的情况
    【解决方案2】:

    您可以混合使用理解、选项框和模式匹配:

    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
    

    【讨论】:

    • 使用Option(p) 似乎比我建议的Some(p) 更好。谢谢!
    【解决方案3】:

    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 时它也可以工作。

    【讨论】:

    • 即使这是可能的,我的问题也与 for 形式特别相关,因为我假设我将在其中包含一个很长的“条件”列表......但是谢谢展示另一种方式!
    【解决方案4】:

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

    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)
    

    【讨论】:

    • 它在我的示例的特定情况下有所帮助,但我不确定它是否适用于(或是否实用)更长的情况,即我的 for 中会有更长的链。跨度>
    猜你喜欢
    • 1970-01-01
    • 2011-06-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多