Diferencia entre método y función en Scala


254

Leí Scala Functions (parte de Another tour of Scala ). En ese post declaró:

Los métodos y las funciones no son lo mismo.

Pero no explicó nada al respecto. ¿Qué estaba tratando de decir?




Una pregunta de seguimiento con buenas respuestas: Funciones frente a métodos en Scala
Josiah Yoder

Respuestas:


238

Jim tiene esto bastante cubierto en su publicación de blog , pero estoy publicando un resumen aquí como referencia.

Primero, veamos qué nos dice la especificación Scala. El Capítulo 3 (tipos) nos informa sobre los Tipos de funciones (3.2.9) y los Tipos de métodos (3.3.1). El Capítulo 4 (declaraciones básicas) habla de la Declaración y Definiciones de Valor (4.1), Declaración y Definiciones Variables (4.2) y Declaraciones y Definiciones de Funciones (4.6). El Capítulo 6 (expresiones) habla de funciones anónimas (6.23) y valores de método (6.7). Curiosamente, los valores de la función se mencionan una vez en 3.2.9, y en ningún otro lugar.

Un tipo de función es (aproximadamente) un tipo de la forma (T1, ..., Tn) => U , que es una abreviatura del rasgo FunctionNen la biblioteca estándar. Las funciones anónimas y los valores de método tienen tipos de función, y los tipos de función se pueden usar como parte de las declaraciones y definiciones de valores, variables y funciones. De hecho, puede ser parte de un tipo de método.

Un tipo de método es un tipo sin valor . Eso significa que no hay valor, ni objeto, ni instancia, con un tipo de método. Como se mencionó anteriormente, un valor de método en realidad tiene un tipo de función . Un tipo de método es una defdeclaración: todo sobre a defexcepto su cuerpo.

Valor declaraciones y definiciones y declaraciones de variables y definiciones son valy vardeclaraciones, incluyendo tanto el tipo y valor - que puede ser, respectivamente, del tipo de función y funciones anónimas o Valores Método . Tenga en cuenta que, en la JVM, estos (valores de método) se implementan con lo que Java llama "métodos".

Una declaración de función es una defdeclaración, que incluye el tipo y el cuerpo . La parte de tipo es el Tipo de método, y el cuerpo es una expresión o un bloque . Esto también se implementa en la JVM con lo que Java llama "métodos".

Finalmente, una función anónima es una instancia de un tipo de función (es decir, una instancia del rasgo FunctionN), ¡y un valor de método es lo mismo! La distinción es que un Valor de método se crea a partir de métodos, ya sea mediante la colocación de un guión bajo ( m _es un valor de método correspondiente a la "declaración de función" ( def) m), o mediante un proceso llamado eta-expansion , que es como un método de conversión automática desde funcionar.

Eso es lo que dicen las especificaciones, así que permítanme poner esto por adelantado: ¡no usamos esa terminología! Conduce a demasiada confusión entre la llamada "declaración de función" , que es parte del programa (capítulo 4 - declaraciones básicas) y "función anónima" , que es una expresión, y "tipo de función" , que es, bueno un tipo, un rasgo.

La siguiente terminología, y utilizada por programadores experimentados de Scala, hace un cambio con respecto a la terminología de la especificación: en lugar de decir declaración de función , decimos método . O incluso la declaración del método. Además, observamos que las declaraciones de valor y las declaraciones de variables también son métodos para fines prácticos.

Entonces, dado el cambio anterior en la terminología, aquí hay una explicación práctica de la distinción.

Una función es un objeto que incluye uno de los FunctionXrasgos, tales como Function0, Function1, Function2, etc. Se podría incluyendo PartialFunction, así, que se extiende en realidad Function1.

Veamos la firma de tipo para uno de estos rasgos:

trait Function2[-T1, -T2, +R] extends AnyRef

Este rasgo tiene un método abstracto (también tiene algunos métodos concretos):

def apply(v1: T1, v2: T2): R

Y eso nos dice todo lo que hay que saber al respecto. Una función tiene un applymétodo que recibe N parámetros de los tipos T1 , T2 , ..., TN y devuelve algo de tipo R. Es contra-variante en los parámetros que recibe, y co-variante en el resultado.

