Estructura rápida y mutante

96

Hay algo que no entiendo del todo cuando se trata de tipos de valores mutantes en Swift.

Como dice el iBook "El lenguaje de programación Swift": De forma predeterminada, las propiedades de un tipo de valor no se pueden modificar desde sus métodos de instancia.

Y para que esto sea posible, podemos declarar métodos con la mutatingpalabra clave dentro de estructuras y enumeraciones.

Lo que no me queda del todo claro es lo siguiente: puedes cambiar una var desde fuera de una estructura, pero no puedes cambiarla desde sus propios métodos. Esto me parece contrario a la intuición, ya que en los lenguajes orientados a objetos, generalmente se intenta encapsular las variables para que solo se puedan cambiar desde adentro. Con las estructuras esto parece ser al revés. Para ampliar, aquí hay un fragmento de código:

struct Point {
    var x = 0, y = 0
    mutating func moveToX(x: Int, andY y:Int) { //Needs to be a mutating method in order to work
        self.x = x
        self.y = y
    }
}

var p = Point(x: 1, y: 2)
p.x = 3 //Works from outside the struct!
p.moveToX(5, andY: 5) 

¿Alguien sabe la razón por la que las estructuras no pueden cambiar su contenido desde dentro de su propio contexto, mientras que el contenido se puede cambiar fácilmente en otro lugar?

Mike Seghers
fuente

Respuestas:

80

El atributo de mutabilidad está marcado en un almacenamiento (constante o variable), no en un tipo. Puede pensar que la estructura tiene dos modos: mutable e inmutable . Si asigna un valor de estructura a un almacenamiento inmutable (lo llamamos leto constante en Swift), el valor se convierte en modo inmutable y no puede cambiar ningún estado en el valor. (incluida la llamada a cualquier método mutante)

Si el valor se asigna a un almacenamiento mutable (lo llamamos varo variable en Swift), puede modificar su estado y se permite la llamada al método mutante.

Además, las clases no tienen este modo inmutable / mutable. En mi opinión, esto se debe a que las clases se utilizan generalmente para representar entidades referenciables . Y la entidad a la que se puede hacer referencia suele ser mutable porque es muy difícil hacer y administrar gráficos de referencia de entidades de manera inmutable con un rendimiento adecuado. Es posible que agreguen esta función más adelante, pero al menos no ahora.

Para los programadores de Objective-C, los conceptos mutables / inmutables son muy familiares. En Objective-C teníamos dos clases separadas para cada concepto, pero en Swift, puedes hacer esto con una estructura. Medio trabajo.

Para los programadores de C / C ++, este también es un concepto muy familiar. Esto es exactamente lo que consthacen las palabras clave en C / C ++.

Además, el valor inmutable se puede optimizar muy bien. En teoría, el compilador Swift (o LLVM) puede realizar una copia-elisión en los valores pasados let, como en C ++. Si usa la estructura inmutable sabiamente, superará a las clases refcontadas.

Actualizar

Como @Joseph afirmó que esto no explica por qué , estoy agregando un poco más.

Las estructuras tienen dos tipos de métodos. métodos simples y mutantes . El método simple implica inmutable (o no mutante) . Esta separación sólo existe para apoyar inmutables semántica. Un objeto en modo inmutable no debería cambiar su estado en absoluto.

A continuación, los métodos inmutables deben garantizar esta inmutabilidad semántica . Lo que significa que no debería cambiar ningún valor interno. Así que el compilador no permite ningún cambio de estado de sí mismo en un método inmutable. Por el contrario, los métodos de mutación son libres de modificar estados.

Y luego, es posible que tenga una pregunta de por qué inmutable es el defecto? Esto se debe a que es muy difícil predecir el estado futuro de los valores mutantes, y eso generalmente se convierte en la principal fuente de dolores de cabeza y errores. Mucha gente estuvo de acuerdo en que la solución es evitar cosas mutables, y luego inmutable por defecto estuvo en la parte superior de la lista de deseos durante décadas en los lenguajes de la familia C / C ++ y sus derivaciones.

Ver estilo puramente funcional para más detalles. De todos modos, todavía necesitamos cosas mutables porque las cosas inmutables tienen algunas debilidades, y discutir sobre ellas parece estar fuera de tema.

Espero que esto ayude.

