RGBtoHex: Usando JavaScript de JS++
Vamos a crear dos archivos, un archivo JavaScript y un archivo JS++, para presentar cómo interactúan entre sí. Vamos a usar el ejemplo clásico ‘rgbToHex’ para entender las garantías de tipo JS++.
Primero, cree un archivo llamado rgbtohex.js (JavaScript) e ingrese el siguiente código:
(No se preocupe por tratar de comprender este código JavaScript. Solo lo usaremos como ejemplo).
function rgbToHex(red, green, blue) { if (typeof red !== "number") throw new TypeError("Expected numeric 'red' value"); if (typeof green !== "number") throw new TypeError("Expected numeric 'green' value"); if (typeof blue !== "number") throw new TypeError("Expected numeric 'blue' value"); if (red < 0 || red > 255) throw new RangeError("Expected 'red' value between 0 and 255 (inclusive)"); if (green < 0 || green > 255) throw new RangeError("Expected 'green' value between 0 and 255 (inclusive)"); if (blue < 0 || blue > 255) throw new RangeError("Expected 'blue' value between 0 and 255 (inclusive)"); var r = red.toString(16); var g = green.toString(16); var b = blue.toString(16); while (r.length < 2) r = "0" + r; while (g.length < 2) g = "0" + g; while (b.length < 2) b = "0" + b; return "#" + r + g + b; }
Guarde el código JavaScript anterior en rgbtohex.js y cree un nuevo archivo main.jspp:
external alert; external rgbToHex; alert(rgbToHex(255, 255, 255));
Comenzamos declarando ‘alerta’ como ‘externa’. ‘alerta’ es en realidad una función nativa de los navegadores web (a través de la «API DOM») que nos permite mostrar cuadros de mensaje. A continuación, declaramos nuestra función JavaScript personalizada que acabamos de escribir (‘rgbToHex’) como ‘externa’. Esto nos permite usar la función de JS++.
Compilar main.jspp. Ahora cree un archivo index.html:
<!DOCTYPE html> <head> <title>RGB to Hex Conversion</title> </head> <body> <script src="rgbtohex.js"></script> <script src="main.jspp.js"></script> </body> </html>
Asegúrese de que la etiqueta <script> para la dependencia de JavaScript (rgbtohex.js) esté escrita antes de la etiqueta <script> del archivo main.jspp.js compilado por JS++.
Abra index.html en su navegador web. Debería ver un cuadro de mensaje como este:
El valor resultante es «#ffffff», que es el código de color hexadecimal equivalente para el valor RGB de (255, 255, 255), los valores que ingresamos en main.jspp.
Acabamos de usar con éxito una función JavaScript personalizada que escribimos desde JS++.
Tipo Garantías
Las «garantías de tipo» son una característica única de JS++ que significa que se garantiza que sus tipos sean correctos durante el análisis en tiempo de compilación y la ejecución del código en tiempo de ejecución, incluso cuando está contaminando su código JS++ con código JavaScript sin tipo. Es lo más parecido que encontrará a la seguridad de tipos que se presenta en lenguajes más seguros que no tienen que lidiar con grandes colecciones de código sin escribir.
Cuando usa palabras clave como ‘externo’, ‘var’ o ‘función’, obtendrá respectivamente importaciones, variables y funciones que son «tipos externos» y no están tipificados ni marcados. En otras palabras, solo estás escribiendo JavaScript dentro de JS++. Sin embargo, cuando declara variables y funciones con un tipo JS++ como ‘int’, ‘string’, ‘bool’, ‘short’ o ‘unsigned int’, estos se conocen como «tipos internos» y se garantiza que son correctos. .
Cuando los «tipos externos» cruzan el territorio de los «tipos internos», se genera una conversión en tiempo de ejecución:
var x = 123; int y = x; // 'x' is converted to 'int', 'y' is therefore guaranteed to be 'int'
Debido a la conversión, tenemos la garantía de tratar siempre correctamente los tipos. Continuaremos explorando cómo funciona este concepto en la práctica a lo largo de este capítulo y el siguiente, pero, primero, veamos cómo se aplica esto a nuestro convertidor de RGB a hexadecimal.
En JS++, el concepto clave a entender es que JS++ divide los tipos de datos en tipos «internos» y tipos «externos». Los tipos de datos internos son los tipos de datos de JS++: int, string, bool, int sin firmar, tipos de arrays, diccionarios y tipos definidos por el usuario (que veremos en el Capítulo 11). Los tipos externos son los tipos de datos de JavaScript.
El sistema de tipos JS++ es esencialmente los tipos de datos internos, los tipos de datos externos y las conversiones entre ellos. Como exploramos en el capítulo de JavaScript, la conversión de tipos es en realidad la forma más segura y tolerante a fallas de manejar tipos en JavaScript, y JS++ se basa en esto.
Valores RGB
El modelo de color RGB define tres valores de color: rojo, azul y verde. Estos valores de color son numéricos y deben estar dentro del rango 0-255. JS ++ en realidad tiene un tipo de datos que se ajusta exactamente a la especificación de ser numérico y garantizar que los números estén en el rango 0-255: el tipo de datos ‘byte’.
Debido a las garantías de tipo, cuando declaramos el tipo de datos en JS ++, puede cambiar el comportamiento de tiempo de ejecución de nuestro programa, de la misma manera que lo harían en C, C++, Java, C# y muchos otros lenguajes de programación de tipo estático. Por lo tanto, si definimos RGB como valores de bytes esperados que oscilan entre 0 y 255, tenemos la garantía de que estos valores serán numéricos y oscilarán entre 0 y 255 durante el análisis en tiempo de compilación y la ejecución de la aplicación en tiempo de ejecución.
Por ejemplo, durante el análisis en tiempo de compilación y la verificación de errores, el compilador le dará un error si accidentalmente intenta pasar un ‘int’ donde se esperaba un ‘byte’ ya que ‘int’ permite números fuera del rango de 0 a 255.
Del mismo modo, en tiempo de ejecución cuando su aplicación se está ejecutando, se garantiza que las variables que declaró como un ‘byte’ nunca serán 256. Esto significa que nunca podremos obtener valores RGB no válidos. (Para los desarrolladores que provienen de entornos como C o C++, también tiene la garantía de que nunca tendrá un ‘int sin firmar’ que sea -1 en tiempo de ejecución porque el desbordamiento sin firmar y firmado da como resultado un ajuste de entero).
Examinando el JavaScript sin tipo
Los valores RGB deben estar dentro del rango de 0 a 255. Dado que JavaScript carece de tipos de datos, tenemos que realizar estas comprobaciones manualmente. Inspeccionemos cómo manejamos esto en nuestra función de JavaScript ‘rgbToHex’.
Las primeras tres líneas de nuestra función de JavaScript ‘rgbToHex’ buscan números:
if (typeof red !== "number") throw new TypeError("Expected numeric 'red' value"); if (typeof green !== "number") throw new TypeError("Expected numeric 'green' value"); if (typeof blue !== "number") throw new TypeError("Expected numeric 'blue' value");
Si no proporcionamos números como argumentos, generamos un error de tiempo de ejecución con la instrucción ‘lanzar’.
Las siguientes tres líneas verifican que estos números se ajusten al rango de 0 a 255:
if (red < 0 || red > 255) throw new RangeError("Expected 'red' value between 0 and 255 (inclusive)"); if (green < 0 || green > 255) throw new RangeError("Expected 'green' value between 0 and 255 (inclusive)"); if (blue < 0 || blue > 255) throw new RangeError("Expected 'blue' value between 0 and 255 (inclusive)");
Una vez más, si los números no están dentro del rango, lanzamos un error de tiempo de ejecución usando la instrucción ‘lanzar’.
Sin embargo, ¿por qué conformarse con errores de tiempo de ejecución durante la ejecución de la aplicación? En JS++, estos errores se pueden verificar sin ejecutar el programa, en tiempo de compilación.
Usar JavaScript de forma más segura
Puede usar JavaScript de forma más segura desde JS++ que desde JavaScript mismo.
Comencemos modificando nuestro código main.jspp para usar explícitamente el tipo de datos ‘byte’:
external alert; external rgbToHex; byte red = 255; byte green = 255; byte blue = 255; alert(rgbToHex(red, green, blue));
Compile main.jspp y abra index.html. El resultado debería ser exactamente el mismo: aparecerá un cuadro de mensaje que contiene «#ffffff». Sin embargo, hay una diferencia esta vez: tiene la garantía de que solo podrá enviar valores de números enteros que van desde 0 a 255.
¿Recuerda todas esas comprobaciones en la función de JavaScript para asegurarse de que recibimos valores de entrada RGB aceptables? Si se proporcionaron valores incorrectos, el script detendrá la ejecución de la aplicación lanzando excepciones. Todos estos posibles errores se han eliminado mediante el uso de JS++ y la declaración de tipos. Estos errores se han eliminado a pesar de que no hemos modificado nada del código JavaScript original. Los errores de tiempo de ejecución todavía están presentes en el código JavaScript, pero nunca se ejecutarán porque los valores de entrada que proporcionamos siempre serán correctos.
Intente cambiar una de las variables ‘rojo’, ‘verde’ o ‘azul’ a un número fuera del rango de 0 a 255:
external alert; external rgbToHex; byte red = -1; byte green = 255; byte blue = 255; alert(rgbToHex(red, green, blue));
Ahora intente compilar el archivo main.jspp modificado. Obtendrá un error:
[ ERROR ] JSPPE5013: Computed value `-1' is out of range for type `byte' at line 4 char 11 at main.jspp
Podemos aprovechar esto y volver a escribir toda la función ‘rgbToHex’ en JS++ en lugar de JavaScript para mejorar la seguridad de tipos y eliminar todas las comprobaciones y errores de tiempo de ejecución.
Mover rgbToHex a JS++
Primero, dejemos de importar la función JavaScript ‘rgbToHex’ eliminando la declaración ‘externa’ que la importa. Nuestro archivo main.jspp ahora debería verse así:
external alert; alert(rgbToHex(255, 255, 255));
No intentes compilar ahora. Lo compilaremos una vez que hayamos movido todo el código JavaScript.
La función de JavaScript ‘rgbToHex’ depende de ‘TypeError’ (nativo de JavaScript) y ‘RangeError’ (también nativo de JavaScript). Importemos estos:
external alert; external TypeError, RangeError; alert(rgbToHex(255, 255, 255));
Finalmente, podemos simplemente copiar y pegar el código de rgbToHex.js (el archivo JavaScript) en main.jspp (el archivo JS++):
external alert; external TypeError, RangeError; function rgbToHex(red, green, blue) { if (typeof red !== "number") throw new TypeError("Expected numeric 'red' value"); if (typeof green !== "number") throw new TypeError("Expected numeric 'green' value"); if (typeof blue !== "number") throw new TypeError("Expected numeric 'blue' value"); if (red < 0 || red > 255) throw new RangeError("Expected 'red' value between 0 and 255 (inclusive)"); if (green < 0 || green > 255) throw new RangeError("Expected 'green' value between 0 and 255 (inclusive)"); if (blue < 0 || blue > 255) throw new RangeError("Expected 'blue' value between 0 and 255 (inclusive)"); var r = red.toString(16); var g = green.toString(16); var b = blue.toString(16); while (r.length < 2) r = "0" + r; while (g.length < 2) g = "0" + g; while (b.length < 2) b = "0" + b; return "#" + r + g + b; } alert(rgbToHex(255, 255, 255));
Ahora podemos compilar el código. Debería compilarse con éxito. Abra index.html en su navegador web y debería notar que el resultado sigue siendo un cuadro de mensaje que muestra «#ffffff».
Simplemente copiamos una gran parte del código JavaScript directamente en JS++ y funcionó. Esto se debe a que JS++ es un superconjunto de JavaScript; en otras palabras, es solo JavaScript con más funciones.
Sin embargo, aún necesitamos convertir este código JavaScript «sin tipo» que copiamos y pegamos en JS++ para aprovechar las garantías de tipo JS++. De lo contrario, solo estamos escribiendo JavaScript normal dentro de JS++.
Convertir el JavaScript «sin tipo» en JS++ «con tipo»
Comenzaremos a convertir nuestra función JavaScript ‘rgbToHex’ en JS++ eliminando todas las verificaciones y errores de tiempo de ejecución. Ya no los necesitarás.
Su archivo main.jspp ahora debería verse así:
external alert; function rgbToHex(red, green, blue) { var r = red.toString(16); var g = green.toString(16); var b = blue.toString(16); while (r.length < 2) r = "0" + r; while (g.length < 2) g = "0" + g; while (b.length < 2) b = "0" + b; return "#" + r + g + b; } alert(rgbToHex(255, 255, 255));
Al eliminar las comprobaciones y los errores de tiempo de ejecución, podemos mejorar el rendimiento potencial. Una observación obvia es que hay menos instrucciones para procesar en general. Sin embargo, al eliminar todas las declaraciones if, mejoramos el paralelismo a nivel de instrucción a nivel de hardware (CPU) y disminuimos las oportunidades de errores de predicción de bifurcación. (Estas optimizaciones pueden aplicarse o no debido a todas las capas adicionales de abstracción en las secuencias de comandos del navegador. Por ahora, será suficiente saber que hemos reducido la cantidad total de operaciones).
Dado que eliminamos las comprobaciones y los errores de tiempo de ejecución, ¡nuestro código ahora está completamente desmarcado! Intente llamar a ‘rgbToHex’ con un valor no válido como 256. Observará que está permitido. Arreglemos eso.
Agregue ‘byte’ delante de cada parámetro para restringir sus tipos de datos:
external alert; function rgbToHex(byte red, byte green, byte blue) { var r = red.toString(16); var g = green.toString(16); var b = blue.toString(16); while (r.length < 2) r = "0" + r; while (g.length < 2) g = "0" + g; while (b.length < 2) b = "0" + b; return "#" + r + g + b; } alert(rgbToHex(255, 255, 255));
Compile main.jspp y abra index.html. Como de costumbre, verá un cuadro de mensaje que muestra «#ffffff».
Ahora intente llamar a la función ‘rgbToHex’ con un valor no válido como -1 o 256 nuevamente. Ya no podrá compilar el código porque los errores se detectarán en el momento de la compilación.
Agregar más tipos
Nuestra función ‘rgbToHex’ todavía se declara con la palabra clave ‘función’. Esta es la forma de JavaScript de declarar una función, y aún deja nuestra función insegura.
En JS++, es una buena práctica declarar siempre los tipos de datos cuando sea posible. Dado que siempre devolvemos un valor de string para nuestra función ‘rgbToHex’, debemos restringir el tipo de retorno a ‘string’.
external alert; string rgbToHex(byte red, byte green, byte blue) { var r = red.toString(16); var g = green.toString(16); var b = blue.toString(16); while (r.length < 2) r = "0" + r; while (g.length < 2) g = "0" + g; while (b.length < 2) b = "0" + b; return "#" + r + g + b; } alert(rgbToHex(255, 255, 255));
Ahora todas las declaraciones de ‘retorno’ dentro de la función deben devolver una expresión que se evalúe como datos de string.
Por último, pero no menos importante, mira estas declaraciones:
var r = red.toString(16); var g = green.toString(16); var b = blue.toString(16);
¿Qué métodos se están llamando?
Recuerde, gran parte de la programación con tipos en JavaScript se basa en la intuición. (El sistema de tipos JS++ se basa en esta intuición, por lo que la programación en JS++ debería sentirse como una progresión natural para los desarrolladores de JavaScript). En cada una de las declaraciones anteriores, se llama a ‘toString(16)’. En otras palabras, sabemos que debemos esperar datos de string. Cambiemos nuestra ‘var’ a ‘string’ entonces:
external alert; string rgbToHex(byte red, byte green, byte blue) { string r = red.toString(16); string g = green.toString(16); string b = blue.toString(16); while (r.length < 2) r = "0" + r; while (g.length < 2) g = "0" + g; while (b.length < 2) b = "0" + b; return "#" + r + g + b; } alert(rgbToHex(255, 255, 255));
Compilar main.jspp. Elimine el archivo JavaScript rgbtohex.js. Edite index.html para eliminar por completo la referencia al archivo JavaScript rgbtohex.js. Su código index.html ahora debería verse así:
<!DOCTYPE html> <head> <title>RGB to Hex Conversion</title> </head> <body> <script src="main.jspp.js"></script> </body> </html>
Abra index.html en su navegador web. Debería ver un cuadro de mensaje con «#ffffff». ¡Felicidades! Acaba de cambiar una función de JavaScript a JS++.
Otras Consideraciones
Como aprendimos en el capítulo anterior sobre JavaScript, intuitivamente tenemos una idea del tipo de datos que se espera.
Sin embargo, no siempre hay un tipo JS++ compatible para todos los valores de JavaScript. Esto se puede ilustrar con jQuery. Por ejemplo, volvamos a visitar un ejemplo del Capítulo 5 sobre bucles:
external $; for (int i = 0; i < 2; i++) { $("#content").append("i is now ", i, "; "); }
Este es un ejemplo interesante porque cubre mucho. En primer lugar, ¿qué pasaría si quisiéramos dividir la llamada al método jQuery para que la selección de #contenido (un elemento de página con ID «contenido») se guarde en una variable? No hay un tipo de JS++ compatible aquí, por lo que solo podemos usar ‘var’:
external $; for (int i = 0; i < 2; i++) { var content = $("#content"); content.append("i is now ", i, "; "); }
En teoría, es mejor tener cero código no seguro. Sin embargo, en la práctica, ‘externo’, ‘var’ y ‘función’ son necesarios para la compatibilidad con JavaScript. En otras palabras, se necesitan tipos externos. Idealmente, nunca usaríamos elementos externos en «código JS++ perfecto», pero la programación del mundo real casi nunca es ideal.
El observador astuto también puede haber notado una conversión en la dirección opuesta: de interno a externo. Declaramos la variable de contador de bucle ‘for’ ‘i’ como un ‘int’, un tipo JS++ interno. Sin embargo, cuando lo pasamos como argumento a jQuery, se convierte en ‘externo’:
content.append("i is now ", i, "; ");
De forma predeterminada, los «tipos primitivos» (como ‘int’, ‘string’, ‘bool’, ‘unsigned int’, etc.) tienen conversiones predeterminadas implícitas definidas hacia y desde ‘externo’. Esto se debe a que JavaScript tiene tipos de datos compatibles naturales para los tipos primitivos internos de JS++. Así, la conversión también es natural. Se vuelve más complejo con los tipos definidos por el usuario y las conversiones definidas por el usuario, pero los cubriremos en capítulos posteriores.
Es importante comprender la diferencia entre una garantía a plazo y una garantía al revés . En una garantía a plazo, no nos preocupamos por el pasado y nos enfocamos en el futuro. Por ejemplo, considere un contenedor simple para la función de cuadro de mensaje del navegador (‘alerta’):
external alert; void messageBox(string message) { alert(message); }
En el código anterior, no importa si llama a ‘messageBox’ con un código no seguro porque se garantiza que la variable ‘mensaje’ será ‘string’ para toda la lógica dentro de la función ‘messageBox’ debido a las garantías de tipo.
Nota final
¿Por qué son necesarias las garantías de tipo JS++? Porque, con JavaScript inseguro, incluso los sitios web grandes con recursos, como GoDaddy, pueden tener pagos interrumpidos que resultan en la pérdida de ventas por un solo TypeError:
En lo anterior, el botón «Continuar» nunca funciona, por lo que no se puede finalizar el pago del carrito de compras.
Curiosamente, la falla de verificación proviene del mismo método ‘toLowerCase’ que exploramos en el Capítulo 9. Dado que JS ++ es el único lenguaje de programación sólido y gradualmente tipificado para JavaScript, las garantías de tipo significan más que solo agregar «tipos» a JavaScript. Significa que se garantiza que los tipos son correctos cuando los declara, y no hay casos extremos que puedan provocar fallas en el tiempo de ejecución (a diferencia de los intentos de agregar tipos de datos por parte de Microsoft y Facebook).
No seas el que rompa el código crítico porque alguien te dijo que escribieras el código en JavaScript. Necesita más que tipos y verificación de tipos, necesita garantías de tipo .