Si quiero comparar dos números (u otras entidades bien ordenadas), lo haría con x < y
. Si quiero comparar tres de ellos, el estudiante de álgebra de secundaria me sugerirá intentarlo x < y < z
. El programador en mí responderá con "no, eso no es válido, tienes que hacerlo x < y && y < z
".
La mayoría de los lenguajes que he encontrado no parecen admitir esta sintaxis, lo cual es extraño dado lo común que es en matemáticas. Python es una notable excepción. JavaScript parece una excepción, pero en realidad es solo un desafortunado subproducto de la precedencia del operador y las conversiones implícitas; en node.js, 1 < 3 < 2
evalúa a true
, porque es realmente (1 < 3) < 2 === true < 2 === 1 < 2
.
Entonces, mi pregunta es esta: ¿Por qué x < y < z
no está comúnmente disponible en lenguajes de programación, con la semántica esperada?
static bool IsInRange<T>(this T candidate, T lower, T upper) where T : IComparable<T>
si realmente le molestara ver&&
s)Respuestas:
Estos son operadores binarios, que cuando se encadenan, normalmente y naturalmente producen un árbol de sintaxis abstracta como:
Cuando se evalúa (lo que haces desde las hojas hacia arriba), esto produce un resultado booleano
x < y
, luego obtienes un error de tipo al intentar hacerloboolean < z
. Parax < y < z
que funcione como lo discutió, debe crear un caso especial en el compilador para producir un árbol de sintaxis como:No es que no sea posible hacer esto. Obviamente lo es, pero agrega cierta complejidad al analizador para un caso que realmente no aparece con tanta frecuencia. Básicamente, está creando un símbolo que a veces actúa como un operador binario y, a veces, actúa efectivamente como un operador ternario, con todas las implicaciones del manejo de errores y todo lo que ello conlleva. Eso agrega mucho espacio para que las cosas salgan mal que los diseñadores de idiomas preferirían evitar si fuera posible.
fuente
x<y<z
medios, o que es más importante,x<y<=z
. Esta respuesta se interpretax<y<z
como un operador trinario. Así es exactamente cómo no debe interpretarse esta expresión matemática bien definida.x<y<z
es en cambio la abreviatura de(x<y)&&(y<z)
. Las comparaciones individuales siguen siendo binarias.¿Por qué
x < y < z
no está comúnmente disponible en lenguajes de programación?En esta respuesta concluyo que
Introducción
Puedo hablar desde la perspectiva de un pitonista sobre esta cuestión. Soy usuario de un idioma con esta función y me gusta estudiar los detalles de implementación del idioma. Más allá de esto, estoy algo familiarizado con el proceso de cambiar lenguajes como C y C ++ (el estándar ISO se rige por comité y versionado por año) y he visto a Ruby y Python implementar cambios importantes.
Documentación e implementación de Python
De los documentos / gramática, vemos que podemos encadenar cualquier número de expresiones con operadores de comparación:
y la documentación además establece:
Equivalencia lógica
Asi que
es lógicamente equivalente en términos de evaluación de
x
,y
yz
, con la excepción de quey
se evalúa dos veces:Nuevamente, la diferencia es que y se evalúa solo una vez con
(x < y <= z)
.(Tenga en cuenta que los paréntesis son completamente innecesarios y redundantes, pero los usé en beneficio de los que provienen de otros idiomas, y el código anterior es Python bastante legal).
Inspección del árbol de sintaxis abstracta analizado
Podemos inspeccionar cómo Python analiza los operadores de comparación encadenados:
Entonces podemos ver que esto realmente no es difícil de analizar para Python o cualquier otro lenguaje.
Y, contrariamente a la respuesta actualmente aceptada, la operación ternaria es una operación de comparación genérica, que toma la primera expresión, un iterable de comparaciones específicas y un iterable de nodos de expresión para evaluar según sea necesario. Simple.
Conclusión sobre Python
Personalmente, considero que la semántica de rango es bastante elegante, y la mayoría de los profesionales de Python que conozco alentarían el uso de la función, en lugar de considerarla dañina: la semántica está claramente establecida en la documentación de buena reputación (como se señaló anteriormente).
Tenga en cuenta que el código se lee mucho más de lo que se escribe. Los cambios que mejoran la legibilidad del código deben ser aceptados, no descartados mediante la aparición de espectros genéricos de miedo, incertidumbre y duda .
Entonces, ¿por qué x <y <z no está comúnmente disponible en lenguajes de programación?
Creo que hay una confluencia de razones que se centran en la importancia relativa de la característica y el momento / inercia relativa de cambio permitidos por los gobernadores de los idiomas.
Se pueden hacer preguntas similares sobre otras características del lenguaje más importantes
¿Por qué no hay herencia múltiple disponible en Java o C #? No hay una buena respuesta aquí para ninguna de las preguntas . Quizás los desarrolladores fueron demasiado vagos, como alega Bob Martin, y las razones dadas son meras excusas. Y la herencia múltiple es un tema bastante importante en informática. Ciertamente es más importante que el encadenamiento del operador.
Existen soluciones simples
El encadenamiento de operadores de comparación es elegante, pero de ninguna manera es tan importante como la herencia múltiple. Y al igual que Java y C # tienen interfaces como solución alternativa, también lo hace cada lenguaje para comparaciones múltiples: simplemente encadena las comparaciones con "y" s booleanos, que funcionan con bastante facilidad.
La mayoría de los idiomas están regidos por un comité.
La mayoría de los idiomas están evolucionando por comité (en lugar de tener un dictador benévolo para la vida sensible como Python). Y especulo que este tema simplemente no ha visto suficiente apoyo para salir de sus respectivos comités.
¿Pueden cambiar los idiomas que no ofrecen esta función?
Si un lenguaje lo permite
x < y < z
sin la semántica matemática esperada, este sería un cambio radical. Si no lo permitía en primer lugar, sería casi trivial agregarlo.Rompiendo cambios
En cuanto a los idiomas con romper cambios: hacemos idiomas de actualización a la rotura de los cambios de comportamiento - pero los usuarios tienden a no como este, especialmente los usuarios de características que pueden ser rotas. Si un usuario se basa en el comportamiento anterior de
x < y < z
, lo más probable es fuertemente protestar. Y puesto que la mayoría de los idiomas se rigen por el comité, no creo que se pueden conseguir mucha voluntad política para apoyar un cambio de este tipo.fuente
x < y < z
a(x < y) && (y < z)
. Recoger las liendres, el modelo mental para la comparación en cadena es de matemáticas general. La comparación clásica no las matemáticas en general es, pero la lógica booleana.x < y
produce una respuesta binaria{0}
.y < z
produce una respuesta binaria{1}
.{0} && {1}
produce la respuesta descriptiva. La lógica se compone, no encadenado ingenuamente.x<y<z
. Una vez, un idioma tiene la oportunidad de hacer algo como esto correctamente, y esa única oportunidad es al inicio del idioma.I have watched both Ruby and Python implement breaking changes.
Para aquellos que tienen curiosidad, aquí hay un cambio importante en C # 5.0 que involucra variables de ciclo y cierres: blogs.msdn.microsoft.com/ericlippert/2009/11/12/…Los lenguajes de computadora intentan definir las unidades más pequeñas posibles y le permiten combinarlas. La unidad más pequeña posible sería algo así como "x <y" que da un resultado booleano.
Puede solicitar un operador ternario. Un ejemplo sería x <y <z. Ahora, ¿qué combinaciones de operadores permitimos? Obviamente x> y> z o x> = y> = z o x> y> = z o tal vez x == y == z debería estar permitido. ¿Qué pasa con x <y> z? x! = y! = z? ¿Qué significa el último, x! = Y e y! = Z o que los tres son diferentes?
Ahora promociones de argumentos: en C o C ++, los argumentos serían promovidos a un tipo común. Entonces, ¿qué significa x <y <z de x es doble pero y y z son largas, largas int? ¿Los tres ascendieron al doble? ¿O y se toma como doble una vez y tanto tiempo como la otra vez? ¿Qué sucede si en C ++ uno o ambos operadores están sobrecargados?
Y por último, ¿permite algún número de operandos? ¿Como a <b> c <d> e <f> g?
Bueno, todo se vuelve muy complicado. Ahora, lo que no me importaría es que x <y <z produzca un error de sintaxis. Porque su utilidad es pequeña en comparación con el daño causado a los principiantes que no pueden entender qué hace realmente x <y <z.
fuente
x + y + z
, con la única diferencia de que eso no implica ninguna diferencia semántica. Por lo tanto, es que ningún idioma conocido se haya preocupado por hacerlo.x < y < z
equivalente a((x < y) and (y < z))
peroy
solo evaluada una vez ), lo que me imagino que los lenguajes compilados optimizan. "Debido a que su utilidad es pequeña en comparación con el daño causado a los principiantes que no pueden entender qué hace realmente x <y <z". Creo que es increíblemente útil. Probablemente va a -1 por eso ...a < b > c < d > e < f > g
, con el significado "obvio"(a < b) and (b > c) and (c < d) and (d > e) and (e < f) and (f > g)
. El hecho de que puedas escribir no significa que debas hacerlo. Eliminar tales monstruosidades es el ámbito de la revisión del código. Por otro lado, escribir0 < x < 8
en python tiene el obvio (sin comillas de miedo), lo que significa que x se encuentra entre 0 y 8, exclusivo.En muchos lenguajes de programación,
x < y
es una expresión binaria que acepta dos operandos y se evalúa como un único resultado booleano. Por lo tanto, si encadena múltiples expresiones,true < z
yfalse < z
no tiene sentido, y si esas expresiones se evalúan con éxito, es probable que produzcan el resultado incorrecto.Es mucho más fácil pensar
x < y
en una llamada de función que toma dos parámetros y produce un único resultado booleano. De hecho, así es como muchos idiomas lo implementan bajo el capó. Es composable, fácilmente compilable, y simplemente funciona.El
x < y < z
escenario es mucho más complicado. Ahora el compilador, en efecto, tiene a la moda tres funciones:x < y
,y < z
y el resultado de esos dos valores anded juntos, todo ello dentro del contexto de una discutible gramática lenguaje ambiguo .¿Por qué lo hicieron al revés? Porque es una gramática inequívoca, mucho más fácil de implementar y mucho más fácil de corregir.
fuente
e1 op1 e2 op2 e3 op3 ...
es equivalente ae1 op e2 and e2 op2 e3 and ...
excepto que cada expresión solo se evalúa una vez. (Por cierto, esta simple regla tiene el efecto secundario confuso de que declaraciones comoa == b is True
ya no tienen el efecto deseado).re:answer
Aquí fue donde mi mente fue inmediatamente para mi comentario sobre la pregunta principal. No considero soporte parax < y < z
agregar ningún valor específico a la semántica del lenguaje.(x < y) && (y < z)
tiene un soporte más amplio, es más explícito, más expresivo, se digiere más fácilmente en sus componentes, es más composable, más lógico, se refactoriza más fácilmente.La mayoría de los lenguajes principales están (al menos parcialmente) orientados a objetos. Fundamentalmente, el principio subyacente de OO es que los objetos envían mensajes a otros objetos (o a ellos mismos), y el receptor de ese mensaje tiene control total sobre cómo responder a ese mensaje.
Ahora, veamos cómo implementaríamos algo como
Podríamos evaluarlo estrictamente de izquierda a derecha (asociativo a la izquierda):
Pero ahora invocamos
__lt__
el resultado dea.__lt__(b)
, que es aBoolean
. Eso no tiene sentido.Probemos asociativo correcto:
No, eso tampoco tiene sentido. Ahora tenemos
a < (something that's a Boolean)
.Bien, ¿qué hay de tratarlo como azúcar sintáctico? Hagamos que una cadena de n
<
comparaciones envíe un mensaje n-1-ary. Esto podría significar que enviamos el mensaje__lt__
aa
, pasandob
yc
como argumentos:Bien, eso funciona, pero aquí hay una asimetría extraña:
a
decide si es menor queb
. Perob
no llega a decidir si es menor quec
, sino que esa decisión también es tomada pora
.¿Qué hay de interpretarlo como un mensaje n-ary enviado a
this
?¡Finalmente! Esto puede funcionar Significa, sin embargo, que el orden de los objetos ya no es una propiedad del objeto (por ejemplo, si
a
es menor queb
ni una propiedada
ni una deb
), sino una propiedad del contexto (es decirthis
).Desde un punto de vista convencional que parece extraño. Sin embargo, por ejemplo, en Haskell, eso es normal. Puede haber múltiples implementaciones diferentes de la
Ord
clase de tipos, por ejemplo, y si es o noa
menor queb
, depende de qué instancia de tipo de clase esté dentro del alcance.Pero, en realidad, no es que raro en absoluto! Tanto Java (
Comparator
) como .NET (IComparer
) tienen interfaces que le permiten inyectar su propia relación de orden en, por ejemplo, algoritmos de clasificación. Por lo tanto, reconocen plenamente que un pedido no es algo que se fija a un tipo, sino que depende del contexto.Hasta donde yo sé, actualmente no hay idiomas que realicen dicha traducción. Sin embargo, existe una precedencia: tanto Ioke como Seph tienen lo que su diseñador llama "operadores trinarios": operadores que son sintácticamente binarios, pero semánticamente ternarios. En particular,
se no se interpreta como que envía el mensaje
=
aa
pasarb
como argumento, sino más bien como enviar el mensaje=
a la "corriente de tierra" (un concepto similar pero no idéntica athis
) pasara
yb
como argumentos. Entonces,a = b
se interpreta comoy no
Esto podría generalizarse fácilmente a los operadores n-arios.
Tenga en cuenta que esto es realmente peculiar de los lenguajes OO. En OO, siempre tenemos un único objeto que es en última instancia responsable de interpretar el envío de un mensaje, y como hemos visto, no es inmediatamente obvio para algo como
a < b < c
qué objeto debería ser.Sin embargo, esto no se aplica a los lenguajes de procedimiento o funcionales. Por ejemplo, en Scheme , Common Lisp y Clojure , el
<
función es n-aria y se puede llamar con un número arbitrario de argumentos.En particular,
<
hace no media "menor que", en lugar de estas funciones se interpretan de forma ligeramente diferente:fuente
Es simplemente porque los diseñadores de idiomas no lo pensaron o no pensaron que fuera una buena idea. Python lo hace como lo describiste con una gramática LL (1) simple (casi).
fuente
1 < 2 < 3
en Java o C # y no tiene un problema con la precedencia del operador; Tiene un problema con los tipos no válidos. El problema es que esto seguirá analizándose exactamente como lo escribió, pero necesita una lógica de casos especiales en el compilador para pasar de una secuencia de comparaciones individuales a una comparación encadenada.El siguiente programa de C ++ compila con un pío de clang, incluso con advertencias configuradas al nivel más alto posible (
-Weverything
):El conjunto de compiladores de GNU, por otro lado, me advierte muy bien
comparisons like 'X<=Y<=Z' do not have their mathematical meaning [-Wparentheses]
.La respuesta es simple: compatibilidad con versiones anteriores. Hay una gran cantidad de código en la naturaleza que utiliza el equivalente de
1<3<2
y espera que el resultado sea verdadero.Un diseñador de idiomas solo tiene una oportunidad de hacer esto "correcto", y ese es el momento en que el idioma se diseña por primera vez. Obtener "incorrecto" inicialmente significa que otros programadores se aprovecharán rápidamente de ese comportamiento "incorrecto". Hacerlo "correcto" la segunda vez romperá esa base de código existente.
fuente
if x == y is True : ...
, en mi opinión: las personas que escriben ese tipo de código merecen ser sometidas a algún tipo de tortura extraordinariamente especial que (si estuviera vivo ahora) haría que el propio Torquemada se desmayara.