2011-06-08 14 views
7

responsabilidad: Antes de que alguien dice: sí, ya sé que es malo estilo y no se anima. Solo estoy haciendo esto para jugar con Scala y tratar de aprender más sobre cómo funciona el sistema de inferencia de tipo y cómo modificar el flujo de control. No pretendo usar este código en la práctica.¿Cómo podría implementar una devolución anticipada desde fuera del cuerpo de un método en Scala?


Así: supongo que estoy en una función más bien largo, con una gran cantidad de comprobaciones sucesivas al principio, que, si no, se supone que todos debemos hacer que la función para devolver algún otro valor (no tirar) , y devuelve el valor normal. No puedo usar return en el cuerpo de Function. ¿Pero puedo simularlo? Un poco como break se simula en scala.util.control.Breaks?

he llegado con esto:

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

Mis cheques aquí son obviamente ficticia, pero el punto principal es que me gustaría evitar algo así, donde el código realmente interesante termina siendo manera anidado demasiado para mi gusto:

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

mi solución funciona bien aquí, y puedo regresar temprano con 4 como valor de retorno si quiero. El problema es que tengo escribir explícitamente el parámetro de tipo [Int] - que es un poco de dolor. ¿Hay alguna forma de evitar esto?

+1

No creo que sea _hasta_ una mala práctica. Parece que se usa en exceso en los idiomas que lo hacen fácil y no ofrecen buenas alternativas. –

+2

Si escribe la distinción entre mayúsculas y minúsculas (utilizando 'else if'), su" cálculo real "solo se anida una vez en comparación con su jerarquía no anidada. ¿Cuál es el problema con eso? En su ejemplo, todo lo que guarda es una palabra clave 'else', pero tiene todos los gastos generales. – Raphael

+0

@Raphael Sí * * en ese ejemplo, pero he especificó que estoy mirando a los casos en que, por supuesto, que tengo más de una condición para comprobar - por lo general 3 o 4, por lo que mi código real se anida 3 o 4 veces. –

Respuesta

3

Es un poco relacionada con su pregunta principal, pero creo que, un enfoque más eficaz (que no requiere una excepción) para implementar return implicaría continuaciones:

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

El único problema aquí, es que debe usar explícitamente cpsunit.

EDIT1: Sí, earlyReturn(4, cond = !checkOK) se puede implementar, pero no va a ser tan general y elegante:

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

k en el fragmento anterior representa el resto de la computación. Según el valor de cond, devolvemos el valor o continuamos el cálculo.

Edit2:Any chance we might get rid of cpsunit? El problema aquí es que shift dentro de la instrucción if no está permitido sin else. El compilador se niega a convertir Unit en Unit @cps[Unit].

+0

Gracias, enfoque muy interesante. ¿Te importaría agregar algunas explicaciones más para aquellos de nosotros que no están familiarizados con las continuaciones? –

+0

¿Alguna posibilidad de que nos deshagamos de 'cpsunit' si permitimos una llamada como' earlyReturn (4, cond =! CheckOK) ', moviendo la condición a un parámetro booleano? ¿O tendrías algo más elegante en mente? Por último: ¿sigue funcionando el enfoque con continuaciones si varios de ellos están anidados? –

+0

>> "¿sigue funcionando el enfoque con continuaciones si varios de ellos están anidados?" Con algunas ediciones, sí, lo hace. –

0

creo una excepción personalizada es el instinto aquí.

Cuestiones relacionadas