2020/01/22

Coming from Java

The first question that we get from new developers working on Ecstasy is how it is similar to, and how it is different from the languages that they already know and use. One of the goals of Ecstasy was to make the language instantly accessible to programmers who already were comfortable with any of the C family of languages, such as C++, Java, and C#. We'll start by looking at one such language, Java, which is one of the most widely used languages today.

(This topic is large, so this entry is just the first installment.)

Here's the pocket translation guide from Java to Ecstasy with respect to the type system:
  • Java's type system is a combination of primitive (machine) types and class-based types, with a few "hybrid" types, such as arrays, that fit neither category. Ecstasy's type system is simply class-based; there are no primitive types.
  • Java's null type has one value, null, that is assignment compatible with any reference type. The Ecstasy Nullable enumeration defines the value Null, although the lower-case null is also supported by alias. In Ecstasy, the Null enum value is only assignable to a Nullable type, or a super-type thereof such as Object.
  • Java's boolean type has two values, true and false. The Ecstasy Boolean enumeration defines the values False and True, although the lower-case false and true are also supported by alias.
  • Java's char type is a 2-byte unsigned integer that represents a common sub-set of Unicode characters. Ecstasy's Char class represents any Unicode code-point.
  • Java's int type is a 32-bit unchecked signed integer value; Java additionally has byte, short and long types for 8-bit, 16-bit, and 64-bit unchecked signed integer values. Ecstasy provides both checked and unchecked, and both signed and unsigned implementations for 8-bit, 16-bit, 32-bit, 64-bit, 128-bit, and variable-length integers (conceptually similar to Java's BigInteger class). For example, UInt32 is a checked unsigned 32-bit integer, and @Unchecked Int128 is an unchecked signed 128-bit integer. Additionally, the alias Int maps to the 64-bit signed integer, Int64, and the alias Byte maps to the 8-bit unsigned integer, UInt8. (In Java, the byte type is signed.)
  • Java also has some proprietary support for decimal values via the BigDecimal class. Ecstasy provides standard 32-bit, 64-bit, 128-bit, and variable-length decimal value support via the Dec32, Dec64, Dec128, and VarDec classes; these are implementations of the IEEE 754-2008 standard for decimal floating point.
  • Java's float and double represent 32-bit and 64-bit IEEE 754 binary floating point values.  Ecstasy provides standard 16-bit, 32-bit, 64-bit, 128-bit, and variable-length IEEE 754 binary floating point values via the Float16, Float32, Float64, Float128, and VarFloat classes. Additionally, Ecstasy provides the ML- and AI-optimized "brain float 16"  type, via the BFloat16 class.
  • Java's primitive type system is based on a 32-bit word size. Ecstasy's does not have a primitive type system, and thus does not have a "word size", but in practice, Ecstasy defaults to using 64-bit integer, decimal, and binary floating point values.
  • In Java, the value "0" is an int. The compiler converts it, if necessary, to other types. In Ecstasy, the value "0" is an IntLiteral, which has the ability (both at compile-time and run-time) to convert to any numeric type. Unlike Java, there is no need for an "l"/"L" suffix on integers to inform the compiler that a value is a long.
  • In Java, the value "0.0" is a double. In Ecstasy, the value "0.0" is an FPLiteral, which has the ability (both at compile-time and run-time) to convert to any decimal or binary floating point type. Unlike Java, there is no need for an "f"/"F" or "d"/"D" suffix to inform the compiler that a value is a 32-bit or 64-bit value.
  • Java supports the class, enum, and interface keywords for declaring classes. Ecstasy supports these three keywords, plus: module, package, service, mixin, and typedef.
  • Classes such as Int64, Float64, Dec64, Char, and String that are used to hold constant values are implemented in Ecstasy using the const keyword instead of the class keyword. Instances of a const class are automatically made immutable as part of their construction; specifically, no reference to an object of a const class becomes visible until after the object is made immutable.
  • Ecstasy classes such as Nullable and Boolean are enumerations; enumerations are abstract classes that contain enum values, such as False and True. Enum values are singleton const classes.
  • Ecstasy module and package classes are singleton const classes, and are written like any other classes would be. Declarative modularity was recently introduced into Java via Project Jigsaw, with some similar goals. You can read more about Ecstasy modules on this blog.
  • Java does not have any language capabilities similar to a service, a mixin, or a typedef in Ecstasy. A service class provides a boundary for concurrent and/or asynchronous behavior, so it can be thought of in the same manner as a Java thread; however, an Ecstasy application may have millions of service objects, while it is unlikely that so many threads would be desirable in any language. An Ecstasy mixin provides cross-cutting functionality; in Java, some combination of boilerplate, delegation, and cut & paste would be used instead. An Ecstasy typedef is a means to provide a name to a type that itself can be expressed using the type algebra of the Ecstasy language. You can read more about class composition on this blog.
To put this into practice, consider this Java example:
package com.mycompany.myproduct.gui;

class Point
        implements Comparable<Point>
    {
    public Point(int x, int y)
        {
        this.x = x;
        this.y = y;
        }

    private final int x;
    private final int y;

    public int getX()
        {
        return x;
        }

    public int getY()
        {
        return y;
        }

    @Override
    public int hashCode()
        {
        return x ^ y;
        }

    @Override
    public boolean equals(Object obj)
        {
        if (obj instanceof Point)
            {
            Point that = (Point) obj;
            return this.x == that.x && this.y == that.y;
            }

        return false;
        }

    @Override
    public String toString()
        {
        return "Point{x=" + x + ", y=" + y + "}";
        }

    @Override
    public int compareTo(Point that)
        {
        int n = this.x - that.x;
        if (n == 0)
            {
            n = this.y - that.y;
            }
        return n;
        }
    }
