Uso de def, val y var en scala

158
class Person(val name:String,var age:Int )
def person = new Person("Kumar",12)
person.age = 20
println(person.age)

Estas líneas de código de salida 12, a pesar de que person.age=20se ejecutó con éxito. Descubrí que esto sucede porque usé def en def person = new Person("Kumar",12). Si uso var o val, la salida es 20. Entiendo que el valor predeterminado es val in scala. Esta:

def age = 30
age = 45

... da un error de compilación porque es un valor por defecto. ¿Por qué el primer conjunto de líneas anteriores no funciona correctamente y, sin embargo, tampoco produce errores?

Byju Veedu
fuente

Respuestas:

254

Hay tres formas de definir cosas en Scala:

  • defdefine un método
  • valdefine un valor fijo (que no se puede modificar)
  • vardefine una variable (que se puede modificar)

Mirando tu código:

def person = new Person("Kumar",12)

Esto define un nuevo método llamado person. Puede llamar a este método solo sin ()porque está definido como método sin parámetros. Para el método de paren vacío, puede llamarlo con o sin '()'. Si simplemente escribes:

person

entonces está llamando a este método (y si no asigna el valor de retorno, simplemente se descartará). En esta línea de código:

person.age = 20

lo que sucede es que primero llama al personmétodo, y en el valor de retorno (una instancia de clase Person) está cambiando la agevariable miembro.

Y la última línea:

println(person.age)

Aquí está llamando nuevamente al personmétodo, que devuelve una nueva instancia de clase Person(con el ageconjunto a 12). Es lo mismo que esto:

println(person().age)
Jesper
fuente
27
Para confundir las cosas, el estado interno de a valse puede cambiar pero el objeto al que se refiere un val no. A valno es una constante.
pferrel
55
Para confundir aún más las cosas, val (y tal vez var también, no lo he probado) se puede usar para definir una función. Cuando se usa def para definir una función / método, el cuerpo de def se evalúa cada vez que se llama. Cuando se usa val, se evalúa solo en el punto de definición. Ver stackoverflow.com/questions/18887264/…
melston
1
@melston Sí, pero un método y una función tampoco son exactamente lo mismo .
Jesper
3
para confundir aún más las cosas, def también se puede usar para definir variables miembro de una clase, no necesariamente para usar var.
Peiti Li
2
@pferrel no es realmente confuso. Igual que con la final de Java. Puede marcar un Listcomo final, pero podría modificar su contenido.
jFrenetic
100

Comenzaría por la distinción que existe en Scala entre def , val y var .

  • def : define una etiqueta inmutable para el contenido del lado derecho que se evalúa perezosamente : evalúe por nombre.

  • val - define una etiqueta inmutable para el contenido del lado derecho que se evalúa con entusiasmo / de inmediato - evaluado por valor.

  • var : define una variable mutable , inicialmente establecida en el contenido evaluado del lado derecho.

Ejemplo, def

scala> def something = 2 + 3 * 4 
something: Int
scala> something  // now it's evaluated, lazily upon usage
res30: Int = 14

Ejemplo, val

scala> val somethingelse = 2 + 3 * 5 // it's evaluated, eagerly upon definition
somethingelse: Int = 17

Ejemplo var

scala> var aVariable = 2 * 3
aVariable: Int = 6

scala> aVariable = 5
aVariable: Int = 5

Según lo anterior, las etiquetas de def y val no se pueden reasignar, y en caso de cualquier intento, se generará un error como el siguiente:

scala> something = 5 * 6
<console>:8: error: value something_= is not a member of object $iw
       something = 5 * 6
       ^

Cuando la clase se define como:

scala> class Person(val name: String, var age: Int)
defined class Person

y luego instanciado con:

scala> def personA = new Person("Tim", 25)
personA: Person

se crea una etiqueta inmutable para esa instancia específica de Persona (es decir, 'personaA'). Cada vez que el campo mutable 'edad' necesita ser modificado, tal intento falla:

scala> personA.age = 44
personA.age: Int = 25

como se esperaba, 'age' es parte de una etiqueta no mutable. La forma correcta de trabajar en esto consiste en usar una variable mutable, como en el siguiente ejemplo:

scala> var personB = new Person("Matt", 36)
personB: Person = Person@59cd11fe

