Skip to the main content of this page.
The sable mammal, Martes zibellina
SABLE Programming Language

Types

You already know how to construct type identifiers from classes and use them in TypeRefs. There are a few more interesting things about types to cover.

Nilable Types

In the CLR and in most languages, expressions of a reference type can have the no-value value, Nil (or null). How, then does one specify "this method argument should never be Nil," or "this method never returns Nil"? Usually, this information is buried in a method comment, "Throws ArgumentNullException if 'arg' is null." Can't we tell the compiler that it shouldn't be Nil, and have it report this as an error?

In SABLE, reference type expressions are more firmly expected to be non-Nil. Reference and value types both can be made Nilable by appending a question mark, allowing them to hold Nil. (For value types, this uses the corelib class, NULLABLE[?].)

intOrNil: value {INT32?}.
nameOrNil  ^{STRING?}.
|mapping| {DICTIONARY [STRING, STRING?]?}.
    "-If set, this maps a STRING key to a STRING or Nil value."

Thus, variables, arguments, and returns can be declared as non-Nil simply by omitting the question mark.

name: value {STRING}.
name  ^{STRING}.
|name| {STRING}.

This makes programmer intent visible at a glance, and it enables checking by the compiler. Non-nilable types cannot be assigned Nil, and to assign an equivalent Nilable type requires a typecast. However, for practical reasons, the compiler doesn't fully enforce this by requiring, for example, that an instance field is assigned before a constructor terminates. To do so would put an undue burden on the programmer. Nilable vs. non-Nilable reference types is a statement of intent, but a very useful one indeed!

Consider the library method which returns the extension of a file name, e.g. '.sab' from 'substituter.exe.sab'. Nil is not a valid file name, so it makes no sense for the method to accept it; and if the file has no extension, the result is an empty string, never Nil. SABLE codifies this by using non-Nilable types in the signature. However, when changing an extension, Nil indicates "no extension":

"System.IO.PATH staticMethod"extension: path {STRING}  ^{STRING}.
    {~ cilName: 'GetExtension'}
    "The extension for the file name in :path, including the leading period,
     or an empty string if the :path doesn't end with an extension."
change: path {STRING} extension: extension {STRING?}  ^{STRING}.
    {~ cilName: 'ChangeExtension'}
    ":path, with its file extension (if any) replaced with that in :extension.
     To obtain :path with its extension removed, use Nil for the :extension.
     (If :extension is empty, the :Result will end with a period.)"

Expressions of a Nilable type accept messages from the SABLE-added interface NILABLE[?], and the CLR class NULLABLE[?] implements that interface, gaining its methods. There are the obvious tests >#isNil and >#notNil, and these gems:

|stringOrNil| {STRING?}.
|stringNN| := stringOrNil ifNil: ['default value'].
stringOrNil ifNotNil: [:string | CONSOLE write: string size].
|size| := stringOrNil ifNotNil: [:string | string size] ifNil: [-1].

If you're sure an expression has a specific type, you cast it to that type and use appropriate operations, risking an INVALID_CAST_EXCEPTION if you're wrong. Similarly, if you're sure a Nilable expression cannot actually be Nil, use a typecast; if wrong, a NIL_REFERENCE_EXCEPTION results when you use it, just as in other languages. SABLE at least helps you clarify when expressions should and should not be Nil.

When you construct an object, obviously that object is not Nil, however, the expression can have a Nilable type. Similar to constructing an interface, you may construct a Nilable type, occasionally useful when initializing a declaration:

|mappingOrNil| := {DICTIONARY[STRING,EMPLOYEE]?} new.

Now, mappingOrNil is Nilable, but it currently holds an instance of {DICTIONARY_IMPL[STRING,EMPLOYEE]}.

Empty TypeRefs

SABLE strives to eliminate redundancy using type inference as we've seen with initialized declarations. Similarly, when assigning a variable or argument whose type is known, we can omit the type within TypeRefs. Consider assigning an instance field within a method. The class must declare the field's type. Must the method redundantly repeat the type? No.

MY_CLASS
    {~ object}
     {~ fields}
    |employeesByName| {DICTIONARY[STRING,EMPLOYEE]?}.
redundantAssignment.
    employeesByName := {DICTIONARY[STRING,EMPLOYEE]} new.
sableAssignment.
    employeesByName := {} new.

Of course, to get the full benefit of this feature, you must use meaningful variable names that cause the reader to recall their types. If there's any question, you can always provide the type explicitly.

We saw this feature used in SUBSTITUTION_TESTER. Since a substituter can change its mapping, the tester had to make a copy for the first of its two substituters. Sadly, dictionaries aren't CLONABLE, but they have a copy constructor used here:

|substituter| := {DOLLAR_SUBSTITUTER} newMapping: ({} newFrom: mapping).

Empty TypeRefs are useful for typecasts, too. When used for a known type, a typecast can omit its type.

|mappingAsObj| {OBJECT} := {DICTIONARY[STRING,STRING]} new.
|substituter| := {DOLLAR_SUBSTITUTER} newMapping: mappingAsObj ~{}.

Nilable/Non-Nilable Conversions

The two features above combine beautifully. According to the logic of your program, you may know that a Nilable expression has a non-Nil value. When the expected type is not known, an empty typecast means, make the type non-Nilable. Use this to send messages of the non-Nilable type, or simply to change the type.

|mappingOrNil| := {DICTIONARY[STRING,EMPLOYEE]?} new.
CONSOLE write: mappingOrNil ~{} size.
|mappingNN| := mappingOrNil ~{}.
CONSOLE write: mappingNN size.

As mentioned above, the reverse is atypical but possible, where a non-Nilable expression has a Nil value. This use-case for this is a field which is left uninitialized; it will be initialized lazily then heavily used when known to be non-Nil The code is cleaner if we typecast once to check for Nil, rather than typecast the field with every usage. For example:

COMPANY
    {~ object}
     {~ fields secret}
    |employees| {DICTIONARY[STRING,EMPLOYEE]}.
printEmployees.
     employees ~{?} ifNil: [employees := My defaultEmployees].
    employees isEmpty then:
        [CONSOLE writeLine: 'Yes, we have no employees (today).'].
    employees keysValuesDo:
        [:name :empl |
         CONSOLE writeLine: '{0}   ${1}' with: name with: empl salary boxed].

Typecast Request, Attempt, and Demand

Typecast expressions are used to create runtime type casts, to unbox value types, to convert between numeric types, and for some other purposes. They follow the expression being cast, and they have the same precedence as a unary message. The typecasts we've seen so far are more specifically called Typecast Requests. There are two additional forms of typecast which modify the standard typecast behavior:

    Receiver ~{TYPE}      "Typecast Request"
    Receiver ~?{TYPE}     "Typecast Attempt"
    Receiver ~!{TYPE}     "Typecast Demand"

Well skip all the details for now.