¿Por qué la mayoría de los lenguajes de programación prohíben las “desigualdades sandwich”? En matemáticas, es común ver ‘desigualdades en emparedado’ que muestran que una cantidad es mayor que otra pero menor que otra, por ejemplo, a <b <c significa lo mismo que a <b && b <c.

La lógica es realmente bastante simple. La “notación sándwich” sería un caso especial de cómo funcionan los operadores. Los casos especiales son malos . Piense en ello como la navaja de Occam para el diseño de lenguaje de programación.

Es un caso especial porque hace que < no se comporte como un operador normal; de hecho, hace < perder la localidad del significado .

Normalmente, los operadores trabajan de manera directa: a ⊕ b toma el resultado de la expresión a y la expresión b y los combina con el operador para producir otra expresión. Todo lo involucrado es una expresión. Esto hace que la sintaxis del lenguaje de programación sea compositiva : podemos descubrir el significado de una expresión únicamente a partir de los significados de sus sub-expresiones y nada externo.

A su vez, esto simplifica el lenguaje general ya que no necesita un caso especial para cada operador; en cambio, todas son instancias de una sola regla más general. Cuantas menos reglas como esta sean necesarias, más fácil será para un programador tenerlas en mente.

En la mayoría de los idiomas, < sigue esta regla general. Toma dos expresiones y da como resultado una expresión booleana. Entonces podemos tratar estas expresiones booleanas como cualquier otra expresión y combinarlas de la misma manera usando otros operadores que operan en booleanos como && . En (a < b) && (b < c) , todos los operadores todavía siguen la regla que di anteriormente. Lo mismo sucede en expresiones como a + b + c , donde el segundo + combina la expresión a + b con la expresión c . Hasta aquí todo bien.

Pero si agregamos la “sintaxis sandwich”, esta regla a veces no funciona para < ! Ahora, la expresión a < b < c ya no es un resultado directo de sus sub-partes: el doble < le da a todo un nuevo significado global. Realmente, el doble < comienza a actuar como su propio operador separado del normal < porque no podemos considerar los dos operadores < separado.

Esto, a su vez, hace que el lenguaje sea más complejo para obtener ganancias cuestionables. El objetivo de un lenguaje de programación es proporcionar un conjunto básico de componentes ortogonales para la construcción sistemática de programas, no para reproducir servilmente la notación existente, especialmente cuando la notación en sí misma es a menudo peculiar y arbitraria . En general, me encanta la notación matemática, pero nunca la llamaría coherente y creo que gran parte de la inconsistencia se debe en gran medida a su propio perjuicio.

¿Estás dispuesto a intercambiar simplicidad y elegancia por familiaridad? ¡No soy! Este es un gran ejemplo de la diferencia entre simple y fácil que fue tan bien presentado en la charla de Rich Hickey: Simple Made Easy. (Aunque no estoy de acuerdo con algunos de los detalles de los que habla).

Esta es la misma razón por la que generalmente estoy contento de que los lenguajes de programación no admitan la multiplicación por yuxtaposición y requieran un operador explícito. (¡Bueno, eso y nombres de variables de varios caracteres!)

Esta notación también es confusa en Python porque True < False es una expresión perfectamente significativa, al igual que 1 + 2 . Pasar de 1 + 2 a 1 + 2 + 3 es sencillo; pasando de True < False a True < False < True no lo es! Compare True < False < True con (True < False) < True .

Esto también debería decirte que prohibir es la palabra incorrecta . Los lenguajes de programación no “prohíben” esto porque no surge en primer lugar. Más bien, simplemente no se desviven para apoyarlo explícitamente.

Aquí hay un ejemplo rápido de por qué podría ser una idea mala y / o confusa usar una notación como esta. Considera las dos expresiones

a

y

a

Esto es lo que esperaría que haga el programa para la primera expresión a

  1. Determina a.
  2. Determinar b.
  3. Compara a
  4. Determinar c.
  5. Compare b

(El orden de los pasos 3 y 4 es completamente discutible y una fuente de confusión potencial)

Por otro lado, esto es lo que sucede cuando calculamos a

  1. Determina a.
  2. Determinar b.
  3. Compara a
  4. Determinar b.
  5. Determinar c.
  6. Compare b

Tenga en cuenta que el paso “Determinar b”. se repite en la evaluación de a

Por ejemplo, supongamos

timesCalled ()

es una función que devuelve el número de veces que se ha llamado a esta función (devuelve 1 la primera vez que se llama, 2 la segunda, etc.). Entonces esperaría que

a

devuelve VERDADERO si y solo si a <1 y 1

a

devolverá VERDADERO si y solo si a <1 y 2

a

siempre debe devolver 2, mientras que llama a timesCalled () nuevamente después de la expresión

