¿Siguen siendo necesarios tipos específicos?

20

Una cosa que se me ocurrió el otro día, son tipos específicos que aún son necesarios o un legado que nos está frenando. Lo que quiero decir es: ¿realmente necesitamos short, int, long, bigint, etc., etc.

Entiendo el razonamiento, las variables / objetos se mantienen en la memoria, la memoria debe asignarse y, por lo tanto, necesitamos saber qué tan grande puede ser una variable. Pero, en realidad, un lenguaje de programación moderno no debería ser capaz de manejar "tipos adaptativos", es decir, si algo solo se asigna en el rango abreviado, usa menos bytes, y si algo se asigna repentinamente a un número muy grande, la memoria se asigna acordemente para ese caso particular.

Flotante, real y doble son un poco más complicados ya que el tipo depende de la precisión que necesite. Sin embargo, las cadenas deberían ser capaces de ocupar menos memoria en muchos casos (en .Net) donde se usa principalmente ascii, pero las cadenas siempre ocupan el doble de memoria debido a la codificación unicode.

Un argumento para tipos específicos podría ser que es parte de la especificación, es decir, por ejemplo, una variable no debería ser mayor que un cierto valor, por lo que lo configuramos como shortint. Pero, ¿por qué no tener restricciones de tipo? Sería mucho más flexible y poderoso poder establecer rangos y valores permisibles en variables (y propiedades).

Me doy cuenta del inmenso problema de modernizar la arquitectura de tipos, ya que está tan estrechamente integrado con el hardware subyacente y cosas como la serialización pueden volverse realmente complicadas. Pero desde una perspectiva de programación, debería ser genial, ¿no?

Homde
fuente
66
PHP, Ruby, Perl y otros no requieren que indique los tipos de variables. El entorno lo resuelve por ti.
FrustratedWithFormsDesigner
77
Las cadenas Unicode no tienen que ocupar memoria adicional cuando se usan solo para ASCII (UTF-8).
2
Pero hay una diferencia entre las variantes y los tipos adaptativos IMO. Las variantes no se escriben en absoluto, pero se escriben cuando se asignan, mientras que los tipos adaptativos se escribirían, pero de manera más flexible. (y me gusta el concepto de restricciones de tipo)
Homde
Esto me recuerda a este proyecto: tom.lokhorst.eu/media/…
LennyProgrammers
44
¿Qué hay de Ada? type hour is range 0 .. 23;
Mouviciel 02 de

Respuestas:

12

Creo totalmente que este es el caso. Las restricciones semánticas valen más que las restricciones de implementación. Preocuparse por el tamaño de algo se siente como preocuparse por la velocidad de algo cuando surgía la programación orientada a objetos.

No ha reemplazado la programación crítica de rendimiento. Simplemente ha hecho que la programación crítica sin rendimiento sea más productiva.

Mark Canlas
fuente
1
Consulte los contratos de código en .NET 4.0.
Steven Jeuris
+1 Cuando se trata de almacenamiento / transmisión de datos (por ejemplo, redes), las restricciones son fundamentales para maximizar la eficiencia del protocolo / implementación. Además, hay mucho terreno por ganar si las colecciones mecanografiadas están disponibles. Aparte de eso, es seguro asumir que la eficiencia puede quedar en segundo plano (especialmente si disminuye la posibilidad de errores semánticos).
Evan Plaice
9

Tipos adaptativos significa lógica para hacer la adaptación, significa trabajar en tiempo de ejecución para ejecutar esa lógica (la creación de plantillas y el tiempo de compilación requerirían un tipo específico, la inferencia de tipos es un caso especial donde se obtiene lo mejor de los dos mundos). Ese trabajo adicional podría estar bien en entornos donde el rendimiento no es crítico y el sistema mantiene un tamaño razonable. En otros entornos no lo es (los sistemas embebidos son uno, donde en algún momento tiene que usar tipos enteros de 32/64 bits para el rendimiento de la CPU y tipos enteros de 8/16 bits para la optimización de la copia de seguridad de la memoria estática).

Incluso los lenguajes de uso general que admiten el enlace tardío (resolución de tipos en tiempo de ejecución, como VB6) tienden a promover una escritura fuerte ahora (VB.NET), debido al impacto en el rendimiento que solía surgir cuando se abusaba del enlace tardío, y porque a menudo terminar con un código feo cuando los tipos no son explícitos ( Referencia / Refactorización profesional en Visual Basic - Danijel Arsenovski ).

