string - ¿Son las cadenas “mutables” de Swift realmente mutables, o son simplemente como cadenas de Java?
immutability (6)
De cierta manera, "mutable" e "inmutable" solo tienen sentido cuando se habla de tipos de referencia. Si intenta extenderlo a los tipos de valor, entonces todos los tipos de valor pueden considerarse funcionalmente equivalentes a los tipos de referencia "inmutables".
Por ejemplo, considere una var
de tipo Int
. ¿Es esto mutable? Algunos de ustedes podrían decir, claro: pueden cambiar su "valor" visible asignándole ( =
). Sin embargo, se puede decir lo mismo de una NSNumber
de NSNumber
y NSString
: puede cambiar su valor visible asignándoselo. Pero NSNumber
y NSString
se describen como clases inmutables .
Lo que realmente está sucediendo con los tipos de referencia es que al asignarlos, la variable (un puntero) apunta a un nuevo objeto. Ni el viejo ni el nuevo objeto en sí se "cambia", pero como apunta a un objeto diferente, usted "ve" un nuevo valor.
Lo que queremos decir cuando decimos que una clase es "mutable" es que ofrece una API (método o referencia) para cambiar realmente el contenido del objeto. Pero, ¿cómo sabemos que el objeto ha cambiado? (¿Más bien es un nuevo objeto?) Es porque podríamos tener otra referencia al mismo objeto, y los cambios en el objeto a través de una referencia son visibles a través de otra referencia. Pero estas propiedades (que apuntan a diferentes objetos, tienen múltiples punteros al mismo objeto) inherentemente solo se aplican a los tipos de referencia. Los tipos de valor, por definición, no pueden tener ese "intercambio" (a menos que parte del "valor" sea un tipo de referencia, como en Array
), y por lo tanto, la consecuencia de la "mutabilidad" no puede ocurrir para los tipos de valor.
Entonces, si creas una clase inmutable que envuelve un entero, sería operativamente equivalente a un Int
. En ambos casos, la única manera de cambiar el valor de una variable sería asignarle ( =
). De modo que Int
también debe considerarse igualmente "inmutable".
Los tipos de valor en Swift son un poco más complejos, ya que pueden tener métodos, algunos de los cuales pueden ser mutating
. Entonces, si puede llamar a un método de mutating
en un tipo de valor, ¿es mutable? Sin embargo, podemos superar esto si consideramos llamar a un método de mutating
en un tipo de valor como azúcar sintáctico para asignarle un valor completamente nuevo (cualquiera sea el método al que se lo mute).
En The Swift Programming Language , en la sección sobre Cadenas, subsección Cadena de mutabilidad , dice esto:
Indicas si una
String
particular se puede modificar (o mutar ) asignándola a una variable (en cuyo caso se puede modificar), oa una constante (en cuyo caso no se puede modificar):
y da código de ejemplo:
var variableString = "Horse"
variableString += " and carriage"
// variableString is now "Horse and carriage"
let constantString = "Highlander"
constantString += " and another Highlander"
// this reports a compile-time error - a constant string cannot be modified”
El libro en iBooks here , o en un navegador web here .
En el siguiente párrafo afirma que "las cadenas son tipos de valor".
Mi pregunta: eso no me parece una cuerda mutable. Se parece a lo que estoy acostumbrado en Java (o C #, Python y otros): objetos de cadena inmutables con enlaces de variables mutables. En otras palabras, había un objeto "Horse" y luego creó un nuevo objeto String "Horse and carriage" y lo estableció en la misma variable. Y dado que no hay manera de distinguir la diferencia entre una referencia a un objeto inmutable y un tipo de valor (¿verdad?), Me pregunto: ¿por qué lo describen de esta manera? ¿Hay alguna diferencia entre estas cadenas Swift y la forma en que está en Java? (O C #, Python, Objective-C / NSString)
En Swift, las estructuras y las enumeraciones son tipos de valor :
De hecho, todos los tipos básicos en Swift (enteros, números de punto flotante, booleanos, cadenas, arrays y diccionarios) son tipos de valor y se implementan como estructuras detrás de escena.
Por lo tanto, una cadena es un tipo de valor que se copia en la asignación y no puede tener varias referencias, pero los datos de los caracteres subyacentes se almacenan en un búfer de copia y escritura que se puede compartir. La referencia de la API para la estructura de String
dice:
Aunque las cadenas en Swift tienen valores semánticos, las cadenas utilizan una estrategia de copia en escritura para almacenar sus datos en un búfer. Este búfer puede ser compartido por diferentes copias de una cadena. Los datos de una cadena solo se copian perezosamente, tras la mutación, cuando más de una instancia de cadena utiliza el mismo búfer. Por lo tanto, la primera en cualquier secuencia de operaciones de mutación puede costar O (n) tiempo y espacio.
Así que, de hecho, var
vs. let
declara una unión mutable versus inmutable a un búfer de caracteres que parece inmutable.
var v1 = "Hi" // mutable
var v2 = v1 // assign/pass by value, i.e. copies the String struct
v1.append("!") // mutate v1; does not mutate v2
[v1, v2] // ["Hi!", "Hi"]
let c1 = v1 // immutable
var v3 = c1 // a mutable copy
// c1.append("!") // compile error: "Cannot use mutating member on immutable value: ''c1'' is a ''let'' constant"
v3 += "gh" // mutates v3, allocating a new character buffer if needed
v3.append("?") // mutates v3, allocating a new character buffer if needed
[c1, v3] // ["Hi", "High?"]
Esto es como variables de cadena de Java no final vs. final
con dos arrugas.
- Con un enlace mutable puede llamar métodos de mutación. Dado que no puede haber ningún alias para un tipo de valor, no puede saber si un método de mutación modifica realmente el búfer de caracteres o se reasigna a la variable String, excepto por el impacto en el rendimiento.
- La implementación puede optimizar las operaciones de mutación mediante la mutación de un búfer de caracteres en su lugar cuando solo lo utiliza una instancia de String.
En realidad, las Cadenas de Swift parecen ser exactamente como NSString
Objective-C (inmutable); Encontré esto en la documentación que vinculaste a
El tipo String de Swift se une perfectamente a la clase NSString de Foundation. Si está trabajando con el marco de Foundation en Cocoa o Cocoa Touch, toda la API de NSString está disponible para solicitar cualquier valor de String que cree, además de las funciones de String que se describen en este capítulo. También puede usar un valor de cadena con cualquier API que requiera una instancia de NSString.
Las cadenas rápidas son valores, no objetos. Cuando cambias un valor, se convierte en un valor diferente. Entonces, en el primer caso, usando var
, simplemente está asignando el nuevo valor a la misma variable. Si bien se garantiza que dejar de tener ningún otro valor después de asignarlo, da un error de compilación.
Entonces, para responder a su pregunta, las cadenas Swift reciben prácticamente el mismo tratamiento que en Java, pero se consideran valores en lugar de objetos.
Para responder a la pregunta original. Las cadenas en Swift no son lo mismo que las cadenas en Java o C # (no sé acerca de Python). Se diferencian de dos maneras.
1) Las cadenas en Java y C # son tipos de referencia. Las cadenas en Swift (y C ++) son tipos de valor.
2) Las cadenas en Java y C # son siempre inmutables, incluso si la referencia no se declara definitiva o de solo lectura. Las cadenas en Swift (y C ++) pueden ser mutables o inmutables dependiendo de cómo se declaren (let vs var en Swift).
Cuando declara final String str = "foo"
en Java, está declarando una referencia inmutable a un objeto String inmutable. String str = "foo"
declara una referencia mutable a un objeto String inmutable.
Cuando declara let str = "foo"
en Swift, está declarando una cadena inmutable. var str = "foo"
declara una cadena mutable.
Primero, estás usando un método inmutable que crea una nueva instancia de valor. mutating
utilizar métodos de mutating
, como la extend
para realizar la operación en la mutación semántica.
Lo que hizo aquí es crear una nueva cadena inmutable y vincularla a un nombre existente.
var variableString = "Horse"
variableString += " and carriage"
Esto está mutando la cadena en el lugar sin ningún enlace de nombre adicional.
var variableString = "Horse"
variableString.extend(" and carriage")
Segundo, el propósito de la separación inmutable / mutable es proporcionar un modelo de programación más fácil y seguro. Puede hacer más suposiciones de forma segura en una estructura de datos inmutable, y puede eliminar muchos casos de dolor de cabeza. Y esto ayuda a la optimización. Sin un tipo inmutable, necesita copiar datos completos cuando lo pasa a una función. De lo contrario, los datos originales pueden ser mutados por la función, y este tipo de efecto es impredecible. Entonces, tales funciones deben ser anotadas como "Esta función no modifica los datos pasados". Con el tipo inmutable, puede asumir que la función no puede modificar los datos, entonces no tiene que copiarlos. Swift lo hace de forma implícita y automática de forma predeterminada.
Sí, en realidad, la diferencia mutable / inmutable no es más que una diferencia en la interfaz en idiomas de nivel superior como Swift. Valor semántico simplemente significa que no admite comparación de identidad. Como se abstraen muchos detalles, la implementación interna puede ser cualquier cosa. El comentario del código Swift aclara que la cadena está usando trucos COW, y luego creo que ambas interfaces inmutables / mutables se asignan en realidad a implementaciones en su mayoría inmutables. Creo que obtendrá el mismo resultado independientemente de la elección de la interfaz. Pero aún así, esto proporciona beneficios del tipo de datos inmutables que mencioné.
Entonces, el ejemplo del código, en realidad hace lo mismo. La única diferencia es que no puede mutar el original que se unió a su nombre en modo inmutable.