eonil
fuente
2
Entonces, básicamente, puedo interpretarlo como: Una estructura no sabe de antemano si será mutable o inmutable, por lo que asume inmutabilidad a menos que se indique de manera diferente. Visto así, de hecho tiene sentido. ¿Es esto correcto?
Mike Seghers
1
@MikeSeghers Creo que tiene sentido.
eonil
3
Si bien esta es hasta ahora la mejor de dos respuestas, no creo que responda POR QUÉ, de forma predeterminada, las propiedades de un tipo de valor no se pueden modificar desde sus métodos de instancia. insinúas que es porque las estructuras predeterminadas son inmutables, pero no creo que eso sea cierto
Joseph Knight
4
@JosephKnight No es que las estructuras predeterminadas sean inmutables. Es que los métodos de estructuras predeterminados son inmutables. Piense en C ++ con la suposición inversa. En los métodos de C ++ de forma predeterminada, esto no es constante, debe agregar explícitamente un constal método si desea que tenga un 'const this' y se permita en instancias constantes (los métodos normales no se pueden usar si la instancia es constante). Swift hace lo mismo con lo contrario por defecto: un método tiene un 'const this' por defecto y usted lo marca de otra manera si tiene que estar prohibido para instancias constantes.
Archivo analógico
3
@golddove Sí, y no puedes. No deberías. Siempre debe crear o derivar una nueva versión del valor, y su programa debe diseñarse para adoptar esta forma intencionalmente. La función existe para prevenir tal mutación accidental. Nuevamente, este es uno de los conceptos centrales de la programación de estilo funcional .
eonil
25

Una estructura es una agregación de campos; si una instancia de estructura particular es mutable, sus campos serán mutables; si una instancia es inmutable, sus campos serán inmutables. Por tanto, se debe preparar un tipo de estructura para la posibilidad de que los campos de cualquier instancia particular puedan ser mutables o inmutables.

Para que un método de estructura mute los campos de la estructura subyacente, esos campos deben ser mutables. Si un método que muta campos de la estructura subyacente se invoca sobre una estructura inmutable, intentaría mutar campos inmutables. Dado que nada bueno podría salir de eso, tal invocación debe prohibirse.

Para lograr eso, Swift divide los métodos de estructura en dos categorías: aquellos que modifican la estructura subyacente y, por lo tanto, solo pueden invocarse en instancias de estructura mutable, y aquellos que no modifican la estructura subyacente y, por lo tanto, deberían ser invocables tanto en instancias mutables como inmutables. . Este último uso es probablemente el más frecuente y, por tanto, el predeterminado.

En comparación, .NET actualmente (¡todavía!) No ofrece ningún medio para distinguir los métodos de estructura que modifican la estructura de aquellos que no lo hacen. En cambio, invocar un método de estructura en una instancia de estructura inmutable hará que el compilador haga una copia mutable de la instancia de estructura, deje que el método haga lo que quiera con él y descarte la copia cuando el método esté listo. Esto tiene el efecto de obligar al compilador a perder tiempo copiando la estructura, ya sea que el método la modifique o no, aunque agregar la operación de copia casi nunca convertirá lo que sería un código semánticamente incorrecto en un código semánticamente correcto; simplemente hará que el código que es semánticamente incorrecto de una manera (modificando un valor "inmutable") sea incorrecto de otra manera (permitiendo que el código piense que está modificando una estructura, pero descartando los cambios intentados). Permitir que los métodos de estructura indiquen si modificarán la estructura subyacente puede eliminar la necesidad de una operación de copia inútil y también asegura que se marcará el intento de uso erróneo.

Super gato
fuente
1
La comparación con .NET y un ejemplo que no tiene la palabra clave mutante me lo dejó claro. El razonamiento de por qué la palabra clave mutante generaría algún beneficio.
Binarian
19

Precaución: términos legos por delante.

Esta explicación no es rigurosamente correcta en el nivel de código más esencial. Sin embargo, ha sido revisado por un tipo que realmente trabaja en Swift y dijo que es lo suficientemente bueno como explicación básica.

Así que quiero intentar responder de forma simple y directa a la pregunta de "por qué".

Para ser precisos: ¿por qué tenemos que marcar las funciones de estructura mutatingcuando podemos cambiar los parámetros de estructura sin modificar las palabras clave?

Entonces, en general, tiene mucho que ver con la filosofía que mantiene a Swift veloz.

Podría pensar en ello como el problema de administrar direcciones físicas reales. Cuando cambie su dirección, si hay muchas personas que tienen la actual, debe notificarles a todas que se ha mudado. Pero si nadie tiene su dirección actual, simplemente puede mudarse a donde quiera, y nadie necesita saberlo.

En esta situación, Swift es como la oficina de correos. Si muchas personas con muchos contactos se mueven mucho, hay una sobrecarga muy alta. Tiene que pagar una gran cantidad de personas para manejar todas esas notificaciones, y el proceso requiere mucho tiempo y esfuerzo. Es por eso que el estado ideal de Swift es que todos en su ciudad tengan la menor cantidad de contactos posible. Entonces no necesita un gran personal para manejar los cambios de dirección y puede hacer todo lo demás más rápido y mejor.