Esa variación significa que a Function1[Seq[T], String]es un subtipo de Function1[List[T], AnyRef]. Ser un subtipo significa que se puede usar en lugar de él. Uno puede ver fácilmente que si voy a llamar f(List(1, 2, 3))y esperar una AnyRefdevolución, cualquiera de los dos tipos anteriores funcionaría.

Ahora, ¿cuál es la similitud de un método y una función? Bueno, si fes una función y mes un método local para el alcance, entonces ambos pueden llamarse así:

val o1 = f(List(1, 2, 3))
val o2 = m(List(1, 2, 3))

Estas llamadas son realmente diferentes, porque la primera es solo un azúcar sintáctico. Scala lo expande a:

val o1 = f.apply(List(1, 2, 3))

Lo cual, por supuesto, es un método llamado a objeto f. Las funciones también tienen otros azúcares sintácticos para su ventaja: literales de función (dos de ellos, en realidad) y (T1, T2) => Rfirmas de tipo. Por ejemplo:

val f = (l: List[Int]) => l mkString ""
val g: (AnyVal) => String = {
  case i: Int => "Int"
  case d: Double => "Double"
  case o => "Other"
}

Otra similitud entre un método y una función es que el primero se puede convertir fácilmente en el segundo:

val f = m _

Scala ampliará eso , suponiendo que el mtipo esté (List[Int])AnyRefen (Scala 2.7):

val f = new AnyRef with Function1[List[Int], AnyRef] {
  def apply(x$1: List[Int]) = this.m(x$1)
}

En Scala 2.8, en realidad usa una AbstractFunction1clase para reducir el tamaño de las clases.

Tenga en cuenta que no se puede convertir al revés, de una función a un método.

Sin embargo, los métodos tienen una gran ventaja (bueno, dos: pueden ser un poco más rápidos): pueden recibir parámetros de tipo . Por ejemplo, si bien farriba puede especificar necesariamente el tipo de Listrecepción ( List[Int]en el ejemplo), mpuede parametrizarlo:

def m[T](l: List[T]): String = l mkString ""

Creo que esto cubre casi todo, pero me complacerá complementar esto con respuestas a cualquier pregunta que pueda quedar.


26
Esta explicación es muy clara. Bien hecho. Desafortunadamente, tanto el libro de Odersky / Venners / Spoon como la especificación de Scala usan las palabras "función" y "método" de manera intercambiable. (Es más probable que digan "función" donde "método" sería más claro, pero a veces también ocurre de otra manera, por ejemplo, la sección 6.7 de la especificación, que cubre la conversión de métodos a funciones, se denomina "Valores de método". Ugh .) Creo que el uso laxo de estas palabras ha generado mucha confusión cuando la gente trata de aprender el idioma.
Seth Tisue

44
@Seth Lo sé, lo sé: PinS fue el libro que me enseñó Scala. Aprendí mejor por las malas, es decir, paulp me puso en orden.
Daniel C. Sobral

44
¡Gran explicación! Tengo una cosa que agregar: cuando cita la expansión del val f = mcompilador como val f = new AnyRef with Function1[List[Int], AnyRef] { def apply(x$1: List[Int]) = this.m(x$1) }debe señalar que el thisinterior del applymétodo no se refiere al AnyRefobjeto, sino al objeto en cuyo método val f = m _se evalúa (el exterior this , por así decirlo). ), ya que se thisencuentra entre los valores que captura el cierre (como, por ejemplo, returncomo se indica a continuación).
Holger Peine

1
@ DanielC.Sobral, ¿cuál es el libro PinS que mencionaste? También estoy interesado en aprender Scala, y no he encontrado un libro con ese nombre,
tldr

55
Programación @tldr en Scala , por Odersky et all. Es la abreviatura común (¡me dijeron que no les gustaba PiS por alguna razón! :)
Daniel C. Sobral

67

Una gran diferencia práctica entre un método y una función es lo que returnsignifica. returnsolo regresa de un método. Por ejemplo:

scala> val f = () => { return "test" }
<console>:4: error: return outside method definition
       val f = () => { return "test" }
                       ^

Al regresar de una función definida en un método, se realiza un retorno no local:

scala> def f: String = {                 
     |    val g = () => { return "test" }
     | g()                               
     | "not this"
     | }
f: String

scala> f
res4: String = test

Mientras que regresar de un método local solo regresa de ese método.

scala> def f2: String = {         
     | def g(): String = { return "test" }
     | g()
     | "is this"
     | }
f2: String

