Concurnas vs Java - Part 3


In this article we complete our examination of the differences between Concurnas and Java.

This final article carries on from the previous article's here (part 1) and here (part 2) in the series.

Working with Data

Concurnas has been designed with numerical computing and data science in mind. It has advanced syntactical support for arrays, matrices and other common data structures on a first class basis.

Working with Arrays

We can create arrays and matrices in Java as follows:

int[] anArray = new int[] {1, 2, 3};
int[][] aMatrix = new int[][]{ {1, 2, 3}, {4, 5, 6} };

In Concurnas this is a far more succinct affair:

anArray = [1 2 3]
aMatrix = [1 2 3 ; 4 5 6]

Concurnas offers us a tremendous amount of syntax for working with arrays and matrices. For instance, we can concatenate arrays and matrices as follows:

anArray = [ 1 2 3 ]
appended = [ anArray 34 ] //appended ==> 1 2 3 34

mat = [ 1 2 ; 3 4]
mat2 = [mat mat] //mat2 == [1 2 1 2; 3 4 3 4]

In Java this is a more complex ordeal, the solution to the above is harder to comprehend and must be specifically tailored to solving the above problem (unless we wish to sacrifice a lot of performance):

int[] anArray = new int[] {1, 2, 3};
int[] appended = new int[4];

System.arraycopy(anArray, 0, appended, 0, 3);
appended[3]=34;

int[][] mat = new int[][]{ {1,2}, {3, 4} };
int[][] mat2 = new int[2][4];

System.arraycopy(mat[0], 0, mat2[0], 0, 2);
System.arraycopy(mat[0], 0, mat2[0], 2, 2);
System.arraycopy(mat[1], 0, mat2[1], 0, 2);
System.arraycopy(mat[1], 0, mat2[1], 2, 2);

Working with Sets, Lists and Maps

Let's try to create some common data structures in Java:

java.util.HashMap<String, Integer> mymap = new java.util.HashMap<String, Integer>();
mymap.put("one", 1);
mymap.put("two", 2);
mymap.put("three", 3);


java.util.ArrayList<Integer> aList = new java.util.ArrayList<Integer>();
aList.append(1);
aList.append(2);
aList.append(3);
aList.append(4);
aList.append(5);

This is far easier in Concurnas as maps, lists and sets have first class citizen support:

mymap = {"one" -> 1, "two" -> 2, "three" -> 3}
aList = [1,2,3,4,5]

When using these data structures in Java we often write code like the following:

boolean cont = mymap.containsKey("one")

java.util.ArrayList<Integer> longNames = new java.util.ArrayList<Integer>();
for(String key : mymap.keySet()){
    if(key.length() > 3){
        longNames.append(mymap.get(key));
    }
}

mymap.remove("one")

This is substantially easier in Concurnas:

cont = "one" in mymap
longNames = mymap[key] for key in mymap if key.length() > 3
del mymap["one"]

List comprehensions

Working with iterations over sequences of data is a very common problem in software engineering. As such one will often see code such as the following in Java:

java.util.ArrayList<Integer> myList = new java.util.ArrayList<Integer>();
myList.add(12);
myList.add(15);
myList.add(18);

java.util.ArrayList<Integer> ret = new java.util.ArrayList<Integer>(myList);
for(int item : myList) {
    if(item % 2 == 0){//only even
        ret.add(item + 10);
    }
}

Concurnas offers list comprehensions which make the above code much easier to read:

myList = [12, 15, 18]
ret = i+10 for i in myList if i mod 2 == 0

Vectorization

Vectorization is a nice shortcut for performing the same operation on each element of an array, matrix (or n dimensional array) or list. Java lacks this functionality. We can replace the following Java code:

int[][] mat = new int[][]{ {1,2}, {3, 4} };
int[][] mat2 = new int[2][2];

for(int n = 0; n < mat.length; n++) {
    int[] row = mat[n];
    for(int m = 0; m < row.length; m++) {
        int item = row[m];
        mat2[n][m] = item*2 + 1;
    }
}
//mat2 => [[3, 5], [7, 9]]

With the following in Concurnas:

mat = [1 2 ; 3 4]
mat2 = mat^*2 + 1 //mat2 ==> [3 5 ; 7 9]

This is dramatically simpler.

We can perform an in place vectorized operation as follows:

mat = [1 2 ; 3 4]
mat^^*2 + 1 //mat ==> [3 5 ; 7 9]

In fact, vectorization may often be implicit:

mat = [1 2 ; 3 4]
mat2 = mat*2 + 1 //mat2 ==> [3 5 ; 7 9]

Ranges

Concurnas has built in support for ranges. In Java if we wish to reverse iterate from 100 to 0 inclusive in steps of 2 we would have to write code as follows:

int a = 100;
while(a >= 0){
    //do something
    a-=2
}

In Concurnas we can work with far simpler code:

for(a in 100 to 0 step 2)
    //do something
}

In fact the range support in Concurnas is very advanced, the following is all easily possible:

numRange = 0 to 10           //a range of: [0, ..., 10]
tepRange = 0 to 10 step 2    //a range of: [0, 2, ..., 10]
revRange = tepRange reversed //a reversed range of: [10, 8, ..., 0]
decRange = 10 to 0 step 2    //a range of: [10, 8, ..., 0]
infRange = 0 to              //an infinite sequence [0,... ]
steInfRa = 0 to step 2       //a stepped infinite sequence [0, 2,... ]
decInfRa = 0 to step -1      //a stepped infinitely decreasing sequence [0, -1,... ]

