Regreso en Scala

84

Soy un programador novato en Scala y me encontré con un comportamiento extraño.

def balanceMain(elem: List[Char]): Boolean =
  {
    if (elem.isEmpty)
      if (count == 0)
        true;
      else false;

    if (elem.head == '(')
      balanceMain(elem.tail, open, count + 1);....

Básicamente, quiero volver verdadero si elem.isEmptyy count == 0. De lo contrario, quiero devolver falso.

Ahora, arriba, he leído que no es necesario agregar una declaración de devolución en scala. Por eso lo he omitido returnarriba. Pero no devuelve el booleano. Si agrego una declaración de devolución como return true. funciona perfectamente. ¿Por que es esto entonces?

Además, ¿por qué se considera una mala práctica tener declaraciones de retorno en scala?

Jatin
fuente
2
Por lo general, no es necesaria la palabra clave return, siempre que divida su código en métodos lo suficientemente pequeños.
mauhiz
@mauhiz Gracias. ¿Puedes explicarlo? Como lo haras.
Jatin
4
parece que estás tomando el curso Coursera Scala. todo lo mejor :)
Weima

Respuestas:

139

No es tan simple como simplemente omitir la returnpalabra clave. En Scala, si no hay return, se toma la última expresión como valor de retorno. Entonces, si la última expresión es lo que desea devolver, puede omitir la returnpalabra clave. Pero si lo que desea devolver no es la última expresión, Scala no sabrá que quería devolverla .

Un ejemplo:

def f() = {
  if (something)
    "A"
  else
    "B"
}

Aquí, la última expresión de la función fes una expresión if / else que se evalúa como String. Como no hay una returnmarca explícita , Scala inferirá que desea devolver el resultado de esta expresión if / else: una cadena.

Ahora, si agregamos algo después de la expresión if / else:

def f() = {
  if (something)
    "A"
  else
    "B"

  if (somethingElse)
    1
  else
    2
}

Ahora la última expresión es una expresión if / else que se evalúa como Int. Entonces, el tipo de retorno de fserá Int. Si realmente quisiéramos que devolviera el String, entonces tenemos problemas porque Scala no tiene idea de que eso es lo que pretendíamos. Por lo tanto, tenemos que arreglarlo almacenando la Cadena en una variable y devolviéndola después de la segunda expresión if / else, o cambiando el orden para que la parte de la Cadena ocurra al final.

Finalmente, podemos evitar la returnpalabra clave incluso con una expresión if-else anidada como la suya:

def f() = {
  if(somethingFirst) {
    if (something)      // Last expression of `if` returns a String
     "A"
    else
     "B"
  }
  else {
    if (somethingElse)
      1
    else
      2

    "C"                // Last expression of `else` returns a String
  }

}

dhg
fuente
4
¡Por todos los dioses, gracias! Estuve luchando con el mismo problema durante horas (también haciendo el curso en Coursera) y no pude entender por qué se requería la devolución.
Igor Rodriguez
para el primer ejemplo, qué sucede si agrega devoluciones. es decir return "A"y return "B"?
SamAko
@ T.Rex volvería a la persona que llama de f () con el valor "A" o "B". Pero como expliqué en mi respuesta. Nunca use return en Scala. Las declaraciones If en Scala funcionan de manera funcional. Evalúan algo con un tipo. Como el operador ternario en Java (? :). Ejemplo: val foo = if (mybool) "A" else "B" - foo será una cadena que contenga "A" o "B". Del mismo modo, piense en una función que no devuelve algo, sino que evalúa el valor de la última expresión en ella.
Grmpfhmbl
23

Este tema es en realidad un poco más complicado como se describe en las respuestas hasta ahora. Esta publicación de blog de Rob Norris lo explica con más detalle y ofrece ejemplos sobre cuándo el uso de return realmente romperá su código (o al menos tendrá efectos no obvios).

En este punto, permítanme citar la esencia de la publicación. La declaración más importante está al principio. Imprime esto como un póster y colócalo en tu pared :-)

La returnpalabra clave no es "opcional" ni "inferida"; cambia el significado de su programa y nunca debe utilizarlo.

Da un ejemplo, donde realmente rompe algo, cuando inserta una función

// Inline add and addR
def sum(ns: Int*): Int = ns.foldLeft(0)((n, m) => n + m) // inlined add

scala> sum(33, 42, 99)
res2: Int = 174 // alright

def sumR(ns: Int*): Int = ns.foldLeft(0)((n, m) => return n + m) // inlined addR