Esta es también la razón por la que la gente de Swift está entusiasmada con los tipos de valor frente a los tipos de referencia. Por naturaleza, los tipos de referencia acumulan "contactos" por todas partes, y los tipos de valor no suelen necesitar más de un par. Los tipos de valor son "Swift" -er.

Así que de vuelta a la imagen pequeña: structs. Las estructuras son un gran problema en Swift porque pueden hacer la mayoría de las cosas que pueden hacer los objetos, pero son tipos de valor.

Continuemos con la analogía de la dirección física imaginando un misterStructque vive en someObjectVille. La analogía se torna un poco complicada aquí, pero creo que todavía es útil.

Entonces, para modelar el cambio de una variable en a struct, digamos que misterStructtiene cabello verde y recibe una orden para cambiar a cabello azul. La analogía se torna torpe, como dije, pero más o menos lo que sucede es que en lugar de cambiarse misterStructel cabello, la persona mayor se muda y entra una nueva persona con cabello azul , y esa nueva persona comienza a llamarse a sí misma misterStruct. Nadie necesita recibir una notificación de cambio de dirección, pero si alguien mira esa dirección, verá a un tipo con el pelo azul.

Ahora modelemos lo que sucede cuando llamas a una función en a struct. En este caso, es como misterStructrecibir un pedido como changeYourHairBlue(). Entonces, la oficina de correos da la instrucción de misterStruct"ir a cambiarte el cabello a azul y decirme cuando hayas terminado".

Si sigue la misma rutina que antes, si está haciendo lo que hizo cuando se cambió la variable directamente, lo misterStructque hará es mudarse de su propia casa y llamar a una nueva persona con cabello azul. Pero ese es el problema.

La orden era "ve a cambiarte el pelo a azul y dime cuando termines", pero es el chico verde quien recibió esa orden. Después de que el chico azul se mude, aún debe enviarse una notificación de "trabajo completado". Pero el chico azul no sabe nada al respecto.

[Para realmente pensar en esta analogía algo horrible, lo que técnicamente le sucedió al chico de cabello verde fue que después de mudarse, inmediatamente se suicidó. ¡Así que tampoco puede notificar a nadie que la tarea está completa ! ]

Para evitar este problema, en casos como éste solamente , Swift tiene que ir directamente a la casa en esa dirección y en realidad cambiar el pelo de la fábrica actual . Ese es un proceso completamente diferente a simplemente enviar a un chico nuevo.

¡Y es por eso que Swift quiere que usemos la mutatingpalabra clave!

El resultado final se ve igual para cualquier cosa que tenga que referirse a la estructura: el habitante de la casa ahora tiene el pelo azul. Pero los procesos para lograrlo son en realidad completamente diferentes. Parece que está haciendo lo mismo, pero está haciendo algo muy diferente. Está haciendo algo que las estructuras Swift en general nunca hacen.

Entonces, para darle un poco de ayuda al compilador pobre, y no hacer que tenga que averiguar si una función muta structo no, por sí sola, para cada función de estructura, se nos pide que tengamos lástima y usemos la mutatingpalabra clave.

En esencia, para ayudar a Swift a mantenerse veloz, todos debemos hacer nuestra parte. :)

EDITAR:

Hola amigo / dudette que me votó negativamente, simplemente reescribí completamente mi respuesta. Si le sienta mejor, ¿eliminará el voto negativo?

Le Mot Juiced
fuente
1
¡Esto es tan <f-word-here> increíblemente escrito! Muy fácil de entender. He buscado en Internet esto y aquí está la respuesta (bueno, sin tener que revisar el código fuente de todos modos). Muchas gracias por tomarse el tiempo de escribirlo.
Vaibhav
1
Solo quiero confirmar que lo entiendo correctamente: ¿Podemos decir que, cuando una propiedad de una instancia de Struct se cambia directamente en Swift, esa instancia de Struct se "termina" y se "crea" una nueva instancia con la propiedad modificada? ¿entre bastidores? Y cuando usamos un método de mutación dentro de la instancia para cambiar la propiedad de esa instancia, ¿este proceso de terminación y reinicialización no tiene lugar?
Ted
@Teo, me gustaría poder responder definitivamente, pero lo mejor que puedo decir es que pasé esta analogía por un desarrollador Swift real, y dijeron "eso es básicamente exacto", lo que implica que hay más en un sentido técnico. Pero con ese entendimiento, que hay más que esto, en un sentido amplio, sí, eso es lo que dice esta analogía.
Le Mot Juiced
8