scala> personB.age = 44
personB.age: Int = 44    // value re-assigned, as expected

como claro, a partir de la referencia de variable mutable (es decir, 'persona B') es posible modificar el campo mutable de clase 'edad'.

Todavía enfatizaría el hecho de que todo proviene de la diferencia mencionada anteriormente, eso tiene que estar claro para cualquier programador de Scala.

Paolo Maresca
fuente
No creo que la explicación anterior sea correcta. Ver las otras respuestas.
Por Mildner
@PerMildner ¿Puede explicar qué es lo que está mal en la respuesta anterior?
Syed Souban
No recuerdo cuál fue mi queja original. Sin embargo, la última parte de la respuesta, sobre personAet al. parece apagado Si la modificación del agemiembro funciona o no es independiente de si usa def personAo no var personB. La diferencia es que en el def personAcaso de que esté modificando la Personinstancia devuelta de su primera evaluación de personA. Esta instancia se modifica, pero no es lo que se devuelve cuando una vez más evalúa personA. En cambio, la segunda vez que lo haces, personA.agelo estás haciendo de manera efectiva new Person("Tim",25).age.
Por Mildner
29

Con

def person = new Person("Kumar", 12) 

está definiendo una función / variable perezosa que siempre devuelve una nueva instancia de Person con el nombre "Kumar" y edad 12. Esto es totalmente válido y el compilador no tiene motivos para quejarse. Llamar a person.age devolverá la edad de esta instancia de Person recién creada, que siempre es 12.

Cuando se escribe

person.age = 45

asigna un nuevo valor a la propiedad age en la clase Person, que es válido ya que age se declara como var. El compilador se quejará si intenta reasignarlo personcon un nuevo objeto Person como

person = new Person("Steve", 13)  // Error
Kintaro
fuente
Si. Este punto se puede demostrar fácilmente llamando al método hashCode en la persona A
Nilanjan Sarkar
26

Para proporcionar otra perspectiva, "def" en Scala significa algo que se evaluará cada vez que se use, mientras que val es algo que se evalúa de inmediato y solo una vez . Aquí, la expresión def person = new Person("Kumar",12)implica que siempre que usemos "persona" recibiremos una new Person("Kumar",12)llamada. Por lo tanto, es natural que los dos "person.age" no estén relacionados.

Así entiendo Scala (probablemente de una manera más "funcional"). No estoy seguro si

def defines a method
val defines a fixed value (which cannot be modified)
var defines a variable (which can be modified)

Sin embargo, es realmente lo que Scala pretende decir. Realmente no me gusta pensar de esa manera al menos ...

xji
fuente
20

Como ya dice Kintaro, la persona es un método (debido a def) y siempre devuelve una nueva instancia de Person. Como descubrió, funcionaría si cambia el método a var o val:

val person = new Person("Kumar",12)

Otra posibilidad sería:

def person = new Person("Kumar",12)
val p = person
p.age=20
println(p.age)

Sin embargo, person.age=20en su código está permitido, ya que obtiene una Personinstancia del personmétodo, y en esta instancia puede cambiar el valor de a var. El problema es que después de esa línea no tiene más referencia a esa instancia (ya que cada llamada a personproducirá una nueva instancia).

Esto no es nada especial, tendría exactamente el mismo comportamiento en Java:

class Person{ 
   public int age; 
   private String name;
   public Person(String name; int age) {
      this.name = name;  
      this.age = age;
   }
   public String name(){ return name; }
}

public Person person() { 
  return new Person("Kumar", 12); 
}

person().age = 20;
System.out.println(person().age); //--> 12
Landei
fuente
8

Tomemos esto:

class Person(val name:String,var age:Int )
def person =new Person("Kumar",12)
person.age=20
println(person.age)

y reescribirlo con un código equivalente

class Person(val name:String,var age:Int )
def person =new Person("Kumar",12)
(new Person("Kumar", 12)).age_=(20)
println((new Person("Kumar", 12)).age)

Ver, defes un método. Se ejecutará cada vez que se llame y cada vez que regrese (a) new Person("Kumar", 12). Y esto no es un error en la "asignación" porque no es realmente una asignación, sino solo una llamada al age_=método (proporcionado por var).

Daniel C. Sobral
fuente