Как я могу реализовать ранний возврат извне тела метода в Scala? - PullRequest
7 голосов
/ 08 июня 2011

Отказ от ответственности: Прежде чем кто-то это скажет: да, я знаю, что это плохой стиль и не поощряется.Я просто делаю это, чтобы поиграть со Scala и попытаться узнать больше о том, как работает система вывода типов и как настроить поток управления.Я не собираюсь использовать этот код на практике.


Итак: предположим, у меня довольно длинная функция, с множеством последовательных проверок в начале, которые, если они не пройдут, всеПредполагается, что функция должна возвращать некоторое другое значение (не throw), а в противном случае возвращает нормальное значение.Я не могу использовать return в теле Function.Но могу ли я имитировать это?Немного похоже на break, имитируемое в scala.util.control.Breaks?

Я придумал это:

object TestMain {

  case class EarlyReturnThrowable[T](val thrower: EarlyReturn[T], val value: T) extends ControlThrowable
  class EarlyReturn[T] {
    def earlyReturn(value: T): Nothing = throw new EarlyReturnThrowable[T](this, value)
  }

  def withEarlyReturn[U](work: EarlyReturn[U] => U): U = {
    val myThrower = new EarlyReturn[U]
    try work(myThrower)
    catch {
      case EarlyReturnThrowable(`myThrower`, value) => value.asInstanceOf[U]
    }
  }

  def main(args: Array[String]) {
    val g = withEarlyReturn[Int] { block =>
      if (!someCondition)
        block.earlyReturn(4)

      val foo = precomputeSomething
      if (!someOtherCondition(foo))
        block.earlyReturn(5)

      val bar = normalize(foo)
      if (!checkBar(bar))
        block.earlyReturn(6)

      val baz = bazify(bar)
      if (!baz.isOK)
        block.earlyReturn(7)

      // now the actual, interesting part of the computation happens here
      // and I would like to keep it non-nested as it is here
      foo + bar + baz + 42 // just a dummy here, but in practice this is longer
    }
    println(g)
  }
}

Мои чеки здесь явно фиктивные, но главное, что я 'Я хотел бы избежать чего-то подобного, когда действительно интересный код оказывается слишком вложенным на мой вкус:

if (!someCondition) 4 else {
  val foo = precomputeSomething
  if (!someOtherCondition(foo)) 5 else {
    val bar = normalize(foo)
    if (!checkBar(bar)) 6 else {
      val baz = bazify(bar)
      if (!baz.isOK) 7 else {
        // actual computation
        foo + bar + baz + 42 
      }
    }
  }
}

Мое решение здесь работает нормально, и я могу вернуться рано с 4 в качестве возвращаемого значения, если яхочу.Проблема в том, что у меня есть для явной записи параметра типа [Int], что немного мучительно.Есть ли способ, которым я могу обойти это?

Ответы [ 2 ]

3 голосов
/ 09 июня 2011

Это немного не связано с вашим основным вопросом, но я думаю, что более эффективный подход (не требующий исключения) для реализации return будет включать продолжения:

def earlyReturn[T](ret: T): Any @cpsParam[Any, Any] = shift((k: Any => Any) => ret)
def withEarlyReturn[T](f: => T @cpsParam[T, T]): T = reset(f)
def cpsunit: Unit @cps[Any] = ()

def compute(bool: Boolean) = { 
    val g = withEarlyReturn {
         val a = 1
         if(bool) earlyReturn(4) else cpsunit    
         val b = 1
         earlyReturn2(4, bool)            
         val c = 1
         if(bool) earlyReturn(4) else cpsunit            
         a + b + c + 42
    }
    println(g)  
}

Единственный единственныйпроблема здесь в том, что вы должны явно использовать cpsunit.

EDIT1: Да, earlyReturn(4, cond = !checkOK) может быть реализовано, но оно не будет таким общим и элегантным:

def earlyReturn2[T](ret: T, cond: => Boolean): Any @cpsParam[Any, Any] =
                            shift((k: Any => Any) => if(cond) ret else k())

k в приведенном выше фрагменте представляет остальную часть вычисления.В зависимости от значения cond мы либо возвращаем значение, либо продолжаем вычисление.

EDIT2: Any chance we might get rid of cpsunit? Проблема в том, что shift внутри оператора if не допускается без else.Компилятор отказывается конвертировать Unit в Unit @cps[Unit].

0 голосов
/ 09 июня 2011

Я думаю, что пользовательское исключение является правильным инстинктом здесь.

...