Las estructuras rápidas se pueden instanciar como constantes (vía let) o variables (vía var)

Considere la Arrayestructura de Swift (sí, es una estructura).

var petNames: [String] = ["Ruff", "Garfield", "Nemo"]
petNames.append("Harvey") // ["Ruff", "Garfield", "Nemo", "Harvey"]

let planetNames: [String] = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
planetNames.append("Pluto") //Error, sorry Pluto. No can do

¿Por qué el anexo no funcionó con los nombres de los planetas? Porque agregar está marcado con la mutatingpalabra clave. Y como planetNamesse declaró using let, todos los métodos así marcados están fuera de los límites.

En su ejemplo, el compilador puede decirle que está modificando la estructura asignando una o más de sus propiedades fuera de un init. Si cambia un poco su código, lo verá xy yno siempre es accesible fuera de la estructura. Observe el leten la primera línea.

let p = Point(x: 1, y: 2)
p.x = 3 //error
p.moveToX(5, andY: 5) //error
lanza
fuente
5

Considere una analogía con C ++. Un método de estructura en Swift siendo mutating/ not- mutatinges análogo a un método en C ++ que no es const/ const. Un método marcado consten C ++ de manera similar no puede mutar la estructura.

Puede cambiar una var desde fuera de una estructura, pero no puede cambiarla desde sus propios métodos.

En C ++, también puede "cambiar una var desde fuera de la estructura", pero solo si tiene una constvariable no estructura. Si tiene una constvariable de estructura, no puede asignar a una var, y tampoco puede llamar a un constmétodo que no sea . De manera similar, en Swift, puede cambiar una propiedad de una estructura solo si la variable de estructura no es una constante. Si tiene una constante de estructura, no puede asignar a una propiedad y tampoco puede llamar a un mutatingmétodo.

newacct
fuente
1

Me pregunté lo mismo cuando comencé a aprender Swift, y cada una de estas respuestas, aunque quizás agregue algunas ideas, son un poco prolijas y confusas por derecho propio. Creo que la respuesta a tu pregunta es bastante simple ...

El método de mutación definido dentro de su estructura quiere permiso para modificar todas y cada una de las instancias de sí mismo que se crearán en el futuro. ¿Qué pasa si una de esas instancias se asigna a una constante inmutable con let? UH oh. Para protegerte de ti mismo (y para que el editor y el compilador sepan lo que estás tratando de hacer), estás obligado a ser explícito cuando quieras darle a un método de instancia este tipo de poder.

Por el contrario, la configuración de una propiedad desde fuera de su estructura está operando en una instancia conocida de esa estructura. Si está asignado a una constante, Xcode te lo hará saber en el momento en que hayas escrito la llamada al método.

Esta es una de las cosas que me encanta de Swift a medida que empiezo a usarlo más: ser alertado de errores a medida que los escribo. ¡Seguro que es mejor que la solución de problemas de errores oscuros de JavaScript!

Kal
fuente
1

SWIFT: uso de función de mutación en estructuras

Los programadores de Swift desarrollaron Structs de tal manera que sus propiedades no se pueden modificar dentro de los métodos de Struct. Por ejemplo, verifique el código que se proporciona a continuación

struct City
{
  var population : Int 
  func changePopulation(newpopulation : Int)
  {
      population = newpopulation //error: cannot modify property "popultion"
  }
}
  var mycity = City(population : 1000)
  mycity.changePopulation(newpopulation : 2000)

Al ejecutar el código anterior obtenemos un error porque estamos tratando de asignar un nuevo valor a la población de propiedades de Struct City. Por defecto, las propiedades de Structs no se pueden modificar dentro de sus propios métodos. Así es como lo han construido los desarrolladores de Apple, de modo que los Structs tendrán una naturaleza estática por defecto.

¿Como lo resolvemos? Cual es la alternativa?

Palabra clave mutante:

Declarar la función como mutante dentro de Struct nos permite alterar propiedades en Structs. Línea No: 5, del código anterior cambia a esto,

mutating changePopulation(newpopulation : Int)

Ahora podemos asignar el valor de la nueva población a la población de la propiedad dentro del alcance del método.

Nota :

let mycity = City(1000)     
mycity.changePopulation(newpopulation : 2000)   //error: cannot modify property "popultion"

Si usamos let en lugar de var para los objetos Struct, entonces no podemos mutar el valor de ninguna propiedad. Además, esta es la razón por la que obtenemos un error cuando intentamos invocar la función mutante usando la instancia let. Por lo tanto, es mejor usar var siempre que cambie el valor de una propiedad.

Me encantaría escuchar sus comentarios y pensamientos… ..

sDev
fuente