El libro

Apéndice C · Tabla de operadores y precedencia

Todos los operadores del lenguaje, con su precedencia y asociatividad. Es referencia rápida: cuando dudas cómo parsea a | f |> g, lo consultas y sigues.

La precedencia está numerada de más alta (1, aprieta primero) a más baja (8, aprieta último). Cada nivel se resuelve contra el nivel inmediatamente superior.

C.1 Tabla principal

NivelOperadoresAsociatividad
1llamada f(args), campo ., índice [i]Postfija
2unario -, notPrefija
3*, /, //, %Izquierda
4+, - (binarios), ++ (concat de strings)Izquierda
5?? (default de Option)Derecha
6==, !=, <, >, <=, >=No-asociativa
7andIzquierda (corto)
8orIzquierda (corto)
9|>, |, ||, |?, |!Izquierda

Detalles por nivel

  • Nivel 1 (postfijos). Se asocian por izquierda: a.b.c significa (a.b).c; f(x)(y) significa (f(x))(y).
  • Nivel 2 (prefijos). Unario -x y not x. Aplican antes que cualquier operador binario.
  • Nivel 3 (multiplicativos). * y / sobre Int y Real, // es división entera, % es resto. Asociación izquierda: a / b / c es (a / b) / c.
  • Nivel 4 (aditivos). + y - binarios sobre números, ++ para concatenar strings.
  • Nivel 5 (??). El default de un Option: o ?? d es el valor que trae o cuando es Some, y d cuando es None. Asocia a derecha, así que a ?? b ?? c es a ?? (b ?? c), y el lado derecho corre solo en el caso None.
  • Nivel 6 (comparaciones). No se encadenan. a < b < c es error de sintaxis; se escribe a < b and b < c. Así desaparece la clase de bugs donde 1 == 1 == true parsea inesperadamente.
  • Nivel 7 (and) y 8 (or). Lógicos con corto-circuito. and aprieta más fuerte que or, así que a or b and c es a or (b and c).
  • Nivel 9 (pipes). Cinco operadores al mismo nivel: |> (apply), | (map), || (flat-map), |? (filter) y |! (each). Todos asocian a izquierda: xs | f |> g es (xs | f) |> g. |! devuelve Unit, así que cierra el pipeline y ningún pipe puede seguirlo.

C.2 Operadores que no son operadores

Algunas formas que en otros lenguajes parecen operadores son, en kaikai, construcciones de sintaxis aparte:

  • = (asignación de let): parte de la sintaxis de let, no es un operador. let x = expr es una declaración.
  • := (escritura a celda mutable): parte de la sintaxis de var. nombre := expr es la forma azucarada de State.set(nombre, expr). No participa en la tabla de precedencia.
  • &expr y @r (celdas Ref[T]): &v arma una celda con v adentro y @r la lee. Son prefijos, no participan en la tabla. La celda de un var es otra cosa: se lee pelada, por su nombre.
  • ; (separador de expresiones): separa dos expresiones escritas en la misma línea. El salto de línea hace lo mismo, así que casi nunca lo necesitas; al final de una línea es inofensivo y no descarta el valor de la expresión (§3.7).
  • -> (en match, en lambdas, en handle): separador sintáctico, no operador.
  • / (en firma de efecto): separa el tipo de retorno de la fila de efectos: fn f() : Int / Log. Solo aparece en posición de firma.
  • ! (propagación de Option/Result): postfijo sobre expresiones que devuelven Option[T] o Result[E, T], hace propagación temprana del error o el None. Equivale más o menos al ? de Rust. No es un operador binario.
  • ? y ?nombre (holes): marcadores de agujero, no operadores.
  • ... (spread): aparece en literales de record y de lista ([h, ...t], Punto { ...p, x: 10 }); también en patrones. No es operador.
  • <> (anotación de unidad de medida): 1.50<USD>, Real<EUR>. La unidad va entre los paréntesis angulares; no son los operadores < y >.

C.3 Equivalencias útiles

Algunos operadores son azúcar para llamadas a funciones del stdlib. Las equivalencias te ayudan cuando el operador no parece funcionar o cuando quieres ver el mecanismo de abajo.

OperadorEquivale a
`x> f`
`x> f(_, b)`
`xsf`
`xs
`xs? p`
`xs! f`
a ++ bstring_concat(a, b) (cuando son String)
a[i]Mutable.array_get(a, i)
a[i] := vMutable.array_set(a, i, v)
&v / @rcelda Ref[T]: armar y leer
nombre := vState.set(nombre, v) (con var)

C.4 Recordatorio: comparación no asociativa

La regla del nivel 5 merece énfasis aparte: es la que más sorprende si vienes de otros lenguajes.

En la mayoría de los lenguajes:

1 == 1 == 1     # Python: false (1 == 1 → true, true == 1 → false)
1 < 2 < 3       # C: 1 (1 < 2 → 1, 1 < 3 → 1; no es lo que parece)

En kaikai, ninguna de las dos compila. El compilador exige que escribas la intención explícitamente:

1 == 1 and 1 == 1       # claramente: dos comparaciones
1 < 2 and 2 < 3         # rango: cada lado se compara explícito

Así desaparece una clase pequeña pero molesta de bugs. Si vienes de Python y te molesta escribir más, la regla es la misma de Pascal, Eiffel y SQL. No es una rareza de kaikai.

← Volver al índice