scala> f2
res5: String = is this

99
Esto se debe a que el cierre captura el retorno.
Daniel C. Sobral

44
No puedo pensar en una sola vez que me gustaría 'regresar' de una función al ámbito no local. De hecho, puedo ver eso como un grave problema de seguridad si una función puede decidir que quiere ir más atrás en la pila. Se siente como un salto largo, pero es mucho más fácil equivocarse accidentalmente. Sin embargo, he notado que scalac no me deja regresar de las funciones. ¿Eso significa que esta abominación ha sido eliminada del lenguaje?
root

2
@root: ¿qué hay de regresar desde adentro de a for (a <- List(1, 2, 3)) { return ... }? Eso se desestima a un cierre.
Ben Lings

Hmm ... Bueno, ese es un caso de uso razonable. Todavía tiene el potencial de conducir a horribles problemas difíciles de depurar, pero eso lo pone en un contexto más sensible.
root

1
Sinceramente, usaría una sintaxis diferente. tener returndevuelve un valor de la función, y alguna forma de escapeo breako continuepara volver de métodos.
Ryan The Leach

38

función Se puede invocar una función con una lista de argumentos para producir un resultado. Una función tiene una lista de parámetros, un cuerpo y un tipo de resultado. Las funciones que son miembros de una clase, rasgo u objeto singleton se denominan métodos . Las funciones definidas dentro de otras funciones se denominan funciones locales. Las funciones con el tipo de resultado de la Unidad se denominan procedimientos. Las funciones anónimas en el código fuente se denominan literales de función. En tiempo de ejecución, los literales de función se instancian en objetos llamados valores de función.

Programación en Scala Segunda Edición. Martin Odersky - Cuchara Lex - Bill Venners


1
Una función puede pertenecer a una clase como def o como val / var. Solo los def son métodos.
Josiah Yoder

29

Deja que digas que tienes una lista

scala> val x =List.range(10,20)
x: List[Int] = List(10, 11, 12, 13, 14, 15, 16, 17, 18, 19)

Definir un método

scala> def m1(i:Int)=i+2
m1: (i: Int)Int

Definir una función

scala> (i:Int)=>i+2
res0: Int => Int = <function1>

scala> x.map((x)=>x+2)
res2: List[Int] = List(12, 13, 14, 15, 16, 17, 18, 19, 20, 21)

Método de aceptación de argumento

scala> m1(2)
res3: Int = 4

Definición de función con val

scala> val p =(i:Int)=>i+2
p: Int => Int = <function1>

El argumento para funcionar es opcional

 scala> p(2)
    res4: Int = 4

scala> p
res5: Int => Int = <function1>

El argumento al método es obligatorio

