tutorial started software getting examples scala

started - scala vs java



¿Por qué Scala necesita parámetros además de los métodos de parámetro cero? (5)

Currying, es por eso

Daniel hizo un gran trabajo al explicar por qué son necesarios métodos sin parámetros. Explicaré por qué se los considera claramente de los métodos de parámetro cero.

Muchas personas ven la distinción entre funciones sin parámetros y de parámetros cero como una forma vaga de azúcar sintáctico. En verdad, es puramente un artefacto de cómo Scala apoya el currying (para más información, ver más abajo una explicación más completa de lo que es currying, y por qué a todos nos gusta tanto).

Formalmente, una función puede tener cero o más listas de parámetros, con cero o más parámetros cada una.
Esto significa que los siguientes son válidos: def a , def b() , pero también el ideado def c()() y def d(x: Int)()()(y: Int) etc ...

Una función def foo = ??? tiene cero listas de parámetros. Una función def bar() = ??? tiene precisamente una lista de parámetros, con cero parámetros. La introducción de reglas adicionales que combinan las dos formas habría socavado el currying como una característica de lenguaje consistente : def a sería equivalente en forma a def b() y def c()() ambos; def d(x: Int)()()(y: Int) sería equivalente a def e()(x: Int)(y: Int)()() .

Un caso donde currying es irrelevante es cuando se trata de la interoperabilidad de Java. Java no admite currying, por lo que no hay problema con la introducción de azúcar sintáctico para métodos de parámetro cero como "test".length() (que invoca directamente java.lang.String#length() ) para invocar también como "test".length .

Una explicación rápida de currying

Scala admite una característica del lenguaje llamada ''currying'', llamada así por el matemático Haskell Curry.
Currying le permite definir funciones con varias listas de parámetros, por ejemplo:

def add(a: Int)(b: Int): Int = a + b add(2)(3) // 5

Esto es útil, porque ahora puede definir inc en términos de una aplicación parcial de add :

def inc: Int => Int = add(1) inc(2) // 3

El currying se ve a menudo como una forma de introducir estructuras de control a través de bibliotecas, por ejemplo:

def repeat(n: Int)(thunk: => Any): Unit = (1 to n) foreach { _ => thunk } repeat(2) { println("Hello, world") } // Hello, world // Hello, world

Como resumen, vea cómo la repeat abre otra oportunidad para usar el currying:

def twice: (=> Any) => Unit = repeat(2) twice { println("Hello, world") } // ... you get the picture :-)

Entiendo la diferencia entre los métodos de parámetros cero y sin parámetros, pero lo que realmente no entiendo es la elección del diseño del lenguaje que hace que los métodos sin parámetros sean necesarios.

Desventajas que puedo pensar:

  • Es confuso. Cada semana o dos hay preguntas aquí o en la lista de correo de Scala al respecto.
  • Es complicado; también tenemos que distinguir entre () => X y => X
  • Es ambiguo: ¿significa x.toFoo(y) lo que dice, o x.toFoo.apply(y) ? (Respuesta: depende de qué sobrecargas existan el método de x toFoo y las sobrecargas en el método de apply Foo , pero si hay un conflicto, no verá un error hasta que intente llamarlo).
  • Desordena la sintaxis de llamada al método de estilo del operador: no hay ningún símbolo para usar en lugar de los argumentos, al encadenar métodos o al final para evitar la interferencia de punto y coma. Con métodos zero-arg puede usar la lista de parámetros vacía () .

Actualmente, no puede tener ambos definidos en una clase: recibe un error que dice que el método ya está definido. También ambos se convierten a una Function0 .

¿Por qué no simplemente hacer que los métodos def foo y def foo() exactamente lo mismo, y permitir que se los llame con o sin paréntesis? ¿Cuáles son las ventajas de cómo es?


Además del hecho convencional mencionado (efecto secundario versus efecto no secundario), ayuda con varios casos:

Utilidad de tener empty-paren

// short apply syntax object A { def apply() = 33 } object B { def apply = 33 } A() // works B() // does not work // using in place of a curried function object C { def m()() = () } val f: () => () => Unit = C.m

Utilidad de tener no-paren

// val <=> def, var <=> two related defs trait T { def a: Int; def a_=(v: Int): Unit } trait U { def a(): Int; def a_=(v: Int): Unit } def tt(t: T): Unit = t.a += 1 // works def tu(u: U): Unit = u.a += 1 // does not work // avoiding clutter with apply the other way round object D { def a = Vector(1, 2, 3) def b() = Vector(1, 2, 3) } D.a(0) // works D.b(0) // does not work // object can stand for no-paren method trait E trait F { def f: E } trait G { def f(): E } object H extends F { object f extends E // works } object I extends G { object f extends E // does not work }

Por lo tanto, en términos de regularidad del lenguaje, tiene sentido tener la distinción (especialmente para el último caso mostrado).


En primer lugar, () => X y => X tiene absolutamente nada que ver con métodos sin parámetros.

Ahora, parece bastante tonto escribir algo como esto:

var x() = 5 val y() = 2 x() = x() + y()

Ahora, si no sigue lo que tiene que ver lo anterior con métodos sin parámetros, entonces debe buscar el principio de acceso uniforme. Todas las anteriores son declaraciones de métodos, y todas pueden ser reemplazadas por def . Es decir, suponiendo que elimines sus paréntesis.


Una cosa buena sobre un problema que surge periódicamente en el LD es que hay respuestas periódicas.

¿Quién puede resistir un hilo llamado "¿Qué pasa con nosotros?"

https://groups.google.com/forum/#!topic/scala-debate/h2Rej7LlB2A

De: martin odersky Fecha: vie, 2 de marzo de 2012 a las 12:13 p.m. Tema: Re: [scala-debate] qué pasa con nosotros ...

Lo que algunas personas piensan que está "mal con nosotros" es que estamos intentando hacer todo lo posible para que las expresiones idiomáticas de Java funcionen sin problemas en Scala. Lo principal habría sido decir que def length () y def length son diferentes, y, lo siento, String es una clase Java, por lo que debes escribir s.length (), no s.length. Trabajamos muy duro para contabilizarlo al admitir conversiones automáticas desde la última hasta la última mitad (). Eso es problemático como es. Generalizar eso para que los dos se identifiquen en el sistema de tipos sería una forma segura de condenar. ¿Cómo entonces desambiguate?

tipo Acción = () => () def foo: Acción

¿Entonces es foo de tipo Acción o ()? ¿Qué hay de foo ()?

Martín

Mi parte favorita de la ficción paulp de ese hilo:

On Fri, Mar 2, 2012 at 10:15 AM, Rex Kerr <[email protected]> wrote: >This would leave you unable to distinguish between the two with >structural types, but how often is the case when you desperately >want to distinguish the two compared to the case where distinguishing >between the two is a hassle? /** Note to maintenance programmer: It is important that this method be * callable by classes which have a ''def foo(): Int'' but not by classes which * merely have a ''def foo: Int''. The correctness of this application depends * on maintaining this distinction. * * Additional note to maintenance programmer: I have moved to zambia. * There is no forwarding address. You will never find me. */ def actOnFoo(...)

Entonces la motivación subyacente para la característica es generar este tipo de hilo de ML.

Un pedacito más de googlology:

El Jue, 1 de abril de 2010 a las 8:04 PM, Rex Kerr <[correo electrónico oculto]> escribió: el Jue, 1 de abril de 2010 a la 1:00 PM, richard emberson <[correo electrónico oculto]> escribió:

Supongo que "def getName: String" es lo mismo que "def getName (): String"

No, en realidad, no lo son. Aunque ambos llaman a un método sin parámetros, uno es un "método con cero listas de parámetros" mientras que el otro es un "método con una lista de parámetros vacía". Si quieres estar aún más perplejo, prueba def getName () (): String (¡y crea una clase con esa firma)!

Scala representa parámetros como una lista de listas, no solo una lista, y

List ()! = Lista (Lista ())

Es una especie de molestia extravagante, especialmente porque hay muy pocas diferencias entre los dos, de lo contrario, y ya que ambos pueden convertirse automáticamente en la función signature () => String.

Cierto. De hecho, cualquier combinación entre métodos sin parámetros y métodos con listas de parámetros vacíos se debe enteramente a la interoperabilidad de Java. Deben ser diferentes, pero luego tratar con los métodos de Java sería demasiado doloroso. ¿Te imaginas tener que escribir str.length () cada vez que tomas la longitud de una cuerda?

Aclamaciones


Yo diría que ambos son posibles porque se puede acceder al estado mutable con un método sin parámetros:

class X(private var x: Int) { def inc() { x += 1 } def value = x }

El value método no tiene efectos secundarios (solo accede al estado mutable). Este comportamiento se menciona explícitamente en Programación en Scala :

Tales métodos sin parámetros son bastante comunes en Scala. Por el contrario, los métodos definidos con paréntesis vacíos, como def height (): Int, se llaman métodos empty-paren. La convención recomendada es usar un método sin parámetros siempre que no haya parámetros y el método accede al estado mutable solo leyendo los campos del objeto que lo contiene (en particular, no cambia el estado mutable).

Esta convención apoya el principio de acceso uniforme [...]

Para resumir, se alienta el estilo en Scala para definir métodos que no toman parámetros y no tienen efectos secundarios como métodos sin parámetros, es decir, dejando fuera el paréntesis vacío. Por otro lado, nunca debe definir un método que tenga efectos secundarios sin paréntesis, porque las invocaciones de ese método se verían como una selección de campo.