Existent Types JS++ invented the concept of "existent types" which enable developers to prevent out-of-bounds errors at compile time. All Standard Library containers, such as System.Array and System.Dictionary will return existent types on container accesses. Existent types use a + suffix. Arrays offer a way to learn about existent types: 12int[] arr = [ 1, 2, 3 ];int+ x = arr[0]; In the above code, the existent type int+ defines the range of possible values as the range of possible values for int (-2,147,483,648 to 2,147,483,647) plus the out-of-bounds value (undefined). JS++ differentiates from JavaScript in that, for internal types, undefined is only used for representing out-of-bounds accesses as all JS++ variables must be initialized and all functions must return. Restrictions Existent types cannot be used as the element type for arrays, and they cannot be used as a type argument to generic classes. Thus, while the following is valid JavaScript, it is not valid JS++: 12var arr1 = [ 1, 2, undefined, 3 ];// int+[] arr1 = [ 1, 2, undefined, 3 ]; // Error because 'int+' is not a valid element type The reason for this restriction is that the JS++ out-of-bounds value (undefined) cannot simultaneously also be a possible within-bounds value. Operators JS++ provides three operators for dealing with nullable types that also apply to existent types: ?? (safe default operator), ?. (safe navigation operator), and ?= (safe assignment operator). Safe Default Operator The safe default operator can be used for providing an alternative value if an out-of-bounds access was made: 12345import System; int[] arr = [ 1, 2, 3 ];int+ x = arr[Math.random(100)];int y = x ?? 5; // Possible values for y: 1, 2, 3, 5 Safe Navigation Operator The safe navigation operator can be used to access a class member or call a method without manually checking for an out-of-bounds access: 123456789101112import System; class Foo{ void doSomething() { Console.log("Something happened."); }} Foo[] arr = [ new Foo ];Foo+ foo = arr[Math.random(100)];foo?.doSomething(); Safe Assignment Operator The safe assignment operator can be used to assign a different value if an out-of-bounds access occurred: 12345import System; int[] arr = [ 1, 2, 3 ];int+ x = arr[Math.random(100)];x ?= 5; // Possible values for x: 1, 2, 3, 5 Combining with Nullable Types Existent types can be combined with nullable types using the ?+ syntax: 1234567891011import System; Dictionary<bool?> inviteeDecisions = { "Roger": true, "Anton": true, "James": null, // James is undecided "Qin": false}; bool?+ isJamesAttending = inviteeDecisions["James"]; // 'null'bool?+ isBryceAttending = inviteeDecisions["Bryce"]; // 'undefined' API Re-design Existent types introduce new best practices. Consider the following case: 12345678910import System; class Foo {} void log(Foo foo) { Console.log(foo.toString());} Foo[] arr = [ new Foo() ];log(arr[0]); // ERROR In JS++, "non-nullable" classes don't have a default value. While we can resolve the above error by providing a new instance of Foo as a default value, this is not always desirable: 12345678910import System; class Foo {} void log(Foo foo) { Console.log(foo.toString());} Foo[] arr = [ new Foo() ];log(arr[0] ?? new Foo); // Fixes the error, but what if we don't want a new instance? Instead, with existent types, a new approach is necessary. Due to the language design, most JS++ code is naturally modular and object-oriented. Free functions and static methods are not easy to test either. By changing our free function that accepts Foo to an instance method of Foo, we can use existent types more freely and correctly: 1234567891011import System; class Foo{ void log() { Console.log(this.toString()); }} Foo[] arr = [ new Foo() ];arr[0]?.log(); // Use the ?. operator instead of ??, which requires us to provide a default value Casting Sometimes, we know we are accessing an in-bounds element. In such cases, we can cast: 1234class Foo {} Foo[] arr = [ new Foo() ];Foo foo = (Foo) arr[0]; In other cases, we can't infer from the source code whether or not the accessed element is in-bounds. However, we know we have checked the value so a cast is still safe: 1234567891011import System; class Foo {} Foo[] arr = [ new Foo() ];int element = Math.random(1, 100);if (arr[element] != undefined) { Foo foo = (Foo) arr[element]; // ...} Unlike casting from other types, casting from nullable or existent types will only strip the nullable or existent type. It will not simultaneously allow downcasting. If you desire to downcast, you need to downcast separately. Casting can throw a CastException. As a result, it is preferred to avoid casting when using existent types and prefer methods such as API re-design instead. Guidelines Never use existent types where nullable types will suffice. In the JS++ Standard Library, existent types are used solely for representing whether a container element exists and is within bounds. It is strongly advised against using existent types outside of container APIs or data structures. See Also undefined Share HTML | BBCode | Direct Link