scala> m1
<console>:9: error: missing arguments for method m1;
follow this method with `_' if you want to treat it as a partially applied function

Consulte el siguiente Tutorial que explica cómo pasar otras diferencias con ejemplos como otro ejemplo de diferencia con la Función Método Vs, Usar la función como Variables, crear la función que devolvió la función


13

Las funciones no admiten valores predeterminados de parámetros. Los métodos lo hacen. La conversión de un método a una función pierde los valores predeterminados de los parámetros. (Scala 2.8.1)


55
¿Hay alguna razón para esto?
corazza

7

Hay un buen artículo aquí. a partir del cual se toman la mayoría de mis descripciones. Solo una breve comparación de funciones y métodos con respecto a mi comprensión. Espero eso ayude:

Funciones : son básicamente un objeto. Más precisamente, las funciones son objetos con un método de aplicación; Por lo tanto, son un poco más lentos que los métodos debido a su sobrecarga. Es similar a los métodos estáticos en el sentido de que son independientes de un objeto a invocar. Un ejemplo simple de una función es como se muestra a continuación:

val f1 = (x: Int) => x + x
f1(2)  // 4

La línea de arriba no es más que asignar un objeto a otro como object1 = object2. En realidad, el object2 en nuestro ejemplo es una función anónima y el lado izquierdo obtiene el tipo de objeto debido a eso. Por lo tanto, ahora f1 es un objeto (Función). La función anónima es en realidad una instancia de Function1 [Int, Int] que significa una función con 1 parámetro de tipo Int y valor de retorno de tipo Int. Llamar a f1 sin los argumentos nos dará la firma de la función anónima (Int => Int =)

Métodos : no son objetos sino asignados a una instancia de una clase, es decir, un objeto. Exactamente lo mismo que el método en java o las funciones miembro en c ++ (como Raffi Khatchadourian señaló en un comentario a esta pregunta ) y etc. Un ejemplo simple de un método es como se muestra a continuación:

def m1(x: Int) = x + x
m1(2)  // 4

La línea anterior no es una simple asignación de valor, sino una definición de un método. Cuando invocas este método con el valor 2 como la segunda línea, la x se sustituye por 2 y el resultado se calculará y obtendrás 4 como salida. Aquí obtendrá un error si simplemente escribe m1 porque es un método y necesita el valor de entrada. Al usar _ puede asignar un método a una función como la siguiente:

val f2 = m1 _  // Int => Int = <function1>

¿Qué significa "asignar un método a una función"? ¿Significa que ahora tiene un objeto que se comporta de la misma manera que el método?
K. M

@KM: val f2 = m1 _ es equivalente a val f2 = nueva Función1 [Int, Int] {def m1 (x: Int) = x + x};
Sasuke

3

Aquí hay una gran publicación de Rob Norris que explica la diferencia, aquí hay un TL; DR

Los métodos en Scala no son valores, pero las funciones sí lo son. Puede construir una función que delegue a un método a través de η-expansion (desencadenado por el subrayado final).

con la siguiente definición:

un método es algo definido con def y un valor es algo que puede asignar a un val

En pocas palabras ( extracto del blog ):

Cuando definimos un método, vemos que no podemos asignarlo a a val.

scala> def add1(n: Int): Int = n + 1
add1: (n: Int)Int

scala> val f = add1
<console>:8: error: missing arguments for method add1;
follow this method with `_' if you want to treat it as a partially applied function
       val f = add1

Tenga en cuenta también el tipo de add1, que no parece normal; No puede declarar una variable de tipo (n: Int)Int. Los métodos no son valores.

Sin embargo, al agregar el operador de postfijo de expansión η (η se pronuncia "eta"), podemos convertir el método en un valor de función. Tenga en cuenta el tipo de f.

scala> val f = add1 _
f: Int => Int = <function1>

scala> f(3)
res0: Int = 4

El efecto de _es realizar el equivalente de lo siguiente: construimos una Function1instancia que delega a nuestro método.

scala> val g = new Function1[Int, Int] { def apply(n: Int): Int = add1(n) }
g: Int => Int = <function1>

scala> g(3)
res18: Int = 4

1

En Scala 2.13, a diferencia de las funciones, los métodos pueden tomar / regresar

  • parámetros de tipo (métodos polimórficos)
  • parámetros implícitos
  • tipos dependientes

Sin embargo, estas restricciones se levantan en dotty (Scala 3) por los tipos de funciones polimórficas # 4672 , por ejemplo, dotty versión 0.23.0-RC1 habilita la siguiente sintaxis

Parámetros de tipo

def fmet[T](x: List[T]) = x.map(e => (e, e))
val ffun = [T] => (x: List[T]) => x.map(e => (e, e))

Parámetros implícitos ( parámetros de contexto )

def gmet[T](implicit num: Numeric[T]): T = num.zero
val gfun: [T] => Numeric[T] ?=> T = [T] => (using num: Numeric[T]) => num.zero

Tipos dependientes

class A { class B }
def hmet(a: A): a.B = new a.B
val hfun: (a: A) => a.B = hmet

Para más ejemplos, vea tests / run / polymorphic-functions.scala


0

Prácticamente, un programador de Scala solo necesita conocer las siguientes tres reglas para usar las funciones y métodos correctamente:

  • Métodos definidos por defy literales de función definidos por=> son funciones. Está definido en la página 143, Capítulo 8 del libro de Programación en Scala, 4a edición.
  • Los valores de función son objetos que se pueden pasar como cualquier valor. Los literales de función y las funciones parcialmente aplicadas son valores de función.
  • Puede omitir el subrayado de una función parcialmente aplicada si se requiere un valor de función en un punto del código. Por ejemplo:someNumber.foreach(println)

Después de cuatro ediciones de Programación en Scala, todavía es un problema para las personas diferenciar los dos conceptos importantes: función y valor de función porque todas las ediciones no dan una explicación clara. La especificación del lenguaje es demasiado complicada. Encontré que las reglas anteriores son simples y precisas.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.