a

devolverá 3 si a <1 y 2 de lo contrario.


Esperemos que esto lo convenza de que no es una buena idea simplemente implementar una macro que reemplace todas las instancias de a

Sería posible implementar desigualdades tipo sándwich en lenguajes de programación, pero la implementación más natural de este tipo haría a diferente de a

No se me ocurre ninguna buena razón. Sería fácil y útil incluir tales expresiones.

Es más fácil para los humanos entender a < b < c, pero más difícil de entender a < b && b < c . Esa es razón suficiente para incluirlo.

Tenga en cuenta que C y Java ya tienen la construcción “?:” Que toma tres argumentos. Si el primer argumento es verdadero, entonces use el segundo argumento, de lo contrario use el tercero, como en esta línea que calcula | xy |

(x

Los operadores ternarios no son el fin del mundo.

“Cuando uso una palabra”, dijo Humpty Dumpty, en un tono más bien despectivo, “significa exactamente lo que elijo que signifique, ni más ni menos”.

‘La pregunta es’, dijo Alice, ‘si puedes hacer que las palabras signifiquen tantas cosas diferentes’.

“La pregunta es”, dijo Humpty Dumpty, “que es ser maestro, eso es todo”.

A través del espejo y lo que Alice encontró allí
Lewis Carroll (Charles Lutwidge Dodgson), 1871

Ninguna de las respuestas anteriores dio en el blanco, en mi humilde opinión. Construcciones como esta están excluidas de la mayoría de los lenguajes de programación modernos porque el diseño de los lenguajes de programación modernos evolucionó de un mundo en el que el análisis era difícil. Si la tarea real de analizar un programa de computadora para compilarlo es una cantidad de trabajo no trivial, y el tiempo para hacerlo es un costo significativo, entonces tiene sentido diseñar lenguajes para facilitar el análisis. Y muchas ideas del diseño moderno del lenguaje de programación todavía se heredan de ese mundo, aunque el análisis ya no es difícil.

  a 

Cuál es depende de las reglas de análisis. Pero con las desigualdades tipo sándwich, has definido una tercera forma, que ignora la definición de

Para lo que vale, es posible definir operadores sandwich de una manera consistente expandiendo 4 veces el conjunto de operadores relacionales (suponiendo que su lenguaje lo permita). Aquí está en Haskell: Página en pastebin.com. Lo hice de una manera bastante detallada, y mirando hacia atrás, podría haber usado Quizás en lugar de ACompResult, pero debería ser claro.

Voy a ser un contrario aquí. Creo que es porque la mayoría de los idiomas ya tienen tanta sintaxis que están a punto de explotar. Es casi una emergencia, todo el tiempo, mantener todos los casos de esquina correctos y razonablemente predecibles para los usuarios normales no sobrehumanos.

La gente ha dado la versión Python de signos

Lisp (¡Chicos! ¡Déjenme terminar!) Ha encadenado las desigualdades del wazzoo:

  [1]> (<1 2)
 T
 [2]> (<1 2 3 4 5 6 7 8 9 10)
 T
 [3]> (<1 2 3 4 5 6 0)
 NULO 

En Lisp siempre está claro qué operador se aplica a qué operandos. Eso se reduce en gran medida donde hay que mirar para descubrir qué debería estar sucediendo. (Pero parece divertido al principio, sin discusión).

De todos modos, obviamente (< 1 2 3 4 5 6 7) es mejor que

(and (< 1 2) (< 2 3) (< 3 4) (< 4 5) (<5 6) (< 6 7))

Para mí incluso eso es mejor que

(1 < 2) && (2 < 3) && (3 < 4) && (4 < 5) && (5 < 6) && (6 < 7)

Pero aparentemente la mayoría de los programadores no están de acuerdo.

Rápido, ¿qué es 1/2/3?

Infix es una pesadilla de la que muy pocas personas intentan despertarse. Pero, oye, soy un buen chico, principalmente uso C #, Python y Matlab.

Estoy tratando de responder esta pregunta desde una perspectiva histórica, ya que creo que ese es el espíritu de la pregunta.

La razón por la que no se incluyeron las desigualdades tipo sándwich es que los lenguajes de programación están diseñados para mantener la sintaxis SIMPLE.

Esto realmente no tiene nada que ver con los lenguajes de programación per se, sino más con Algebra.

Cuando veas 2 + 3 * 6, sabes multiplicar primero por orden de operaciones.
Otra regla simple que nos dice qué significa nuestra sintaxis es que leemos de izquierda a derecha.
2 + 3 + 5 se convierte en 5 + 5, porque hacemos cosas de izquierda a derecha.
Otra regla simplificadora que encontramos en matemáticas es que los operadores en matemáticas siempre toman 2 o menos argumentos. Entonces, cuando escribe “2 ¡aquí el lenguaje revela que dos operaciones están sucediendo!

Entonces, la respuesta es que la mayoría de los lenguajes de programación no permiten las desigualdades en sándwich porque puede causar confusión y errores , y elegimos no permitir que evite la confusión. Mantener las reglas del lenguaje de programación SIMPLE es bueno para todos: facilita la creación de compiladores. Significa que nunca habrá confusión sobre lo que se supone que debe hacer el código.

Un problema clave de ambigüedad sería este:
1) a Entonces, tiene VERDADERO 2) a