Matthieu
fuente
Defina "escritura automática".
@delnan: reemplazó el tipeo automático con encuadernación tardía, que es lo que quise decir :)
Matthieu
Hay muchos lenguajes de uso general que resuelven tipos en tiempo de ejecución, Common Lisp para nombrar solo uno. (Para fines de rendimiento, puede declarar tipos en Common Lisp, por lo que puede hacerlo solo en secciones de rendimiento crítico).
David Thornley
@David Thornley: "forzar" una escritura fuerte puede haber sido demasiado fuerte, "promocionar" sería más apropiado, actualicé mi respuesta en consecuencia. Un lenguaje que le permita elegir entre los dos tipos de enlace dependiendo de la situación es ciertamente mejor que ser forzado de una forma u otra. Especialmente cuando no se realiza una programación de bajo nivel y se centra en la lógica.
Matthieu
4

Simplicidad, memoria y velocidad Cuando declaro una variable, la memoria para esa variable se asigna en un bloque. Para admitir una variable en crecimiento dinámico, tendría que agregar el concepto de memoria no contigua a esa variable (ya sea eso o reservar el bloque más grande que la variable puede representar). La memoria no contigua reduciría el rendimiento en la asignación / recuperación. Asignar la mayor cantidad posible sería un desperdicio en el escenario en el que solo necesito un byte pero el sistema se reserva mucho tiempo.

Piense en las compensaciones entre una matriz y un vector (o una lista vinculada). Con una matriz, buscar una posición específica es una simple cuestión de obtener la posición de inicio y cambiar el puntero de memoria x espacios para ubicar esa nueva posición en la memoria. Piense en un int como un bit [32] leer un int implica recorrer esa matriz para obtener todos los valores de bit.

Para crear un tipo de número dinámico, debe cambiarlo de una matriz de bits a un vector de bits. Leer su número dinámico implica ir a la cabeza, obtener ese bit, preguntar dónde está el siguiente bit en la memoria, moverse a esa ubicación, obtener ese bit, etc. Para cada bit en el número dinámico, está haciendo tres operaciones de lectura ( actual), leer (dirección de siguiente), mover (siguiente). Imagina leer los valores de un millón de números. Eso es un millón de operaciones adicionales. Puede parecer insignificante. Pero piense en los sistemas (como los financieros) donde cada milisegundo importa.

Se tomó la decisión de que poner la responsabilidad sobre el desarrollador para verificar el tamaño y validar es una pequeña compensación en comparación con afectar el rendimiento del sistema.

Michael Brown
fuente
1
La otra alternativa es implementar números similares a las listas de matriz donde la matriz se reasigna cuando el número supera el tamaño actual. También debe tener en cuenta el caso en el que el usuario QUIERE que el desbordamiento se repita.
Michael Brown
Eso es cierto, pero algo así como una simplificación. Podría llegar a una estructura de matriz más eficiente, mientras que no es tan rápido como tipeado estáticamente podría ser "lo suficientemente rápido" para la mayoría de los casos. por ejemplo, podría guardar información en bloques de diferentes tipos, si la matriz no fuera completamente irregular, eso no ocuparía mucha más memoria o rendimiento. O la matriz podría sacrificar algo de memoria para tener un índice de algunos tipos. La matriz podría incluso auto optimizarse en función de su contenido. Aún podría tener la opción de escribir el memorysize mediante una restricción de tipo si necesitara rendimiento.
Homde
Para ser justos, no es tan brutal como parece. Cf mi próxima respuesta.
Paul Nathan
3

Se requieren tipos específicos para lenguajes y proyectos centrados en hardware. Un ejemplo son los protocolos de red en el cable.

Pero creemos, por diversión, un tipo varint en un lenguaje como C ++. Constrúyalo a partir de una newvariedad de entradas.

No es difícil implementar la adición: solo haga una xor de los bytes juntos y verifique los bits altos: si hay una operación de acarreo, newen un nuevo byte superior y transfiera el bit. La resta sigue trivialmente en la representación del complemento a 2. (Esto también se conoce como sumador de transporte de ondas).

La multiplicación sigue de manera similar; use la adición / desplazamiento iterativo. Como siempre, el verdadero giro en tu cola es la división [*].

¿Pero qué pierdes cuando esto sucede?

  • Tiempo determinista. Tiene un syscall ( new) que puede activarse en puntos que no son necesariamente controlables.

  • Espacio determinista.

  • Las matemáticas de semi-software son lentas.

