Tengo estos dos pequeños programas:
do
#include <stdio.h>
int main()
{
if (5) {
printf("true\n");
}
else {
printf("false\n");
}
return 0;
}
Java
class type_system {
public static void main(String args[]) {
if (5) {
System.out.println("true");
}
else {
System.out.println("false");
}
}
}
que informa el mensaje de error:
type_system.java:4: error: incompatible types: int cannot be converted to boolean
if (5) {
^
1 error
Mi entendimiento
Hasta ahora, entendí este ejemplo como una demostración de los diferentes sistemas de tipos. C tiene un tipo más débil y permite la conversión de int a boolean sin errores. Java está más fuertemente tipado y falla, porque no se permite ninguna conversación implícita.
Por lo tanto, mi pregunta: ¿dónde entendí mal las cosas?
Lo que no estoy buscando
Mi pregunta no está relacionada con un mal estilo de codificación. Sé que es malo, pero me interesa saber por qué C lo permite y Java no. Por lo tanto, estoy interesado en el sistema de tipos de lenguaje, específicamente su fortaleza.
java
c
type-systems
toogley
fuente
fuente
int
valores. Un ejemplo más apropiado seríaif (pointer)
.Respuestas:
1. C y Java son lenguajes diferentes
El hecho de que se comporten de manera diferente no debería ser terriblemente sorprendente.
2. C no está haciendo ninguna conversión de
int
abool
Como podria C ni siquiera tenía un
bool
tipo verdadero para convertir hasta 1999 . C fue creado a principios de la década de 1970, yif
fue parte de él incluso antes de que fuera C, cuando era solo una serie de modificaciones a B 1 .if
no fue simplemente unNOP
en C por casi 30 años. Actuó directamente sobre valores numéricos. El verborrea en el estándar C ( enlace PDF ), incluso más de una década después de la introducción debool
s a C, todavía especifica el comportamiento deif
(p 148) y?:
(p 100) usando los términos "desigual a 0" e "igual a 0 "en lugar de los términos booleanos" verdadero "o" falso "o algo similar.Convenientemente ...
3. ... los números simplemente son lo que operan las instrucciones del procesador.
JZ
yJNZ
son sus instrucciones básicas de ensamblaje x86 para la ramificación condicional. Las abreviaturas son " J UMP si Z ero" y " J UMP si N ot Z ero". Los equivalentes para el PDP-11, donde se originó C, sonBEQ
(" B ranch si EQ ual") yBNE
(" B ranch si N ot E qual").Estas instrucciones verifican si la operación anterior resultó en cero o no y saltan (o no) en consecuencia.
4. Java tiene mucho más énfasis en la seguridad que C 2
Y, con la seguridad en mente, decidieron que restringir
if
aboolean
s valía la pena el costo (tanto de implementar tal restricción como de los costos de oportunidad resultantes).1. B ni siquiera tiene tipos en absoluto. Los lenguajes de ensamblaje generalmente tampoco. Sin embargo, B y los lenguajes de ensamblaje logran manejar la ramificación muy bien.
2) En palabras de Dennis Ritchie al describir las modificaciones planificadas a B que se convirtieron en C (énfasis mío):
fuente
C 2011 Borrador en línea
Tenga en cuenta que esta cláusula especifica solo que la expresión de control tendrá un tipo escalar (
char
/short
/int
/long
/ etc.), no específicamente un tipo booleano. Se ejecuta una rama si la expresión de control tiene un valor distinto de cero.Compara eso con
Especificación del lenguaje Java SE 8
Java, OTOH, requiere específicamente que la expresión de control en una
if
instrucción tenga un tipo booleano.Por lo tanto, no se trata de escribir débil versus fuerte, se trata de lo que cada definición de lenguaje respectiva especifica como una expresión de control válida.
Editar
En cuanto a por qué los idiomas son diferentes en este aspecto particular, varios puntos:
C se derivó de B, que era un lenguaje "sin tipo", básicamente, todo era una palabra de 32 a 36 bits (dependiendo del hardware), y todas las operaciones aritméticas eran operaciones enteras. El sistema de tipos de C se atornillaba poco a poco, de modo que ...
C no tenía un tipo booleano distinto hasta la versión de 1999 del lenguaje. C simplemente siguió la convención B de usar cero para representar
false
y no cero para representartrue
.Java data de C un par de décadas después y fue diseñado específicamente para abordar algunas de las deficiencias de C y C ++. Sin duda, ajustar la restricción sobre lo que puede ser una expresión de control en una
if
declaración fue parte de eso.No hay razón para esperar ningún dos lenguajes de programación que se pueden hacer las cosas de la misma manera. Incluso los lenguajes tan estrechamente relacionados como C y C ++ divergen de algunas maneras interesantes, de modo que puede tener programas legales de C que no son programas legales de C ++, o son programas legales de C ++ pero con semántica diferente, etc.
fuente
int
aboolean
, mientras que Java no lo hizo. La respuesta es que no hay tal conversión en C. Los idiomas son diferentes porque son idiomas diferentes.Muchas de las respuestas parecen estar dirigidas a la expresión de asignación incrustada que está dentro de la expresión condicional. (Si bien ese es un tipo conocido de trampa potencial, no es la fuente del mensaje de error de Java en este caso).
Posiblemente esto se deba a que el OP no publicó el mensaje de error real y el
^
cursor apunta directamente al=
operador de asignación.Sin embargo, el compilador apunta a
=
porque es el operador el que produce el valor final (y, por lo tanto, el tipo final) de la expresión que ve el condicional.Se queja de probar un valor no booleano, con el siguiente tipo de error:
La prueba de enteros, aunque a veces es conveniente, se considera una trampa potencial que los diseñadores de Java eligen evitar. Después de todo, Java tiene un verdadero tipo de datos booleanos , que C no tiene (no tiene ningún tipo booleano) .
Esto también se aplica a los punteros de prueba de C para nulo / no nulo a través de
if (p) ...
yif (!p) ...
, que Java de manera similar no permite, en cambio, requiere un operador de comparación explícito para obtener el booleano requerido.fuente
if
declaración.bool b = ...; int v = 5 + b;
. Esto es diferente a los idiomas con un tipo booleano completo que no se puede usar en aritmética.int v = 5; float f = 2.0 + v;
es legal en C._Bool
es uno de los tipos enteros sin signo estándar definidos por el Estándar (ver 6.2.5 / 6).Hay dos partes en su pregunta:
¿Por qué Java no convertir
int
aboolean
?Esto se reduce a que Java pretende ser lo más explícito posible. Es muy estático, muy "en tu cara" con su sistema de tipos. Las cosas que se convierten automáticamente en otros idiomas no lo son, en Java. Tienes que escribir
int a=(int)0.5
también. La conversiónfloat
aint
perdería información; lo mismo que convertirint
aboolean
y por lo tanto sería propenso a errores. Además, habrían tenido que especificar muchas combinaciones. Claro, estas cosas parecen ser obvias, pero pretendían errar por precaución.Ah, y en comparación con otros lenguajes, Java fue enormemente exacto en su especificación, ya que el código de bytes no era solo un detalle de implementación interna. Tendrían que especificar cualquiera y todas las interacciones, precisamente. Enorme empresa.
¿Por qué
if
no acepta otros tipos queboolean
?if
perfectamente podría definirse como para permitir otros tipos queboolean
. Podría tener una definición que diga que los siguientes son equivalentes:true
int != 0
String
con.length>0
null
(y no unBoolean
valorfalse
).null
y cuyo métodoObject.check_if
(inventado por mí solo para esta ocasión) regresetrue
.No lo hicieron; no era realmente necesario, y querían tenerlo lo más robusto, estático, transparente, fácil de leer, etc. posible. Sin características implícitas. Además, la implementación sería bastante compleja, estoy seguro, tener que probar cada valor para todos los casos posibles, por lo que el rendimiento también puede haber jugado un pequeño factor también (Java solía ser lento en las computadoras de ese día; recuerde allí no había compiladores JIT con los primeros lanzamientos, al menos no en las computadoras que usé entonces).
Razón más profunda
Una razón más profunda podría ser el hecho de que Java tiene sus tipos primitivos, por lo tanto, su sistema de tipos está dividido entre objetos y primitivos. Tal vez, si hubieran evitado eso, las cosas habrían resultado de otra manera. Con las reglas dadas en la sección anterior, tendrían que definir la veracidad de cada primitiva explícitamente (ya que las primitivas no comparten una superclase, y no hay una bien definida
null
para las primitivas). Esto se convertiría en una pesadilla, rápidamente.panorama
Bueno, y al final, tal vez sea solo una preferencia de los diseñadores de idiomas. Cada idioma parece girar a su manera allí ...
Por ejemplo, Ruby no tiene tipos primitivos. Todo, literalmente todo, es un objeto. Les resulta muy fácil asegurarse de que cada objeto tenga un método determinado.
Ruby busca la veracidad en todos los tipos de objetos que puedes arrojarle. Curiosamente, todavía no tiene
boolean
tipo (porque no tiene primitivas), y tampoco tieneBoolean
clase. Si pregunta qué clase tiene el valortrue
(fácilmente disponibletrue.class
), obtieneTrueClass
. Esa clase realmente tiene métodos, a saber, los 4 operadores para booleans (| & ^ ==
). Aquí,if
considera su valor falsey si y solo si esfalse
onil
(elnull
de Ruby). Todo lo demás es verdad. Entonces,0
o""
ambos son ciertos.Hubiera sido trivial para ellos crear un método
Object#truthy?
que podría implementarse para cualquier clase y devolver una verdad individual. Por ejemplo,String#truthy?
podría haberse implementado para ser verdadero para cadenas no vacías, o cualquier otra cosa. No lo hicieron, a pesar de que Ruby es la antítesis de Java en la mayoría de los departamentos (tipeo de pato dinámico con mixin, reapertura de clases y todo eso).Lo que puede sorprender a un programador de Perl que está acostumbrado a
$value <> 0 || length($value)>0 || defined($value)
ser veraz. Y así.Ingrese SQL con su convención que
null
dentro de cualquier expresión automáticamente lo hace falso, pase lo que pase. Por lo tanto(null==null) = false
. En Ruby,(nil==nil) = true
. Tiempos felices.fuente
((int)3) * ((float)2.5)
está bastante bien definido en Java (lo es7.5f
).int
afloat
también pierde información en general. ¿Java también prohíbe tal conversión implícita?Además de las otras buenas respuestas, me gustaría hablar sobre la consistencia entre los idiomas.
Cuando pensamos en una declaración si matemáticamente pura, entendemos que la condición puede ser verdadera o falsa, no otro valor. Cada lenguaje de programación importante respeta este ideal matemático; si le das un valor booleano verdadero / falso a una declaración if, entonces puedes esperar ver un comportamiento consistente e intuitivo todo el tiempo.
Hasta aquí todo bien. Esto es lo que implementa Java, y solo lo que implementa Java.
Otros idiomas intentan brindar conveniencias para valores no booleanos. Por ejemplo:
n
es un entero. Ahora definaif (n)
ser taquigrafía paraif (n != 0)
.x
es un número de coma flotante. Ahora definaif (x)
ser taquigrafía paraif (x != 0 && !isNaN(x))
.p
es un tipo de puntero. Ahora definaif (p)
ser taquigrafía paraif (p != null)
.s
es un tipo de cadena. Ahora definaif (s)
serif (s != null && s != "")
.a
es un tipo de matriz. Ahora definaif (a)
serif (a != null && a.length > 0)
.Esta idea de proporcionar pruebas if abreviadas parece buena en la superficie ... hasta que encuentre diferencias en diseños y opiniones:
if (0)
se trata como falso en C, Python, JavaScript; pero tratado como verdadero en Ruby.if ([])
se trata como falso en Python pero cierto en JavaScript.Cada idioma tiene sus propias buenas razones para tratar expresiones de una forma u otra. (Por ejemplo, los únicos valores falsos en Ruby son
false
ynil
, por0
lo tanto, son verdaderos).Java adoptó un diseño explícito para obligarlo a proporcionar un valor booleano a una instrucción if. Si tradujo apresuradamente el código de C / Ruby / Python a Java, no puede dejar las pruebas if lax sin cambios; necesita escribir la condición explícitamente en Java. Tomar un momento para detenerse y pensar puede salvarlo de errores descuidados.
fuente
x != 0
es lo mismo quex != 0 && !isNaN(x)
? Además, normalmente ess != null
para punteros, pero algo más para no punteros.Bueno, cada tipo escalar en C, puntero, booleano (desde C99), número (punto flotante o no) y enumeración (debido a la asignación directa a números) tiene un valor de Falsey "natural", por lo que es lo suficientemente bueno para cualquier expresión condicional .
Java también los tiene (incluso si los punteros de Java se denominan referencias y están muy restringidos), pero introdujo el auto-boxing en Java 5.0 que confunde las aguas de manera inaceptable. Además, los programadores de Java exhortan el valor intrínseco de escribir más.
El único error que generó el debate acerca de si la restricción de las expresiones condicionales de tipo booleano es el error tipográfico de escribir una misión en la que se pretende una comparación, que se no se dirigió al restringir el tipo de expresiones condicionales en absoluto , pero al no permitir el uso de una asignación de expresión desnuda por su valor
Cualquier compilador moderno de C o C ++ lo maneja fácilmente, dando una advertencia o error para tales construcciones cuestionables si se le pregunta.
Para aquellos casos en los que es exactamente lo que se pretendía, resulta útil agregar paréntesis.
En resumen, restringir a booleano (y el equivalente en recuadro en Java) parece un intento fallido de hacer una clase de errores tipográficos causados por la elección
=
de errores de compilación de asignación.fuente
==
con operandos booleanos, de los cuales el primero es una variable (que luego podría escribirse=
) dentro,if
ocurre con mucha menos frecuencia que con otros tipos, diría, por lo que es solo un intento semi-fallido.""
,(Object) ""
,0.0
,-0.0
,NaN
, las matrices vacías, yBoolean.FALSE
? Especialmente el último es divertido, ya que es un puntero no nulo (verdadero) que desempaqueta a falso. +++ También odio escribirif (o != null)
, pero ahorrarme algunos caracteres todos los días y dejarme pasar medio día depurando mi expresión "inteligente" no es un buen negocio. Dicho esto, me encantaría ver un camino intermedio para Java: reglas más generosas pero sin dejar ninguna ambigüedad.Dos de los objetivos de diseño de Java fueron:
Permita que el desarrollador se concentre en el problema comercial. Haga las cosas más simples y menos propensas a errores, por ejemplo, recolección de basura para que los desarrolladores no necesiten centrarse en las pérdidas de memoria.
Portátil entre plataformas, por ejemplo, ejecutar en cualquier computadora con cualquier CPU.
Se sabía que el uso de la asignación como expresión causaba muchos errores debido a errores tipográficos, por lo que, según el objetivo n. ° 1 anterior, no está permitido la forma en que está tratando de usarlo.
Además, inferir que cualquier valor distinto de cero = verdadero y cualquier valor cero = falso no es necesariamente portátil (sí, lo creas o no, algunos sistemas tratan 0 como verdadero y 1 como falso ), por lo que bajo el objetivo # 2 anterior, no está permitido implícitamente . Todavía puedes emitir explícitamente, por supuesto.
fuente
Como ejemplo de lo que hacen otros idiomas: Swift requiere una expresión de un tipo que admita el protocolo "BooleanType", lo que significa que debe tener un método "boolValue". El tipo "bool" obviamente es compatible con este protocolo y usted puede crear sus propios tipos. Los tipos enteros no admiten este protocolo.
En versiones anteriores del idioma, los tipos opcionales admitían "BooleanType" para que pudiera escribir "if x" en lugar de "if x! = Nil". Lo que hizo que usar un "Bool opcional" fuera muy confuso. Un bool opcional tiene valores nulo, falso o verdadero y "si b" no se ejecutaría si b fuera nulo y si b fuera verdadero o falso. Esto ya no está permitido.
Y parece que hay un tipo al que no le gusta abrir tus horizontes ...
fuente