Ahora, el problema es que para evitar CUALQUIER CONFUSIÓN, el signo

Esto provoca la posibilidad de que los compiladores cometan errores y creen errores raros. Entonces, como regla general, evitamos eso. También en días hace mucho tiempo habría tomado mucho tiempo en la computadora para descubrir la intención del autor.

Si bien estoy ampliamente de acuerdo con la respuesta de Tikhon Jelvis, también mencionaría:

  • Utilidad E1 < E2 < E3 no es lo mismo que (E1 < E2) && (E2 < E3) ya que este último evalúa E2 dos veces. Más bien, es una forma abreviada conveniente para let e2 = E2 in (E1 < e2) && (e2 < E3) .
  • Ambigüedad A diferencia de E1 + E2 + E3 , E1 < E2 < E3 no tiene un significado inequívoco en términos de binario <, ya que
  • Casos especiales Los casos especiales son realmente malos, aunque en teoría no hay ninguna razón por la cual los operadores infix n-ary no puedan ser definidos por el usuario: serían no asociativos y en su lugar tomarían arbitrariamente muchos argumentos a través de la repetición de operadores no sintetizados como en a
  • Alternativas Por supuesto, ya existe una forma de definir operadores n-arios en la mayoría de los idiomas: funciones normales. Lisp tiene (

Hm, esa es una buena pregunta. Compartiré mis pensamientos.

Como señala Tikhon, la notación matemática es a menudo inconsistente y / o arbitraria. Dicho esto, ciertamente podemos definir la sintaxis de “desigualdad en sandwich” de una manera consistente. Además, a < b < c es más fácil de leer y escribir que (a < b) and (b < c) , que es una construcción común, y nos es familiar. Escribir a < b < c cuando quieres comparar resultados booleanos es bastante raro, y creo que si alguien quisiera hacer eso, de todos modos lo pondrían entre paréntesis. (De hecho, en Haskell, debe poner entre paréntesis a < b < c , porque < se define como asociativo de izquierda o derecha).

Así que creo que esta es definitivamente una característica que es agradable tener, por lo que en realidad es solo una cuestión de si encaja .

En Python (el único lenguaje que conozco para admitir el operador sándwich), todos los operadores son casos especiales de todos modos y Python enfatiza tener una sintaxis legible, incluso pseudocodey. Así que creo que un operador de caso especial más está bien.

Creo que también encajaría en C ++, porque a C ++ le gusta tener todas las funciones. (Siendo un poco gracioso aquí.)

Haskell es interesante, porque le permite definir operadores de infijo binarios arbitrarios, incluso le permite especificar la precedencia y la asociatividad. Los operadores de comparación son solo ejemplos de eso. Sin embargo, no hay soporte de primera clase para los operadores de mayor arity: cualquier "operador ternario" (como if-then-else) son casos especiales. Así que creo que sería muy incómodo extender < para ser operadores n-arios. Supongo que Haskell podría agregar soporte para operadores n-ary en el futuro, aunque no tengo idea de cómo funcionaría.

Sin embargo, lo interesante de Haskell es que aún puedes definir tus propios operadores de sándwich a partir de operadores binarios. Aquí hay un ejemplo que preparé, que almacena resultados intermedios en un Maybe , aunque requiere tres operadores diferentes, por lo que el resultado no parece tan limpio.

 (!<) :: Ord a => a -> a -> Maybe a infixr 4 !< x !< y | x < y = Just x | otherwise = Nothing infixr 4 !!< (!!<) :: Ord a => a -> Maybe a -> Maybe a x !!< Nothing = Nothing x !!< (Just y) = x !< y infixr 4 !!!< (!!!<) :: Ord a => a -> Maybe a -> Bool x !!!< Nothing = False x !!!< (Just y) = x < y -- Meant to look like 1 < 2 < 3 < 4 < 5 < 6 example = 1 !!!< 2 !!< 3 !!< 4 !!< 5 !< 6 

… porque la cultura del mundo del software es (todavía) muy conservadora. Todos estaríamos programando en Pascal si muchas personas se salieran con la suya.

En cuanto al argumento de que “a

Sin embargo, debo confesar que había estado usando Python durante unos cinco años antes de descubrir que uno podía escribir, por ejemplo …

si 13 <= edad <20:
imprimir “aha! eres un adolescente”

… en lugar de escribir el engorroso:

si edad> = 13 y edad <20:

… e incluso entonces preguntándome si necesito agregar corchetes para (a) orden de precedencia y / o (b) legibilidad.

El rango de respuestas a esta pregunta es esclarecedor por decepcionante. Algunos se basan en un análisis reflexivo, pero otros demuestran que la mentabilidad “cuatro patas buenas, dos patas malas” de ‘Animal Farm’ de Orwell está viva y bien y activa en el campo del desarrollo de software.

En un lenguaje con evaluación dirigida por objetivos (como Icon), estas desigualdades funcionan correctamente porque cada expresión tiene efectivamente dos “valores”: el valor de retorno y si la expresión tuvo éxito o no. Entonces, ‘a

a

Debido a que tergiversa lo que realmente está haciendo la computadora (1), no es particularmente más práctico (2), y puede hacer que la sintaxis sea más confusa (3).
Con más detalle:

  1. La máquina real no puede comparar más de un valor a la vez. Para preformar una desigualdad en sándwich, el procesador realiza los mismos 3 pasos. Primero compara a y b. Entonces byc. Y finalmente compara las salidas booleanas de la primera a las operaciones. Si escribe a
  2. Escribir (a
  3. Cuantas menos reglas de sintaxis tenga un lenguaje, más limpio será. A menos que una regla de sintaxis simplifique masivamente algo que debería ser simple pero no lo es o es necesario para diferenciar dos formas distintas de realizar una operación, generalmente no se incluirá en el lenguaje.

No repetiré lo que todos explicaron, pero es posible en Python extraer la expresión tan larga como quieras. solo ten en cuenta que no es recomendable

  >>> 2 <3 <4 <6 <9
 Cierto
 >>> 2 <3> 1
 Cierto
 >>> 1 <2 <3> -1> -2
 Cierto
 >>> 1 <2 <3> (-1> -2)> -10
 Cierto
 >>> 1 <2 <3> (-1> -2)> -10> (-2 * 10) <12
 Cierto

No hay otra razón que la tradición, derivada de mantener la vida de los escritores de compiladores e intérpretes más simples.

Hay muchos beneficios para la notación a

Un compilador o intérprete puede comprender fácilmente a = b (que && lo hace en muchos idiomas de todos modos como alternativa).

Python y Mathematica implementan a

No hay una buena razón, aunque las malas decisiones en el diseño básico de un idioma dado podrían hacerlo un poco difícil o moderadamente oscuro. En LISP, por ejemplo, uno simplemente puede escribir (

No hay una razón técnica, porque los compiladores o intérpretes siempre pueden convertir (a < b < c) a (a < b) && (b < c) . Y una buena noticia es que han comenzado a proporcionar esta característica de azúcar sintáctica, desde Python.

El OP sugiere que
“a

¿Pero es esto realmente lo que esperarías?

Cuando leo ” a espero que se evalúe b dos veces * (y si fuera una función, podría devolver valores diferentes cada vez).

¿Esperaría que b sea evaluado dos veces en ” a ? Debo decir que solo esperaría una evaluación de b, que sería inconsistente: b debe evaluarse dos veces * para que la declaración original del OP sea correcta, lo que sorprendería a muchos autores y revisores y sería una excelente fuente de errores.

Alternativamente, si b solo se evalúa una vez, entonces el OP no puede ser correcto, y hay circunstancias en las que a

* o solo una vez si a> = b, para el significado habitual de &&

Nada le impide escribir desigualdades de emparedado, como a c. Lo que sucede es que el lenguaje de programación tiene que entenderlos. Por lo general, la relación binaria implícita es la función lógica AND.

C, por ejemplo, admite la asignación en línea, por ejemplo, a = (b = 5) -c, donde b se asigna a 5. Sin embargo, algunos lenguajes de programación suponen que b = 5 es una función binaria (es b igual a 5, en lugar de; establecer b = 5 )

porque no tienen la sintaxis mixfix. No hay buena razón.

en Agda, puedes definirlo fácilmente:

  _ <_ <_: Nat -> Nat -> Nat -> Bool
 a 

es solo una función mixfix (en particular, un operador ternario).

Creo que es sobre todo tradición.

Python tiene desigualdades de sándwich sin problemas ni problemas y no puedo ver problemas. Simplemente escriba “if 10

El único problema es en las divertidas condiciones donde las cantidades son de diferentes tipos. ¿Qué se convierte en qué? Por ejemplo, si escribe: “if 10.333