There's a common pattern in software libraries, which is the conditional result. For example, a language may have an iterator type, something that has a method that returns a type T:
T next();
Now, if there is no "next item" to return, the iterator could return some other value, like -1 or null. Of course, in some cases, -1 and null are perfectly valid values, so this design turns out to be a fairly poor one.
Instead, the iterator type may have to represent the "no next item" as a separate method, which must be called separately:
interface Iterator<T> { // note the ugly brace placement
boolean hasNext();
T next();
}
Now the caller can iterate like:
while (iter.hasNext()) {
T value = iter.next();
}
This could be simplified if a language supported more than one return value, like most languages support more than one parameter to a method:
(boolean, T) next();
The problem is what to return as a value when the boolean value is false. You could return a null value, but the cost to doing this is one billion dollars (payable to Tony Hoare).
Ecstasy addresses this common challenge by using conditional return values. For example:
The next() method is allowed to return True and an element, or False if there is no element. Consuming a conditional method is incredibly simple; here's a helper methods on the Iterator interface itself:
There's one other thing, the underlying reason that the next() method can't return a "null", and that is because Ecstasy corrects the billion dollar mistake:
That one line is the actual code that defines the concept of Null in Ecstasy. And here's what it means:
Object o = Null; // ok ... Null is an Object
String s = Null; // error
Int i = Null; // error
Object[] a = Null; // error
In other words, Null is an object just like "Hello world!" is an object, and all of the type rules apply to Null just like they apply to any other object. (Among other benefits, you can kiss those NullPointerExceptions goodbye.)
It really is that simple.
T next();
Now, if there is no "next item" to return, the iterator could return some other value, like -1 or null. Of course, in some cases, -1 and null are perfectly valid values, so this design turns out to be a fairly poor one.
Instead, the iterator type may have to represent the "no next item" as a separate method, which must be called separately:
interface Iterator<T> { // note the ugly brace placement
boolean hasNext();
T next();
}
Now the caller can iterate like:
while (iter.hasNext()) {
T value = iter.next();
}
This could be simplified if a language supported more than one return value, like most languages support more than one parameter to a method:
(boolean, T) next();
The problem is what to return as a value when the boolean value is false. You could return a null value, but the cost to doing this is one billion dollars (payable to Tony Hoare).
Ecstasy addresses this common challenge by using conditional return values. For example:
/**
* An iterator over a sequence of elements.
*/
interface Iterator<Element>
{
/**
* Get a next element.
*
* @return a tuple of (true, nextValue) or (false) if no elements are available
*/
conditional Element next();
}
The next() method is allowed to return True and an element, or False if there is no element. Consuming a conditional method is incredibly simple; here's a helper methods on the Iterator interface itself:
/**
* Perform the specified action for all remaining elements in the iterator, allowing for
* a possibility to stop the iteration at any time.
*
* @param process an action to perform on each element; if the action returns true, the
* iterator is considered "short-circuited", the method returns immediately
* and no more elements are iterated over
*
* @return true iff the iterator was short-circuited; otherwise false if the iteration
* completed without short-circuiting
*/
Boolean untilAny(function Boolean process(Element))
{
while (Element value := next())
{
if (process(value))
{
return True;
}
}
return False;
}
There's one other thing, the underlying reason that the next() method can't return a "null", and that is because Ecstasy corrects the billion dollar mistake:
/**
* The Nullable type is the only type that can contain the value Null.
*
* Nullable is an Enumeration whose only value is the singleton enum value {@code Null}.
*/
enum Nullable { Null }
That one line is the actual code that defines the concept of Null in Ecstasy. And here's what it means:
Object o = Null; // ok ... Null is an Object
String s = Null; // error
Int i = Null; // error
Object[] a = Null; // error
In other words, Null is an object just like "Hello world!" is an object, and all of the type rules apply to Null just like they apply to any other object. (Among other benefits, you can kiss those NullPointerExceptions goodbye.)
It really is that simple.
Does this offer any advantage over algebraic union types like scala's Option or Haskell's Maybe? If this is a separate type-system facility in a language that already has union types, isn't this redundant?
ReplyDeleteI believe that it serves a similar purpose. I have not used either Scala or Haskell in anger, so I cannot speak to advantages per se, only preferences. The reason in Ecstasy for the conditional return type (which is actually not itself a type, but a convention of types that is understood and respected by both the compiler and code verifier) is to simplify syntax without introducing the possibility of type error, and to do so without requiring nullability.
ReplyDelete