Как написать ленивую версию orElse с переменным аргументом

Можно ли написать обобщенный метод 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) //, ...

person Matt R    schedule 01.12.2009    source источник


Ответы (2)


Согласно Спецификации языка Scala (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)*)

Вы можете заменить ленивый аргумент функцией и неявным преобразованием типа:

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)
person Thomas Jung    schedule 01.12.2009
comment
Я пробовал это в реплике, но, к сожалению, это заставляет использовать все три аргумента. - person Matt R; 01.12.2009
comment
Вы правы, неявное преобразование типов вынудило оценку. Я исправил код. - person Thomas Jung; 01.12.2009

Я нашел вопрос немного поздно :). Одна из возможностей — обернуть => A во вспомогательный класс вместе со вспомогательной функцией, чтобы упростить его создание:

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 в Scalaz, называемый 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) //, ...
person Petr    schedule 14.07.2013