代码之家  ›  专栏  ›  技术社区  ›  Matt R

如何编写一个懒惰的变量参数版本的“orelse”

  •  4
  • Matt R  · 技术社区  · 15 年前

    能写一篇通论吗 orElse 方法从 Option 需要可变数量的参数吗?也就是说,不是:

    lazy val o1 = { println("foo"); None }
    lazy val o2 = { println("bar"); Some("bar") }
    lazy val o3 = { println("baz"); Some("baz") } 
    // ...
    o1 orElse o2 orElse o3 // orElse ...
    

    你可以使用:

    orElse(o1, o2, o3) //, ...
    
    2 回复  |  直到 11 年前
        1
  •  4
  •   Thomas Jung    15 年前

    根据 The Scala Language Specification (4.6函数声明和定义)不能按名称参数定义varargs:

    ParamType ::= Type
    | ‘=>’ Type
    | Type ‘*’
    
    scala> def orElse(x : (=> String)*)
    <console>:1: error: no by-name parameter type allowed here
           def orElse(x : (=> String)*)
    

    可以用函数和隐式类型转换替换lazy参数:

    def orElse[T](x : (()=> Option[T])*) : Option[T] = 
        if(x.isEmpty) None else x.first.apply.orElse(orElse((x drop 1) :_*))
    implicit def anyToFun0[T](t : => T) : (() => T) = () => t
    orElse(o1, o2, o3)
    
        2
  •  1
  •   Petr    11 年前

    我发现这个问题有点晚了:)。一种可能是包装 => A 与helper函数一起放入helper类以简化其创建:

    import scala.language.implicitConversions
    
    class Helper[+A](value: => A) extends Function0[A] {
      override def apply(): A = value;
    }
    object Helper {
      def unapply[A](h: Helper[A]): Option[A] = Some(h());
    }
    implicit def toHelper[A](body: => A) = new Helper(body);
    

    不需要提取器,它只允许在助手上轻松匹配。然后我们可以写

    def orElse[A](xs: Helper[Option[A]]*): Option[A] =
      xs.collectFirst[A]({
        case Helper(Some(r)) => r;
      })
    
    lazy val o1 = { println("foo"); None }
    lazy val o2 = { println("bar"); Some("bar") }
    lazy val o3 = { println("baz"); Some("baz") }
    
    orElse(o1, o2, o3) //, ...
    

    这只是一个简化的解决方案,一个更现实的解决方案是

    def orElse[A](x: Option[A], xs: Helper[Option[A]]*): Option[A]
    

    更有效的实施。


    已经有一个类类似于 Helper 在斯卡拉兹,被称为 Name 实施 Need 这样可以确保最多对身体进行一次评估。因此,使用scalaz,它可以实现为

    import scala.language.implicitConversions
    import scalaz._
    import scalaz.Scalaz._
    
    implicit def toNeed[A](body: => A): Name[A] = Need(body);
    
    def orElse[A](xs: Name[Option[A]]*): Option[A] =
      xs.collectFirst[A]({
        case Name(Some(r)) => r;
      })
    
    lazy val o1 = { println("foo"); None }
    lazy val o2 = { println("bar"); Some("bar") }
    lazy val o3 = { println("baz"); Some("baz") }
    
    orElse(o1, o2, o3) //, ...