check = 2 in numRange //checking for presence

Tuples

Let's say we wish to return more than one value from a function. Here is how we have to do it in Java:

public static class NameAndAge{
    public final String name;
    public final int age;
    public NameAndAge(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public static NameAndAge getDetails() {
    return new NameAndAge("dave", 27);
}

And here is the equivalent in Concurnas, leveraging the power of tuples:

def getDetails() => ("dave", 27)

They are also handy syntactically for implementing algorithms:

def gcd(x int, y int){//greatest common divisor of two integers
    while(y){
        (x, y) = (y, x mod y)//tuple decomposition
    }
    x
}

Dependency Injection

Concurnas has first class citizen support for dependency injection via "Object providers". This is a nice software engineering tool which can be harnessed to easily create complete object graphs with the relevant dependencies injected with a declarative syntax:

trait MessageGetter {public def getMessage() String}
trait MessageSender{public def sendMessage(msg String) void}

inject class MessageProcessor(obtainer MessageGetter, sender MessageSender){
//the default constructor for MessageProcessor is marked as being injectable
  public def processMessage(){
    this.sender.sendMessage(this.obtainer.getMessage())
  }
}

class SimpleMG ~ MessageGetter { 
  def getMessage() String => 'A message'
}
class MessagePrinter ~ MessageSender{
  def sendMessage(msg String) void => System.out.println(msg)
}

provider MPProvider{//
  provide MessageProcessor //provide objects of this type
  MessageGetter => new SimpleMG()//dependency satisfaction for MessageProcessor 
  MessageSender => new MessagePrinter()//dependency satisfaction for MessageProcessor
}

//to be used as follows:
mpProvider = new MPProvider()
mp = mpProvider.MessageProcessor()
//mp is a MessageProcessor instance with all dependencies satisfied

Java has no first class citizen support for dependency injection. Third party libraries such as Spring, Google Guice etc must be used to achieve the above.

Domain Specific Languages

Domain specific languages, or DSL's for short can be very easily supported in Concurnas. This is made possible through a number of features, none of which can be found in Java.

Operator Overloading

Operator overloading essentially allows us to overload the function of the native +, -, ** etc operators for use with custom objects of our choosing. For instance, if we wished to support Complex numbers we could do so as follows:

class Complex(real double, imag double){
    override toString() => ip = "+" if imag >== 0 else ""; "{real}{ip}{imag}i"
    def +(\comp Complex) => Complex(\comp.real + this.real, \comp.imag + this.imag)
    def -(\comp Complex) => Complex(this.real-\comp.real, this.imag-\comp.imag)
}

Complex numbers may be used like this:

res = Complex(1.3, 9.2) + Complex(14.3, 9.2)
//res ==> 15.6+18.4i

In contrast, Java does not provide a means of implementing operator overloading, so we'd be obliged to stick with the following, less intuitive, syntax:

Complex res = new Complex(1.3, 9.2).plus(new Complex(14.3, 9.2))

Expression lists

Expression lists are a nice feature of Concurnas which enables developers to essentially remove the dots . and function invocation parenthesis () from chains of function and method invocations. This can lead to code which reads closer to natural language. Here is an example:

def plus(a int, b int) = > a + b

res = plus 12 24
//res ==> 36

This is not a feature of Java.

Extension functions

Ever wanted add a method to a class but didn't want to use a helper function, subclass the holding class, write a wrapper or otherwise change the classes design? Extension functions offer a nice solution:

def String repeat(times int){
    sb = StringBuilder();
    while(times-- > 0){
        sb.append(this)
    }
    sb.toString()
}

res = "hi".repeat(5)//res = hihihihihi

Combined with expression lists they are also great for offering infix functions:

def int addtwice(a int) => this + a * 2

res = 12 addtwice 24
//res ==> 60

Operators may even be overloaded using extension functions:

def String *(times int){
    sb = StringBuilder();
    while(times-- > 0){
        sb.append(this)
    }
    sb.toString()
}

res = "hi" * 5//res = hihihihihi

Extension functions are not a feature of Java.

Exceptions

Concurnas takes the view that exceptions are meant to be exceptional. Whilst Concurnas provides all the infrastructure to catch exceptions - usage is optional, exceptions don't have to be caught. This is in contrast to Java where exceptions must be explicitly marked as being unchecked by extending RuntimeException for them to be optionally catchable, otherwise they must be caught.

Language extensions

Sometimes it is useful to use and embed other programming languages in our main body of code. Not only does this integrate more tightly with our development work flow (in not having to switch between programming environments to access the features of different languages) and simplify our build process, but it allows us to write safer more reliable code which is syntactically and semantically checked at compilation time. Concurnas permits this via "Language Extensions". Consider the following snippet of sql embedded within Concurnas code:

using com.mylanguages import sql
results = sql || select name from people where yob < 1970 ||

The best we could do in Java is capture the above as a String for a runtime library to process.

Making use of Concurnas

In summary, Concurnas offers a significant number of changes when compared to vanilla Java. Java is not a bad language but Concurnas is a better, more concise and expressive language for most modern software engineering tasks. Using Concurnas results in developers having to write less code leading to an increase in productivity, fewer bugs and overall higher quality software which lasts longer and yields greater return on investment.