JavaScript – in contrast to Object Pascal – is a dynamic language. Beyond dynamic type checks, this also means that it can be extended at runtime. Thus, variables and with this fields of prototypes may only be present (accessible) when they have been set first. In external JavaScript libraries, these are typically initialized or set, but not necessarily. And sometimes it is even intentional to leave them unset.
This concept is known as nullable types. A concept which is present – to some extend – in most modern languages. For Delphi an implementation is available in the DSharp project. It is defined as generic, which makes it useful for any type. At its core it’s a record that contains the original type and a second variable to store whether the value has been set or not.
With Smart Mobile Studio we could just copy the same concept and it should work identical. However, we can do better than this as we can make use of the underlying JavaScript, which itself already knows nullable types. Also, when translating headers one often faces nullable types (in particular in WebIDL or TypeScript definitions), which need to be as close to the original type as possible.
Nullable Types in JavaScript
Although the average reader of this blog might not like to dive too deep into the muddy waters of JavaScript, it is useful to at least understand the concept of dynamic types in JavaScript. As you might or might not know, variables in JavaScript do not need to have a type when specified. Despite being separate on the Pascal side, one can’t even just differentiate between an integer and a float. In fact the type remains fully dynamic throughout its entire lifecycle. And – with a null value called ‘undefined’ present – it can also be null (which equals to: not set at all).
However, on the Object Pascal side we have to decide, the type can either be Integer or Float or something else. A variable can hardly have two types. Thus if you specify a type as Integer it will always be an integer and as such it has always a value. To solve this, one could pick the ‘Variant’ type, but doing so looses the strong typing, which can be considered to be an advantage of Pascal over JavaScript.
A working solution for this would be to use a specific type (just pick the most prominent one) and cast it to any other type. In the context of float vs. integer, one would probably pick float and ‘cast’ this to integer using the round() function.
Unfortunately this won’t help us for nullable types as in Pascal we can assume that a value is always set and that it has a certain type.
Nullable Type Proposal
If we want to mimic nullable types without rewriting the underlying DWScript code generator, we must use Variant. But we can do this with a twist. We extend it with a helper. Something like:
type TNullableFloat = Variant; TNullableFloatHelper = strict helper for TNullableFloat end;
would allow us to extend the TNullableFloat with everything we need to add functions similar to the DSharp code.
The ‘strict’ in the above code snippet is important to avoid adding the helper functions to all variants. The least necessary to add would be something like this:
TNullableFloat = Variant; TNullableFloatHelper = strict helper for TNullableFloat private function GetHasValue: Boolean; public property HasValue: Boolean read GetHasValue; end; function TNullableFloatHelper.GetHasValue: Boolean; begin Result := Self <> null; end;
which introduces a property to check whether the variable is set at all.
Now we can check the variable easily whether the value has been set. Still, the improvement is little as we could also check whether it’s variant value is ‘null’ as easy as this. This said, it is a big improvement over using the float type solely as it would not allow to check whether it is set or not without doing hacks.
To make it even more useful, more type relevant code should be added.
Type Dependent Extensions
Since the underlying DWScript does still neither support generics nor templates, it’s necessary to do the next bits for every type that should become nullable. In this case we only focus on the float type (without restriction of any kind).
type TNullableFloat = Variant; TNullableFloatHelper = strict helper for TNullableFloat private function GetHasValue: Boolean; function GetValue: Float; public property HasValue: Boolean read GetHasValue; property Value: Float read GetValue; end;
with it’s implementation
{ TNullableFloatHelper } function TNullableFloatHelper.GetValue: Float; begin if not HasValue then raise EInvalidOpException.Create('Nullable float type has no value'); Result := Float(Self); end; function TNullableFloatHelper.GetHasValue: Boolean; begin Result := Self <> null; end;
can now return the value in its primitive type. If not set, an exception is raised. The improvements over using solely variants are the fact that you can now assign it to other (float) types without the need for explicit casting (done implicitly in the GetValue function). In addition you get an exception if it is not specified. An exception that you can handle, if needed.
To enhance it even further one could also override operators for this variant type. This might become useful to disallow certain operations (as implicitly done by the primitive types).
By having the ‘raw’ variant type available it’s also possible to make use of the DWScript’s native coalesce() function. Its operator is ‘??’ and translates to something like this:
function Coalesce(var Value: Variant; Default: Variant): Variant; begin if value = null then Result := Default else Result := Value; end;
So for example let’s consider the nullable float value x in the following example
WriteLn(x ?? 1);
If it is specified it will print its value, otherwise it will print ‘1’.
Conclusion
This article explained the concept of nullable types and how it can be used in Smart Mobile Studio. The presented solution can be used to translate nullable types, which are common in TypeScript or WebIDL definitions to Object Pascal headers.
So far this is not used in the API headers for good reasons. The above code will introduce additional code, where-as the API headers are written in a way not to produce any output. Also, as the underlying DWScript does neither support generics nor templates it would be required to write code for each and every type. However, if this concept (or a similar one) is available natively in DWScript it will surely be used and thus it’s important to already know about it.