2012-03-02 18 views
27

EDITAR: Sigo recibiendo votos aquí. Solo para que conste, ya no creo que esto sea importante. No lo he necesitado desde que lo publiqué.Scala - parámetro de método mutable (var) referencia

me gustaría hacer después de Scala ...

def save(srcPath: String, destPath: String) { 
    if (!destPath.endsWith('/')) 
     destPath += '/' 
    // do something 
} 

... pero no puedo por culpa destPath es un val. ¿Hay alguna manera de declarar destPath como var?

Nota: hay preguntas similares pero en todas OP solo quería modificar la matriz.

Por favor, no aconsejan siguiente:

mutación de los parámetros de entrada a menudo es visto como un mal estilo y hace que sea más difícil de razonar sobre el código.

Creo que es válido en la programación imperativa (Scala permite ambos, ¿verdad?) Y agregar algo como tmpDestPath simplemente agregará desorden.

EDITAR: No malinterprete. Sé que las cadenas no son mutables y no quiero una referencia a la referencia porque no quiero modificar los datos de la persona que llama. Solo quiero modificar la referencia local a la cadena que el llamador me dio con mi cadena (por ejemplo, orig + '/'). Quiero modificar ese valor solo en el alcance del método actual. Mira, esto es perfectamente válido en Java:

void printPlusOne(int i) { 
    i++; 
    System.out.println("i is: " + i); 
    System.out.println("and now it's same: " + i); 
} 

que no tenga que crear nueva variable y no tengo para calcular i + 1 dos veces.

+5

Después de la aclaración, la respuesta es: No se puede. – Debilski

+2

Eso es lo que sospechaba. Voy a publicarlo en scala-debate. – woky

+0

Bueno, la comunidad de Scala no va a estar realmente a favor de poder modificar directamente los parámetros de la función, ya sea por valor o por referencia. El razonamiento es el mismo que el de por qué a Scala también le falta algo más de su ejemplo: los operadores unarios '++' para tipos numéricos. Tales cosas apestan a un estilo de programación no funcional orientado a efectos secundarios, que es algo que Scala generalmente lo alienta a evitar. Tal como está, si quieres mutar repetidamente un parámetro de función, primero debes almacenarlo en una 'var', ¡lo que hace que tus intenciones sean más claras, de todos modos! – Destin

Respuesta

9

La JVM no permite el paso de referencia de los punteros a los objetos (que es cómo lo haría en C++), por lo que no puede hacer exactamente lo que quiere.

Una opción es devolver el nuevo valor:

def save(srcPath: String, destPath: String): String = { 
    val newPath = (if (!destPath.endsWith("/")) destPath+'/' else destPath) 
    // do something 
    newPath 
} 

Otra es la de crear un contenedor:.

case class Mut[A](var value: A) {} 

def save(srcPath: String, destPath: Mut[String]) { 
    if (!destPath.value.endsWith("/")) destPath.value += '/' 
    // do something 
} 

cual los usuarios tendrán entonces que utilizar en el modo en (Por supuesto, estarán tentados a save("/here",Mut("/there")) que descartarán las alteraciones, pero este es siempre el caso con los argumentos de la función de paso por referencia).


Editar: lo que está proponiendo es una de las mayores fuentes de confusión entre los programadores no expertos. Es decir, cuando modifica el argumento de una función, ¿está modificando una copia local (pase por valor) o la original (paso por referencia)? Si no puede modificarlo, está bastante claro que cualquier cosa que haga es una copia local.

Solo hazlo de esa manera.

val destWithSlash = destPath + (if (!destPath.endsWith("/")) "/" else "") 

Vale la pena la confusión acerca de lo que realmente está sucediendo.

+1

Hola. Gracias. Me temo que malinterpretaste mi pregunta. Lo he actualizado – woky

+1

"La JVM no permite el paso de referencia de los punteros a los objetos" Este punto requiere una aclaración muy cuidadosa. Al pasar una primitiva en Java: de hecho estamos pasando por valor. Pero, al pasar un objeto: de hecho está pasando una referencia a ese objeto (que podemos afirmar que también se está haciendo: valor por defecto). Por este motivo, la declaración "Java solo pasa por valor" en realidad causa mucha confusión, pero en realidad esa afirmación requiere la comprensión de que las primitivas se pasan por valor y las referencias de objeto * se pasan por valor. –

1

Los objetos de cadena son inmutables en Scala (y Java).Las alternativas que se me ocurren son:

  1. Devuelve la cadena de resultados como valor de retorno.
  2. En lugar de usar un parámetro String, use StringBuffer o StringBuilder, que no son inmutables.