Si necesita usar un lenguaje de capa de hardware y también necesita operar a un nivel alto (lento) y no quiere incorporar un motor de secuencias de comandos, varinttiene mucho sentido. Probablemente esté escrito en alguna parte.

[*] Cf algoritmos matemáticos de hardware para formas más rápidas de hacerlo, aunque generalmente el truco son las operaciones paralelas.

Paul Nathan
fuente
2

Esta es una buena pregunta. Explica por qué un lenguaje como Python no necesita "short, int, long, bigint, etc.": los enteros son, bueno, enteros (hay un solo tipo entero en Python 3), y no tienen un tamaño límite (más allá de la memoria de la computadora, por supuesto).

En cuanto a Unicode, la codificación UTF-8 (que es parte de Unicode) solo usa un único carácter para los caracteres ASCII, por lo que no es tan malo.

En términos más generales, los lenguajes dinámicos parecen ir en la dirección que usted menciona. Sin embargo, por razones de eficiencia, los tipos más restringidos son útiles en algunos casos (como los programas que deben ejecutarse rápidamente). No veo muchos cambios en el futuro previsible, ya que los procesadores organizan los datos en bytes (o 2, 4, 8, etc., bytes).

Eric O Lebigot
fuente
1

Sobre la base de la teoría del lenguaje tienes razón. Los tipos deben basarse en un conjunto de estados legales, las transformaciones disponibles para esos estados y las operaciones que se realizan en esos estados.

Sin embargo, esto es más o menos lo que le ofrece la programación OOP en su forma típica. De hecho, en Java, está hablando de las clases BigIntegery BigDecimal, que asignan espacio en función de la cantidad necesaria para almacenar el objeto. (Como señaló FrustratedWithFormsDesigner, muchos lenguajes de tipo secuencia de comandos están aún más lejos en este camino y ni siquiera requieren una declaración de tipo y almacenarán lo que les des).

Sin embargo, el rendimiento sigue siendo relevante, y dado que es costoso cambiar los tipos en tiempo de ejecución y dado que los compiladores no pueden garantizar el tamaño máximo de una variable en tiempo de compilación, todavía tenemos variables de tamaño estático para tipos simples en muchos idiomas.

jprete
fuente
Me doy cuenta de que algún tipo de tipeo dinámico / adaptativo parece costoso y menos eficaz que lo que tenemos ahora, y usando los compiladores actuales ciertamente lo serían. Pero estamos 100% seguros de que si construye un lenguaje y un compilador desde cero, no podría hacerlos, si no es tan rápido como estáticamente escrito, al menos lo más rápido posible para que valga la pena.
Homde
1
@MKO: ¿Por qué no lo intentas y ves?
Anon
1
Sí, puede hacerlo factiblemente rápido (pero probablemente nunca tan rápido como un sistema estático para números). Pero la parte "vale la pena" es más complicada. La mayoría de las personas trabajan con datos cuyo rango se ajusta cómodamente en una into una double, y si no lo hace, lo saben, por lo que el dimensionamiento dinámico del valor es una característica que no necesitan pagar.
jprete
Como todos los programadores, por supuesto, sueño con algún día hacer mi propio idioma;)
Homde
@jprete: no estoy de acuerdo; la mayoría de las personas desconocen los posibles resultados intermedios grandes. Tal lenguaje puede y ha sido hecho lo suficientemente rápido para la mayoría de los propósitos.
David Thornley
1

Depende del idioma. Para lenguajes de nivel superior como Python, Ruby, Erlang, etc., solo tiene el concepto de números integrales y decimales.

Sin embargo, para una determinada clase de idiomas que tienen estos tipos son muy importantes. Cuando está escribiendo código para leer y escribir formatos binarios como PNG, JPeg, etc., necesita saber con precisión cuánta información se está leyendo a la vez. Lo mismo con escribir kernels del sistema operativo y controladores de dispositivos. No todos hacen esto, y en los lenguajes de nivel superior usan bibliotecas C para hacer el trabajo pesado detallado.

En short, todavía hay un lugar para los tipos más específicos, pero muchos problemas de desarrollo no requieren esa precisión.

Berin Loritsch
fuente
0

Recientemente creé un editor de lógica de escalera y tiempo de ejecución y decidí estar muy limitado con los tipos:

  • Booleano
  • Número
  • Cuerda
  • Fecha y hora

Creo que lo hizo más intuitivo para el usuario. Esta es una desviación radical de la mayoría de los PLC que tienen todo el rango "normal" de tipos que verías en un lenguaje como C.

Scott Whitlock
fuente
0

