We're going to create two files, a JavaScript file and a JS++ file, to introduce how they interact together. We're going to use the classic 'rgbToHex' example to understand JS++ type guarantees.
First, create a file named rgbtohex.js (JavaScript) and enter the following code:
(Don't worry about trying to understand this JavaScript code. We're only going to use it as an example.)
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; }
Save the above JavaScript code to rgbtohex.js and create a new main.jspp file:
external alert; external rgbToHex; alert(rgbToHex(255, 255, 255));
We start by declaring 'alert' as 'external'. 'alert' is actually a function native to web browsers (via the "DOM API") that allows us to show message boxes. Next, we declare our custom JavaScript function we just wrote ('rgbToHex') as 'external'. This allows us to use the function from JS++.
Compile main.jspp. Now create an index.html file:
<!DOCTYPE html> <head> <title>RGB to Hex Conversion</title> </head> <body> <script src="rgbtohex.js"></script> <script src="main.jspp.js"></script> </body> </html>
Make sure the <script> tag for the JavaScript dependency (rgbtohex.js) is written before the JS++ compiled main.jspp.js file's <script> tag.
Open index.html in your web browser. You should see a message box like this:
The resulting value is "#ffffff", which is the equivalent hexadecimal color code for the RGB value of (255, 255, 255), the values we inputted in main.jspp.
We've just successfully used a custom JavaScript function we wrote from JS++.
"Type guarantees" are a unique feature of JS++ that mean your types are guaranteed to be correct during compile-time analysis and runtime code execution even when you're polluting your JS++ code with untyped JavaScript code. It's the closest thing you'll find to the type safety featured in safer languages that don't have to deal with large collections of untyped code.
When you use keywords like 'external', 'var', or 'function', you'll respectively get imports, variables, and functions that are "external types" and are untyped and unchecked. In other words, you're just writing JavaScript inside of JS++. However, when you declare variables and functions with a JS++ type such as 'int', 'string', 'bool', 'short', or 'unsigned int', these are known as "internal types" and are guaranteed to be correct.
When "external types" cross into the territory of "internal types," a runtime conversion is generated:
var x = 123; int y = x; // 'x' is converted to 'int', 'y' is therefore guaranteed to be 'int'
Due to the conversion, we are guaranteed to always be dealing with types correctly. We'll continue to explore how this concept works in practice throughout this chapter and the next, but, first, let's see how this applies to our RGB to Hex Converter.
In JS++, the key concept to understand is that JS++ splits data types into "internal" types and "external" types. Internal data types are the JS++ data types: int, string, bool, unsigned int, array types, dictionaries, and user-defined types (which we'll cover in Chapter 12). External types are the data types of JavaScript.
The JS++ type system is essentially the internal data types, the external data types, and the conversions between them. As we explored in the JavaScript chapter, type conversion is actually the safest and most fault-tolerant way to deal with types in JavaScript, and JS++ builds on this.
The RGB color model defines three color values: red, blue, and green. These color values are numeric and must fit within the range 0-255. JS++ actually happens to have a data type that exactly fits the specification of being numeric and guaranteeing numbers to be in the 0-255 range: the 'byte' data type.
Due to type guarantees, when we declare the data type in JS++, it can change the runtime behavior of our program – much the same way as they would in C, C++, Java, C#, and many other statically-typed programming languages. Therefore, if we define RGB as expecting byte values that range from 0 to 255, we are guaranteed for these values to be numeric and range between 0 to 255 during compile-time analysis and runtime application execution.
For example, during compile-time analysis and error checking, the compiler will give you an error if you accidentally try to pass an 'int' where a 'byte' was expected since 'int' allows numbers outside the 0 to 255 range.
Likewise, at runtime when your application is running, variables that you declared as a 'byte' are guaranteed to never be 256. This means we can never get invalid RGB values. (For developers coming from backgrounds such as C or C++, you're also guaranteed to never have an 'unsigned int' be -1 at runtime because unsigned – and signed – overflow results in integer wrapping.)
RGB values must be within the 0 to 255 range. Since JavaScript lacks data types, we have to manually perform these checks. Let's inspect how we handle this in our 'rgbToHex' JavaScript function.
The first three lines of our 'rgbToHex' JavaScript function check for numbers:
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 we did not provide numbers as arguments, we generate a runtime error with the 'throw' statement.
The next three lines check that these numbers fit within the 0 to 255 range:
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)");
Once again, if the numbers are not in range, we throw a runtime error using the 'throw' statement.
However, why settle for runtime errors during application execution? In JS++, these errors can be checked without running the program — at compile time.
You can use JavaScript more safely from JS++ than you can from JavaScript itself.
Let's start by modifying our main.jspp code to explicitly use the 'byte' data type:
external alert; external rgbToHex; byte red = 255; byte green = 255; byte blue = 255; alert(rgbToHex(red, green, blue));
Compile main.jspp and open index.html. The result should be exactly the same: a message box will pop up containing "#ffffff". However, there's a difference this time: you're guaranteed to only be able to send whole number values ranging from 0 to 255.
Remember all those checks in the JavaScript function to make sure we received acceptable RGB input values? If incorrect values were provided, the script will stop application execution by throwing exceptions. All of these potential errors have been wiped out by using JS++ and declaring types. These errors have been wiped out despite the fact that we haven't modified any of the original JavaScript code. The runtime errors are still present in the JavaScript code, but they will never execute because the input values we provide will always be correct.
Try changing one of the 'red', 'green', or 'blue' variables to a number outside the 0 to 255 range:
external alert; external rgbToHex; byte red = -1; byte green = 255; byte blue = 255; alert(rgbToHex(red, green, blue));
Now try to compile the modified main.jspp file. You'll get an error:
[ ERROR ] JSPPE5013: Computed value `-1' is out of range for type `byte' at line 4 char 11 at main.jspp
We can build on this and re-write the entire 'rgbToHex' function in JS++ instead of JavaScript to improve type safety while removing all the runtime checks and errors.
First, let's stop importing the JavaScript 'rgbToHex' function by removing the 'external' statement that imports it. Our main.jspp file should now just look like this:
external alert; alert(rgbToHex(255, 255, 255));
Don't try to compile right now. We'll compile once we've moved all the JavaScript code.
The 'rgbToHex' JavaScript function depends on 'TypeError' (native to JavaScript) and 'RangeError' (also native to JavaScript). Let's import these:
external alert; external TypeError, RangeError; alert(rgbToHex(255, 255, 255));
Finally, we can just copy and paste the code from rgbToHex.js (the JavaScript file) into main.jspp (the JS++ file):
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));
Now we can compile the code. It should compile successfully. Open index.html in your web browser, and you should notice the result is still a message box displaying "#ffffff".
We just copied a large chunk of JavaScript code directly into JS++ and it worked. This is because JS++ is a superset of JavaScript; in other words, it's just JavaScript with more features.
However, we still need to convert this "untyped" JavaScript code that we copied and pasted into JS++ to take advantage of JS++ type guarantees. Otherwise, we're just writing regular JavaScript inside JS++.
We'll start converting our JavaScript 'rgbToHex' function into JS++ by removing all the runtime checks and errors. You won't be needing them anymore.
Your main.jspp file should now look like this:
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));
By removing the runtime checks and errors, we can actually improve potential performance. An obvious observation is that there are fewer instructions to process overall. However, by removing all the if statements, we improve instruction-level parallelism at the hardware (CPU) level and decrease the opportunities for branch misprediction. (These optimizations may or may not apply due to all the additional layers of abstraction in browser scripting. For now, it will suffice to know that we've decreased the overall number of operations.)
Since we removed the runtime checks and errors, our code is now completely unchecked! Try calling 'rgbToHex' with an invalid value like 256. You'll observe that it's allowed. Let's fix that.
Add 'byte' in front of each parameter to restrict their data types:
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 and open index.html. As usual, you'll see a message box displaying "#ffffff".
Now try calling the 'rgbToHex' function with an invalid value like -1 or 256 again. You will no longer be able to compile the code because the errors will be detected at compile time.
Our 'rgbToHex' function is still declared with the keyword 'function'. This is the JavaScript way of declaring a function, and it still leaves our function unsafe.
In JS++, it is best practice to always declare data types when possible. Since we always return a string value for our 'rgbToHex' function, we should restrict the return type to '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));
Now all 'return' statements inside the function must return an expression that evaluates to string data.
Last but not least, look at these statements:
var r = red.toString(16); var g = green.toString(16); var b = blue.toString(16);
What methods are being called?
Remember, a lot of programming with types in JavaScript is based on intuition. (The JS++ type system builds on top of this intuition so programming in JS++ should feel like a natural progression for JavaScript developers.) In each of the above statements, 'toString(16)' is being called. In other words, we know we should expect string data. Let's change our 'var' to 'string' then:
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));
Compile main.jspp. Delete the rgbtohex.js JavaScript file. Edit index.html to completely remove the reference to the JavaScript rgbtohex.js file. Your index.html code should now look like this:
<!DOCTYPE html> <head> <title>RGB to Hex Conversion</title> </head> <body> <script src="main.jspp.js"></script> </body> </html>
Open index.html in your web browser. You should see a message box with "#ffffff". Congratulations! You've just changed a JavaScript function into JS++.
As we learned in the previous chapter about JavaScript, we intuitively have a sense of the data type to expect.
However, there isn't always a compatible JS++ type for all JavaScript values. This can be illustrated with jQuery. For example, let's re-visit an example from Chapter 5 on loops:
external $; for (int i = 0; i < 2; i++) { $("#content").append("i is now ", i, "; "); }
This is an interesting example because it covers a lot. First of all, what if we wanted to break up the jQuery method call so that the selection of #content (a page element with ID "content") is saved to a variable? There isn't a compatible JS++ type here, so we can just use 'var':
external $; for (int i = 0; i < 2; i++) { var content = $("#content"); content.append("i is now ", i, "; "); }
In theory, it's better to have zero unsafe code. However, in practice, 'external', 'var', and 'function' are necessary for compatibility with JavaScript. In other words, external types are needed. Ideally, we would never use externals in "perfect JS++ code, " but real-world programming is almost never ideal.
The astute observer may have also noticed a conversion in the opposite direction: from internal to external. We declared the 'for' loop counter variable 'i' as an 'int', an internal JS++ type. However, when we pass it as an argument to jQuery, it gets converted to 'external':
content.append("i is now ", i, "; ");
By default, the "primitive types" (such as 'int', 'string', 'bool', 'unsigned int', etc.) have implicit default conversions defined to and from 'external'. This is because JavaScript has natural compatible data types for the internal JS++ primitive types. Thus, the conversion is also natural. It gets more complex with user-defined types and user-defined conversions, but we'll cover those in later chapters.
It's important to understand the difference between a forward guarantee and a backwards guarantee. In a forward guarantee, we don't worry about the past and focus on the future. For instance, consider a simple wrapper for the browser's message box function ('alert'):
external alert; void messageBox(string message) { alert(message); }
In the above code, it doesn't matter if you call 'messageBox' with unsafe code because the 'message' variable is guaranteed to be 'string' going forward for all logic inside the 'messageBox' function due to type guarantees.
Why are JS++ type guarantees necessary? Because, with unsafe JavaScript, even large websites with resources, like GoDaddy, can have broken checkouts resulting in lost sales from a single TypeError:
In the above, the "Continue" button never works so the shopping cart checkout cannot be finished.
Curiously, the checkout failure comes from the same 'toLowerCase' method that we explored in Chapter 8. Since JS++ is the only sound, gradually-typed programming language for JavaScript, type guarantees mean more than just adding "types" to JavaScript. It means the types are guaranteed to be correct when you declare them, and there are no edge cases that can result in runtime failures (unlike attempts to add data types by Microsoft and Facebook).
Don't be the one to break critical code because someone told you to write the code in JavaScript instead. You need more than types and type checking, you need type guarantees.