En el segundo escenario, cabe tener algo como:

def save(srcPath: String, destPath: StringBuilder) { 
    if (!destPath.toString().endsWith("/")) 
     destPath.append("/") 
    // do something 
    // 
} 

EDITAR

Si he entendido bien, que desea utilizar el argumento como una variable local. No se puede, porque todos los argumentos del método son val en Scala. El único que hay que hacer es copiar a una variable local en primer lugar:

def save(srcPath: String, destPath: String) { 
    var destP = destPath 
    if (!destP.endsWith("/")) 
     destP += "/" 
    // do something 
    // 
} 
+1

Hola. Gracias. Me temo que malinterpretaste mi pregunta. Lo he actualizado – woky

+0

Anuncio 2. No es una opción, eso es un desorden. StringBuilder implica que voy a construir una gran cadena. ¿Por qué hacer "String -> StringBuilder -> String" si puedo "Cadena (-> tal vez nueva Cadena)"? Su solución es solo una solución a la falta de conocimiento de Scala o a la limitación de Scala. No harías esto en Java, ¿verdad? – woky

+0

No dije que usar un StringBuilder es elegante, pero si no quiere devolver una cadena como un valor de retorno, entonces tiene que usar un argumento de algún tipo de cadena mutable. En realidad, puede usar endsWith() directamente en un StringBuilder en Scala (no necesita llamar a String()). – Giorgio

24

No se puede.

Deberá declarar un var adicional (o utilizar un estilo más funcional :-)).

ejemplo simplista:

def save(srcPath: String, destPath: String) { 
    val normalizedDestPath = 
     if (destPath.endsWith('/')) destPath 
     else destPath + '/' 
    // do something with normalizedDestPath 
} 
+0

Agregué un ejemplo. –

0

Aquí hay un par de sugerencias:

1) Modificar una función un poco

def save(srcPath: String, destPath: String) { 
    var dp = destPath 
    if (!dp.endsWith('/')) 
    dp+= '/' 
    // do something, but with dp instead of destPath 
} 

2) Crear una función de utilidad a utilizar antes de llamar a save

def savedPath(path: String) = 
    if(path.endsWith("/")) 
    path 
    else 
    path + "/" 

//call your save method on some path 
val myDestPath = ... 
val srcPath = ... 
save(srcPath, savedPath(myDestPath)) 
0

No, eso no está permitido en Scala. Otros han descrito algunas soluciones de bajo nivel (todas buenas), pero agregaré una de mayor nivel. Para este tipo de fines de normalización de cadenas, mantengo una extensión de pimped para scala.String con métodos como sufijo, prefijo, removeSuffix y removePrefix. sufijo y prefijo añaden o anteponen una cadena a otra, a menos que el sufijo o prefijo ya esté allí. removeSuffix y removePrefix hacen lo obvio, eliminando una cadena del final o el comienzo de otra, si está presente. Su caso de uso se escribiría

val normalizedPath = destPath.addSuffix("/") 

Si lo hace un montón de operaciones de análisis de datos o archivos, estos métodos son tan práctico que no vas a creer que pudiera existir sin ellos.

+0

¿Has comprobado 'stripPrefix' y' stripSuffix' en 'StringLike'? Parece que ya hacen lo que hacen 'removePrefix' y' removeSuffix'. –

3

Tal vez usted podría conseguir el sistema de tipos para hacer el trabajo para usted, por lo que ni siquiera tiene que preocuparse acerca de cómo agregar una barra cada vez:

class SlashString(s: String) { 
    override val toString = if (s endsWith "/") s else s + "/" 
} 
implicit def toSlashString(s: String) = new SlashString(s) 

Ahora no es necesario ningún código en todo para cambiar la entrada String:

def save(srcPath: String, destPath: SlashString) { 
    printf("saving from %s to %s", srcPath, destPath) 
} 

val src: String = "abc" 
val dst: String = "xyz" 

scala> save(src, dst) 
saving from abc to xyz/ 

es cierto que hay un poco de configuración en la salida, pero esto será menos lo mismo con clases implícitas en la versión 2.10, y se elimina todo el desorden del método, que era por lo que estabas preocupado

0

Sé que esto es una vieja pregunta, pero si sólo desea volver a utilizar el nombre del argumento quizá:

def save(srcPath: String, destPath: String) { 
    ((destPath: String) => { 
    // do something 
    })(if (!destPath.endsWith('/')) destPath + '/' else destPath) 
} 
Cuestiones relacionadas