And here is the corresponding Ecstasy code:
const Point(Int x, Int y);
This particular example is dramatic, because the const class declaration in Ecstasy implies automatic implementations of the Comparable, Hashable, Orderable, and Stringable interfaces. Furthermore, the parameters specified at the class level declare two properties, and a constructor.

Local variable declarations are similar, but the use of the comma as a general purpose separator (as in C) is not permitted. For example, in Java:
int a=0, b=0, c=0;
In Ecstasy, these would likely become separate declarations:
Int a=0;
Int b=0;
Int c=0;
It is also possible (and occasionally necessary) to declare and initialize multiple left-hand-side variable ("L-values"); the above example could be written as:
(Int a, Int b, Int c) = (0, 0, 0);
Note that the left-hand-side is in the form of a tuple, and the right-hand-side has a corresponding tuple type. In this form, the type of each left-hand-side variable can differ, and a type is only specified when declaring a variable. For example, if a function "foo()" exists that returns both an Int and a String, then the above-defined variable "c" and a new String variable can be assigned as follows:
(c, String d) = foo();
This introduces a dramatic difference in Ecstasy: Methods and functions can return more than one value, and those return values can be treated either as individual values, or as a tuple of values.

Furthermore, method and function parameters can also be provided either as individual values, or as a tuple of values, or as named values. Consider this example in Java that uses multiple delegating constructors:
class ErrorList
    {
    public ErrorList(int maxErrors)
        {
        this(maxErrors, false);
        }

    public ErrorList(boolean abortOnError)
        {
        this(0, abortOnError);
        }

    public Example(int max, boolean abortOnError)
        {
        this.max = max;
        this.abortOnError = abortOnError;
        // ...        
        }

    private int max;
    private boolean abortOnError;
    }
Using default parameter values, the Ecstasy equivalent of this example would not need all of those redundant constructors, each with slightly different signatures:
class ErrorList(Int max=0, Boolean abortOnError=False)
    {
    // ...    
    }
And the class could then be constructed using any combination of named parameters, as in the following example:
ErrorList errs = new ErrorList(abortOnError=True);
For the most part, though, the Ecstasy syntax is designed to maintain a high level of compatibility with Java (and C#) syntax. One area in which the syntax differs is with respect to type assertions and type tests. In Java, the type test uses the relational operator "instanceof", and the type assertion uses the C-style cast syntax, which often requires two sets of parenthesis, as in this Java code:
if (x instanceof List)
    {
    ((List) x).add(item);
    }
Ecstasy simplifies this syntax dramatically by employing the dot notation that is already so naturally used for property access and method invocation. The "is" keyword replaces "instanceof", and the "as" keyword replaces the awkward use of parenthesis for the type assertion (aka "type casting"):
if (x.is(List))
    {
    x.as(List).add(item);
    }
This approach is far easier to read, because it follows left-to-right, with no precedence concerns. Furthermore, if the compiler determines that the value "x" is not subject to concurrent modification, then type inference obviates the need for the type-assertion altogether:
if (x.is(List))
    {
    x.add(item);
    }
Operator precedence also differs slightly from Java, in order to simplify more operators into left-to-right ordering and to resolve a number of cases in which parenthesis were awkwardly required in Java:
Operator        Description             Level   Associativity       
--------------  ----------------------  -----   -------------       
&               reference-of              1                         
                                                                    
++              post-increment            2     left to right       
--              post-decrement                                      
()              invoke a method                                     
[]              access array element                                
?               conditional                                         
.               access object member                                
.new            postfix object creation                             
.as             postfix type assertion                              
.is             postfix type comparison                             
                                                                    
++              pre-increment             3     right to left       
--              pre-decrement                                       
+               unary plus                                          
-               unary minus                                         
!               logical NOT                                         
~               bitwise NOT                                         
                                                                    
?:              conditional elvis         4     right to left       
                                                                    
*               multiplicative            5     left to right       
/                                                                   
%               (modulo)                                            
/%              (divide with remainder)                             
                                                                    
+               additive                  6     left to right       
-                                                                   
                                                                    
<< >>           bitwise                   7     left to right       
>>>                                                                 
&                                                                   
^                                                                   
|                                                                   
                                                                    
..              range/interval            8     left to right       
                                                                    
<  <=           relational                9     left to right       
>  >=                                                               
<=>             order ("star-trek")                                 
                                                                    
==              equality                 10     left to right       
!=                                                                  
                                                                    
&&              conditional AND          11     left to right       
                                                                    
^^              conditional XOR          12     left to right       
||              conditional OR                                      
                                                                    
? :             conditional ternary      13     right to left       
                                                                    
:               conditional ELSE         14     right to left       
As you can see, a number of operators are grouped together, which previously each had their own precedence level; this implicitly employs left-to-right precedence for all operators within that grouping. Bitwise operators also have been moved to a significantly higher precedence level, which reduces the need for unnecessarily awkward parenthesization. Additionally, almost all operators map directly to methods, which means that explicit left-to-right behavior can be achieved by replacing a relational operator with the corresponding method invocation.

(Continue to Part II.)

1 comment:

  1. Nice to read this article. Very interesting

    ReplyDelete

All comments are subject to the Ecstasy code of conduct. To reduce spam, comments on old posts are queued for review before being published.