Cuándo usar paréntesis en la notación infija Scala

Cuando programo en Scala, hago más y más cosas funcionales. Sin embargo, cuando se utiliza la notación infija, es difícil saber cuándo se necesitan paréntesis y cuándo no.

Por ejemplo, el siguiente fragmento de código:

def caesar(k:Int)(c:Char) = c match { case c if c isLower => ('a'+((c-'a'+k)%26)).toChar case c if c isUpper => ('A'+((c-'A'+k)%26)).toChar case _ => c } def encrypt(file:String,k:Int) = (fromFile(file) mkString) map caesar(k)_ 

The (fromFile (file) mkString) necesita paréntesis para poder comstackr. Cuando lo elimine, aparece el siguiente error:

 Caesar.scala:24: error: not found: value map def encrypt(file:String,k:Int) = fromFile(file) mkString map caesar(k)_ ^ one error found 

mkString obviamente devuelve una cadena en la que (mediante la conversión implícita AFAIK) puedo usar la función de mapa.

¿Por qué este caso particular necesita paréntesis? ¿Existe una guía general sobre cuándo y por qué la necesita?

Esto es lo que armé para mí después de leer la especificación:

  • Cualquier método que tome un solo parámetro se puede usar como un operador infijo: am(b) se puede escribir amb .
  • Cualquier método que no requiera un parámetro se puede utilizar como operador de postfijo: se puede escribir am .

Por ejemplo, a.##(b) se puede escribir a ## b y a.! se puede escribir a!

  • Los operadores de Postfix tienen menor prioridad que los operadores de infijo , por lo que foo bar baz significa foo.bar(baz) mientras que foo bar baz bam significa (foo.bar(baz)).bam y foo bar baz bam bim means (foo.bar(baz)).bam(bim) .
  • Además, dado un método sin parámetros m del objeto a , amm es válido, pero amm no es como se analizaría como exp1 op exp2 .

Debido a que hay una versión de mkString que toma un solo parámetro, se verá como un operario infijo en el fromFile(file) mkString map caesar(k)_ . También hay una versión de mkString que no toma ningún parámetro que pueda usarse como operador de postfijo:

 scala> List(1,2) mkString res1: String = 12 scala> List(1,2) mkString "a" res2: String = 1a2 

En algún momento al agregar un punto en la ubicación correcta, puede obtener la precedencia que necesita, por ejemplo, fromFile(file).mkString map { }

Y todo eso de precedencia ocurre antes de tipear y otras fases, por lo tanto, aunque la función de list mkString map function no tenga sentido como list.mkString(map).function , así es como se analizará.

Las menciones de referencia de Scala (6.12.3: Prefijo, In fi xio y Operaciones de posfijo)

En una secuencia de tipo consecutiva en operaciones fi x t0 op1 t1 op2 . . .opn tn t0 op1 t1 op2 . . .opn tn t0 op1 t1 op2 . . .opn tn , todos los operadores op1, . . . , opn op1, . . . , opn op1, . . . , opn debe tener la misma asociatividad.
Si todos son asociativos de izquierda, la secuencia se interpreta como (. . . (t0 op1 t1) op2 . . .) opn tn .

En su caso, ‘ map ‘ no es un término para el operador ‘ mkstring ‘, por lo que necesita agruparlo (con el paréntesis alrededor de ‘ fromFile(file) mkString ‘)


En realidad, Matt R comenta:

En realidad, no es un problema de asociatividad, más que “los operadores de Post fi x siempre tienen menor prioridad que en los operadores de fi x . Por ejemplo, e1 op1 e2 op2 siempre es equivalente a (e1 op1 e2) op2 “. (También desde 6.12.3)

La respuesta de huynhjl (upvoted) da más detalles, y la respuesta de Mark Bush (también votada arriba) apunta a ” Un tour de Scala: Operadores ” para ilustrar que “Cualquier método que tome un solo parámetro se puede usar como un operador infijo” .

Aquí hay una regla simple: nunca usé operadores de postfijo. Si lo hace, ponga la expresión completa terminando con el operador postfix entre paréntesis.

De hecho, comenzando con Scala 2.10.0, hacer eso generará una advertencia por defecto.

Para una buena medida, es posible que desee mover el operador de postfijo y usar notación de puntos para ello. Por ejemplo:

 (fromFile(file)).mkString map caesar(k)_ 

O, incluso más simple,

 fromFile(file).mkString map caesar(k)_ 

Por otro lado, preste atención a los métodos donde puede proporcionar un paréntesis vacío para convertirlos en infijo:

 fromFile(file) mkString () map caesar(k)_ 

La especificación no lo deja claro, pero mi experiencia y experimentación han demostrado que el comstackdor de Scala siempre intentará tratar las llamadas a métodos utilizando la notación infija como operadores de infijo. Aunque el uso de mkString es postfix, el comstackdor intenta interpretarlo como infijo, por lo que intenta interpretar “mapa” como su argumento. Todos los usos de los operadores de posfijo deben ser inmediatamente seguidos por un terminador de expresión o ser utilizados con notación de “punto” para que el comstackdor lo vea como tal.

Puede obtener una pista de esto (aunque no está explicado) en Un recorrido por Scala: Operadores .