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?
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?
Respuestas:
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 FunctionN
en 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 def
declaración: todo sobre a def
excepto su cuerpo.
Valor declaraciones y definiciones y declaraciones de variables y definiciones son val
y var
declaraciones, 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 def
declaració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 FunctionX
rasgos, 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 apply
mé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 AnyRef
devolució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 f
es una función y m
es 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) => R
firmas 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 m
tipo esté (List[Int])AnyRef
en (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 AbstractFunction1
clase 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 f
arriba puede especificar necesariamente el tipo de List
recepción ( List[Int]
en el ejemplo), m
puede 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.
val f = m
compilador 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 this
interior del apply
método no se refiere al AnyRef
objeto, sino al objeto en cuyo método val f = m _
se evalúa (el exterior this
, por así decirlo). ), ya que se this
encuentra entre los valores que captura el cierre (como, por ejemplo, return
como se indica a continuación).
Una gran diferencia práctica entre un método y una función es lo que return
significa. return
solo 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
for (a <- List(1, 2, 3)) { return ... }
? Eso se desestima a un cierre.
return
devuelve un valor de la función, y alguna forma de escape
o break
o continue
para volver de métodos.
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
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
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>
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 Function1
instancia 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
En Scala 2.13, a diferencia de las funciones, los métodos pueden tomar / regresar
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
Prácticamente, un programador de Scala solo necesita conocer las siguientes tres reglas para usar las funciones y métodos correctamente:
def
y 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.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.