scala> sumR(33, 42, 99)
res3: Int = 33 // um.

porque

Una returnexpresión, cuando se evalúa, abandona el cálculo actual y regresa al llamador del método en el que returnaparece.

Este es solo uno de los ejemplos que se dan en la publicación vinculada y es el más fácil de entender. Hay más y les animo mucho a que vayan allí, lean y comprendan.

Cuando viene de lenguajes imperativos como Java, esto puede parecer extraño al principio, pero una vez que se acostumbre a este estilo, tendrá sentido. Permítanme cerrar con otra cita:

Si se encuentra en una situación en la que cree que desea regresar antes, debe repensar la forma en que ha definido su cálculo.

Grmpfhmbl
fuente
12
No estoy de acuerdo con Rob, en mi opinión, no debe usar returnsolo en expresiones lambda, pero esta es una elección de diseño de mierda en el lenguaje, el código ni siquiera debería compilarse (en Python esto se evita al tener una lambdapalabra clave sin la declaración de retorno). .. en cada otro caso, no veo un problema real en el uso returnsi desea devolver un valor (y sí, salir de la ejecución del método porque es para lo que se usa el retorno en todos los idiomas)
daveoncode
2
Eres libre de tener tu opinión, pero mucha gente está de acuerdo en que usar return en Scala es al menos de mal estilo. Te reto a que encuentres ejemplos de documentos oficiales de Scala que usen return. Es AFAIK ni siquiera se explica en los documentos oficiales, solo en el documento de especificaciones (capítulo 6.20). Para citar al propio Martin Odersky (Programación en Scala) "El estilo recomendado para los métodos es, de hecho, evitar tener declaraciones de retorno explícitas, y especialmente múltiples. En su lugar, piense en cada método como una expresión que produce un valor, que se devuelve". La primera edición de este libro está disponible gratis en línea artima.com/pins1ed
Grmpfhmbl
4

No programo Scala, pero uso otro lenguaje con retornos implícitos (Ruby). Tiene código después de su if (elem.isEmpty)bloque: la última línea de código es lo que se devuelve, por lo que no obtiene lo que espera.

EDITAR: Aquí también hay una forma más sencilla de escribir su función. Simplemente use el valor booleano de isEmpty y cuente para devolver verdadero o falso automáticamente:

def balanceMain(elem: List[Char]): Boolean =
{
    elem.isEmpty && count == 0
}
jmdeldin
fuente
Gracias. Pero solo quiero regresar si elem.isEmpty && count == 0 return true; de ​​lo contrario, continúe el bloque. Lo anterior volverá incluso si es falso.
Jatin
@Jatin: Ah, no me di cuenta de eso. explicitEn este caso, sería apropiado regresar temprano y entonces.
jmdeldin
@Frank: Tampoco está definido en el código del OP. Supuse que era una llamada a un método.
jmdeldin
4

No escriba ifdeclaraciones sin su correspondiente else. Una vez que agregue el elsea su fragmento, verá que su truey falseson de hecho las últimas expresiones de la función.

def balanceMain(elem: List[Char]): Boolean =
  {
    if (elem.isEmpty)
      if (count == 0)
        true
      else
        false
    else
      if (elem.head == '(')
        balanceMain(elem.tail, open, count + 1)
      else....
Bart Schuller
fuente
5
Lo apliqué, obtuve 3 IF anidados y se ve feo. ¿Hay algún patrón para que se vea mejor?
Capacytron
4

De forma predeterminada, se devolverá la última expresión de una función. En su ejemplo, hay otra expresión después del punto, donde desea su valor de retorno. Si desea devolver algo antes de su última expresión, aún debe usar return.

Puede modificar su ejemplo de esta manera, para devolver un Booleande la primera parte

def balanceMain(elem: List[Char]): Boolean = {
  if (elem.isEmpty) {
    // == is a Boolean resulting function as well, so your can write it this way
    count == 0
  } else {
    // keep the rest in this block, the last value will be returned as well
    if (elem.head == "(") {
      balanceMain(elem.tail, open, count + 1)
    }
    // some more statements
    ...
    // just don't forget your Boolean in the end
    someBoolExpression
  }
}
Tharabas
fuente
9
No "declaración". "expresión"
Viktor Klang
0

Utilice la coincidencia de casos para fines de devolución anticipada. Te obligará a declarar todas las ramas de retorno explícitamente, evitando el error descuidado de olvidar escribir return en alguna parte.

Zhu Jinxuan
fuente