Los lenguajes de programación se han estado moviendo en esa dirección. Tome cadenas por ejemplo. En los idiomas antiguos, debe declarar el tamaño de la cadena, comoPIC X(42) en COBOL, DIM A$(42)en algunas versiones de BASIC o [ VAR] CHAR(42)en SQL. En los idiomas modernos, solo tiene un stringtipo asignado dinámicamente y no necesita pensar en el tamaño.

Sin embargo, los enteros son diferentes:

Lo que quiero decir es: ¿realmente necesitamos short, int, long, bigint, etc., etc.

Echa un vistazo a Python. Solía ​​distinguir entre el tamaño de máquina ( int) y el tamaño arbitrario (long enteros de ). En 3.x, el primero se ha ido (lo viejo longes lo nuevo int) y nadie se lo pierde.

Pero todavía hay un tipo especializado para secuencias de enteros de 8 bits en forma de bytesy bytearray. ¿Por qué no usar un tupleo listde enteros, respectivamente? Cierto,bytes que tiene métodos extra similares a cadenas que tupleno, pero seguramente la eficiencia tuvo mucho que ver con eso.

Flotante, real y doble son un poco más complicados ya que el tipo depende de la precisión que necesite.

Realmente no. El enfoque de "todo es doble precisión" es muy común.

dan04
fuente
1
Tal vez los tipos base deberían declarar la intención básica del tipo, es decir, int para números "ordinarios", el doble para todos los "decimales" normales (¿no deberían los ints tener decimales aunque por simplicidad?) "Dinero" para trabajar con cantidades y bytes para trabajar con datos binarios. Una restricción de tipo declarada a través de un atributo podría permitir declarar rango permitido, precisión decimal, nulabilidad e incluso valores permitidos. Sería genial si pudieras crear tipos personalizados y reutilizables de esa manera
Homde
@konrad: En mi humilde opinión, la razón por la que los enteros "sin signo" causan tales dolores de cabeza en C es que a veces se usan para representar números y otras veces para representar miembros de un anillo algebraico abstracto envolvente. Tener tipos separados de "anillo" y "número sin signo" podría asegurar que un código como unum64 += ring32a-ring32bsiempre produzca el comportamiento correcto, independientemente de si el tipo entero predeterminado es de 16 bits o 64 [tenga en cuenta que el uso de +=es esencial; una expresión como unum64a = unum64b + (ring32a-ring32b);debería ser rechazada como ambigua.]
supercat
0

Entiendo el razonamiento, las variables / objetos se mantienen en la memoria, la memoria debe asignarse y, por lo tanto, necesitamos saber qué tan grande puede ser una variable. Pero, en realidad, un lenguaje de programación moderno no debería ser capaz de manejar "tipos adaptativos", es decir, si algo solo se asigna en el rango abreviado, usa menos bytes, y si algo se asigna repentinamente a un número muy grande, la memoria se asigna acordemente para ese caso particular.

Flotante, real y doble son un poco más complicados ya que el tipo depende de la precisión que necesite. Sin embargo, las cadenas deberían ser capaces de ocupar menos memoria en muchos casos (en .Net) donde se usa principalmente ascii, pero las cadenas siempre ocupan el doble de memoria debido a la codificación unicode.

Fortran ha tenido algo similar (no sé si esto es exactamente lo que quiere decir, ya que realmente estoy viendo dos preguntas). Por ejemplo, en F90 hacia arriba no necesita definir explícitamente un tamaño de letra , por así decirlo. Lo cual es bueno, no solo porque le brinda un lugar central para definir sus tipos de datos, sino también una forma portátil de definirlos. REAL * 4 no es lo mismo en todas las implementaciones en todos los procesadores (y por procesador me refiero a CPU + compilador), no por una posibilidad remota.

selected_real_kind (p, r) devuelve el valor de tipo de un tipo de datos real con precisión decimal mayor de al menos p dígitos y rango de exponente mayor de al menos r.

Entonces vas, por ejemplo;

program real_kinds
integer,parameter :: p6 = selected_real_kind(6)
integer,parameter :: p10r100 = selected_real_kind(10,100) !p is precision, r is range
integer,parameter :: r400 = selected_real_kind(r=400)
real(kind=p6) :: x
real(kind=p10r100) :: y
real(kind=r400) :: z

print *, precision(x), range(x)
print *, precision(y), range(y)
print *, precision(z), range(z)
end program real_kinds

(Creo que es un ejemplo bastante autoexplicativo).

Aún no sé si entendí su pregunta correctamente, y esto es lo que usted menciona.

Torre
fuente