The Concurnas Programming Language

Concurnas is an open source JVM programming language designed for building reliable, scalable, high performance concurrent, distributed and parallel systems




Introducing Concurnas


Using Concurnas helps organizations take full advantage of modern multi-core CPU and GPU hardware by making concurrent and parallel programming easier.

Concurnas is interoperable with Java (and other JVM languages) - organizations are able to leverage existing Java software in a concurrent environment.

Compared to other languages, with Concurnas developers need to write fewer lines of code. That code is safer and easier to test, increasing quality and boosting developer productivity.


Concurnas Axioms


Easy to learn

Inspired by languages such as Python and Java, Concurnas is a statically typed, garbage collected language utilizing type inference with an easy to learn syntax.

High performance

Concurnas is a compiled language which runs on the Java Virtual Machine and as such has access to the incredible performance offered by the JVM

Easy to scale

Concurnas makes it possible to use the same code from a small research prototype to a planet scale distributed production solution

Open Source

Concurnas is an open source technology, free to use and modify. With ongoing development and commercial support provided by Concurnas Ltd


News


  • 2021-05-19 JAXLondon - Concurnas - a Modern JVM Language for Modern Software... Watch it...
  • 2021-02-07 jokerconf - Back from the 70s - the Concurnas concurrency model!... Watch it...
  • 2020-10-29 Concurnas at jokerconf this year... We're presenting Concurnas at jokerconf this year
  • 2020-06-22 Concurrent programming with Concurnas... in Oracle Java magazine
  • 2020-05-29 The Concurnas Programming Language online talk... Watch it...
  • 2020-05-26 Concurnas Q+A on InfoQ... read it here...
  • ...More News...

Concurnas in a Nutshell


Concurrent computing

Concurnas offers a simple but extremely powerful concurrency model which allows you to write concurrent code without having to write boilerplate code to manage threads, critical sections or locks!

Distributed computing

Concurnas offers first class citizen support for distributed computing as a specialization of concurrent computing

GPU computing

Concurnas offers first class citizen support for GPU programming. You can write your code in idiomatic Concurnas code and run on the GPU without having to switch to C/C++!

Off Heap Memory

Concurnas offers first class citizen support for interacting with non heap managed, non garbage collected memory. This makes working with GPUs, big data and custom databases a breeze.

Imperative

Concurnas supports all the usual constructs used in modern imperative computing such as: if-elif-else, for, while loops, assertions, exceptions. Plus implicit returns from blocks, with statements, list comprehension, copiers and much much more!

Object Oriented

Concurnas takes the hard work and boiler plate out of object oriented programming and makes it fun again. Concurnas has advanced support for classes (concrete, abstract, inheritance), generics (in out, wild cards, bounded), enumerations, annotations. As well as new features such as traits, tuples, object providers, typedefs, usage based generic type inference, automatic generation/ redirection of class field getters/setters, class hashcodes and equalities.

Functional

The functional programming paradigm incorporates a set of tremendously useful approaches to solving problems, but pure functional programming is rarely seen in the wild. With Concurnas we've taken the most widely used elements of functional programming, and integrated them into Concurnas. With Concurnas you have access to method and function references, lambdas, partial function evaluation, lazy evaluation, pattern matching plus a whole lot more!

Reactive

Concurnas has first class citizen support for the emerging reactive programming paradigm. With Concurnas you can write code which will react to changes in input parameters automatically - thus allowing you to create reactive pipelines. A very natural way of solving algorithmic problems.

Data structures

Concurnas has first class citizen support for core data structures including arrays, matrices, lists, maps, ranges and sets.

Vectorization

A technique popular in the scientific computing community, Concurnas allows you to apply an operator to all elements of an array/matrix/list via a one character operator

IDE support

Concurnas offers a standalone compiler, an interactive REPL shell, Jupyter Notebook, VS Code, Atom and Sublime text support.

Null safety

Concurnas is a null safe language, this helps to eliminate one of the most common sources of bugs in modern programming: the null pointer exception!

Java compatible

Concurnas runs on the Java Virtual Machine, which not only gives you access to the JDK and fantastic multi-platform performance offered by the JVM but also compatibility with other JVM languages such as Java, Scala, Kotlin and Clojure - plus you can easily integrate/use all your existing JVM code without modification.

Type inferred language

Concurnas is a statically typed language which uses type inference extensively to remove the burden of types. Often times Concurnas code is indistinguishable from Python code (widely recognized as one of the easiest languages to code in) but with the performance of Java!

A Concise syntax

Programming languages are not just for machines - people need to read them too! Concurnas offers a concise syntax and many shortcuts to make writing easy to read and maintainable code the norm. Concurnas is also able to be verbose, for code which requires that extra bit of clarity.

Extensible and DSLs

Concurnas has extensive support for Domain Specific Languages via; expression lists, extension functions and operator overloading. Concurnas also supports language extensions, which allow you to directly embed segments of non Concurnas code into your Concurnas code files.


A taste of Concurnas


Creating and using isolates
def gcd(x int, y int){//greatest common divisor of two integers
  while(y){
    x, y = y, x mod y
  }
  x
}

calc1 = gcd(8, 20)!//run this calculation in an isolate
calc2 = gcd(6, 45)!//run this calculation in a separate isolate

calc3 = calc1 if calc1 > calc2 else calc2
//^^^ wait for the results of calc1 and calc2 before assigning calc3

Concurnas allows you to easily execute computation, concurrently, in separate thread-like containers called isolates via use of the bang ! operator.

Concurrent computation is often recognized as one of the most difficult aspects of modern software engineering

Notice how concurrent computation is a one liner in Concurnas, there is no worry about creating/managing threads, thread pools, life-cycles, locks (or deadlocks!), critical sections, synchronization, wait, notifying threads etc. All this hard stuff is taken care of automatically - leaving you to focus on what's important.

Isolation of isolates
n = 10

nplusone  = { n += 1; n }!//perform this calculation in an isolate, a copy of n is made
nminusone = { n -= 1; n }!//another isolate, on a separate copy of n
//The above two isolates will never interfere with one another's copy of n

//nplusone == 11
//nminusone == 9
def complexFunc() {
  System.out.println("Hello world!")
}

complexFunc()!//execute concurrently in seperate isolate

All code in Concurnas is executed in isolates. Isolates are mapped onto the underlying hardware threads of the machine upon which one is running one's code. Isolates operate within their own dedicated memory space.

All state used in an isolate is copied with the exception of references, actors and some specially marked shareable classes. This isolation of state makes reasoning and designing concurrent algorithms easier since it reduces the need to code for synchronization and race conditions.

Refs and reactive computing
aRef int://this is a ref of type int
{//complex isolate executed concurrently eventually sets aRef...
  //complex code here...
  aRef = 10
}!

//aRef's current value is requested of it:
result int = aRef//If no value has yet been set execution will pause

System.out.println("The value of aRef is: " + result)
execount int: = 0
for(a in 1 to 10){
  //some complex calculation..
  execount++
}!//spawn an isolate to execute the above concurrently.

await(execount; execount==10)//pause further execution until execount == 10
aval int://no initial value set
bval int: = 22 //initial value known

sumvals int: = every(aval, bval) { aval + bval }
//after initializing above, execution is allowed to carry on...

aval = 100
bval = 200

//every block above will run concurrently and eventually set sumvals to:
await(sumvals; sumvals==300)

Refs allow us to share state between isolates in a controlled manner.

They are not copied between isolates, rather they are shared but provide controlled, atomic access to their changeable state. If a value is not yet set on a ref when it is requested, execution is paused and other isolates are allowed to execute until a value has been set, after which execution continues.

We can react to changes to refs via the following reactive programming constructs provided by Concurnas:

  • await Will pause execution until a value has been set on a ref and an optional guard condition has been satisfied
  • onchange Will trigger execution of its code block to occur concurrently upon any of its monitored refs being changed
  • every Behaves as onchange but will trigger if an initial value has been set on any monitored ref
Compact reactive computing
a int:
b int:

c <= a + b //same as: c int: = every(a, b) { a + b }
d <- a + b //same as: d int: = onchange(a, b) { a + b }

As with most syntactical elements of Concurnas, a concise version of the reactive programming constructs every and onchange is provided. This effectively turns reactive computing into a one line operation.

Transactions
account1 int: = 100
account2 int: = 100
onchange(account1, account2) {
  System.out.println("sum: {account1 + account2}")
}

trans{
  account1 -= 10
  account2 += 10
}

//will output: "sum: 200"

Concurnas supports software transactional memory via the trans block keyword. This allows us to make changes to one or more refs and have those changes visible outside of our transaction on an atomic basis.

Temporal programming
from com.concurnas.lang.pulsar import Pulsar, RealtimePulsar
from java.time import Duration

pulsar = new RealtimePulsar()

oneOffTrigger := pulsar.after(Duration.ofSeconds(10))//schedule event for 10 seconds time...
every(oneOffTrigger){
  System.out.println("Current time: " + oneOffTrigger)
}
//schedule event every 2 seconds 10 times...
repeatingTrigger := pulsar.repeat(Duration.ofSeconds(2), 10)
every(repeatingTrigger){
  System.out.println("Current time: " + repeatingTrigger)
}

A common pattern in concurrent systems engineering is to want to have some form of time based trigger, "wait 10 seconds then do x" or "do y every 3 seconds etc" or "perform this action at this certain time". At Concurnas we refer to this as temporal computing.

Temporal computing is supported via the built in Pulsar library found at: com.concurnas.lang.pulsar. This library allows us to schedule activities to take place in the future after a certain amount of time has elapsed. It also allows us to schedule tasks for future repetition.

Concurnas offers both a realtime implementation of pulsars and a 'Frozen'-time implementation (designed primarily for testing temporal applications), which permits time to be injected as a controlled variable.

Parfor
result = parfor(b in 0 to 10){
  gcd(b, 10-b)
}

//result == [10:, 1:, 2:, 1:, 2:, 5:, 2:, 1:, 2:, 1:, 10:]

Concurnas has first class citizen support for parallel for loops. These are a convenient and intuitive mechanism for performing task based operations in the context of a loop, in parallel. They have the same syntax as conventional loops in Concurnas.

Actors
actor MyActor{//lets define an actor
  -count = 0
  def increment(){
    count++
  }
}

//Use of the above actor:
ma = new MyActor()

ma.increment()//just like normal method call, will block until execution is complete

sync{//sync - wait for all isolates created in the block to complete
  {ma.increment()}!//called concurrently
  {ma.increment()}!
}

count = ma.getCount()
//count == 3, not possible with regular objects!

Actors in Concurnas enable us to build micro-services. Each actor operates within its own dedicated isolate. Actors look and behave just like regular objects from the perspective of the calling code.

Actors allow us to write service code in a single threaded style and be able to run them safely in a concurrent manner without having to concern ourselves with synchronization or race conditions as each call to an actor runs sequentially and has exclusive access to the sate of the actor until it completes. Naturally, because actors implement their own concurrency control they are safe to be shared without copying across multiple isolates.

Typed Actors
class MyCounter(-count int){//ordinary class
  def increment(){
    count++
  }
}

//We can now define and use our typed actor through the aid of the of keyword as follows:
actor MyActor of MyCounter(0)


//let's use our new typed actor:
ma = new MyActor()

ma.increment()

sync{//sync - wait for all isolates created in the block to complete
  ma.increment()!
  ma.increment()!
}

count = ma.count//count == 3

Sometimes we do not have the luxury of defining actors from scratch. This is particularly the case if we are working with pre existing code. To this end Concurnas provides typed actors. Typed actors construct their own and wrap around an instance of an ordinary object.

Any constructor used to create an instance of the class or method being wrapped is also provided by the typed actor.

Default Actors
class MyCounter(-count int){
  def increment(){
    count++
  }
}

inst1 = actor MyCounter(0)      //create a new actor on MyCounter
inst2 = new actor MyCounter(0)  //create a new actor on MyCounter

Another way to create a typed actor is to simply use the actor keyword in place of or in addition to the new keyword when creating an instance of an object (now and actor!).

Idiomatic Kernels
//a simple kernel for performing matrix multiplication...
gpukernel 2 matMult(M int, K int, 
      global in A float[2], global in B float[2], global out resultMat float[2]) {
  globalRow = get_global_id(0) 
  globalCol = get_global_id(1) 
  acc = 0.0f;
  for (k=0; k<K; k++) {
    acc += A[k*M + globalRow] * B[globalCol*K + k]
  }
  resultMat[globalCol*M + globalRow] = acc;
} 

Concurnas has first class citizen support for GPU computing. GPUs can be used like CPU co processors and through their use we're often able to improve the speed of computation for many compute bound problems by over 100x compared to conventional CPU based computation.

Central to GPU computing are kernels. These are executed on our GPUs in order to perform computation. Concurnas allows you to create kernels in a subset of the Concurnas programming language. This means we can write idiomatic Concurnas code with only slight modifications in order to perform computation on the GPU.

Moving Data
//create an array buffer on a GPU device
ar gpus.GPUBufferInput = device.makeOffHeapArrayMixed<int[]>(int[].class, 10)
//copy data from the heap into the buffer...
copyComplete boolean:gps.GPURef = ar.writeToBuffer([1 2 3 4 5 6 7 8 9 10])
//perform other computation, possibly on the GPU, whilst we wait for the copy to complete...
await(copyComplete)

//an input only buffer
ar gpus.GPUBufferInput = device.makeOffHeapArrayIn<int[]>(int[].class, 10)
//a 2 by 4 output matrix
mat gpus.GPUBufferOutput = device.makeOffHeapArrayOut<int[2]>(int[2].class, 2, 4)

When we examine algorithms to execute on the GPU, often a big factor in their design and usage concerns transference of data to and from the GPU. Concurnas offers dedicated support for this process.

Some of the most efficient GPU algorithms achieve their efficiency by implementing pipelining of data transference and computation together so to always keep the GPU busy doing useful work. Concurnas makes this easy by virtue of its native support for non blocking GPU operations such as data transference via refs.

Using existing code
//defining raw C99 compliant OpenCL C code:
@GPUStubFunction(source="float plus1(float a, float b){ return a + b;}")
gpudef plus1(a float, b float) float

//using existing C99 compliant OpenCL C code:
@GPUStubFunction(sources=['./cpucode.cl', './gpuutilCode.cl'])
gpudef plus2(a float, b float) float

Concurnas implements GPU computing by using OpenCL. This is advantageous because in addition to its multi vendor support (mainly Intel, Nvidia and AMD) one has access to a whole host of existing library code for GPU computation.

Concurnas allows for existing code to be used in one of two ways, either directly via a code string written in C99 compliant OpenCL C code or as a direct file reference in an annotation attached to a stub kernel.

Working with local memory
//a matrix multiplication algorithm leveraging GPU local memory
gpukernel 2 matMultLocal(M int, N int, K int, 
    constant A float[2], constant B float[2], global out result float[2]) {
  row = get_local_id(0)
  col = get_local_id(1)
  globalRow = CacheSize*get_group_id(0) + row //row of result (0..M)
  globalCol = CacheSize*get_group_id(1) + col //col of result (0..N)
	
  //local memory holding cache of CacheSize*CacheSize elements from A and B
  local cacheA = float[CacheSize, CacheSize]
  local cacheb = float[CacheSize, CacheSize]
	
  acc = 0.0f
	
  //loop over all tiles
  cacheSize int = K/CacheSize
  for (t=0; t<cacheSize; t++) {
    //cache a section of A and B from global memory
    //into local memory tiledRow = CacheSize*t + row
    tiledCol = CacheSize*t + col
    cacheA[col][row] = A[tiledCol*M + globalRow]
    cacheb[col][row] = B[globalCol*K + tiledRow]
		
    barrier(true)//ensure all work items finished caching
		
    for (k=0; k<CacheSize; k++) {//accumulate result for matrix subsections
      acc += cacheA[k][row] * cacheb[col][k]
    }
		
    barrier(true)//ensure all work items finished before moving on to next cache section
  }
  
  result[globalCol*M + globalRow] = acc
}

Concurnas has native idiomatic support for local GPU memory and barriers. Often algorithms with a strong degree of spatial locality benefit hugely in terms of performance when they are implemented with local memory on a GPU

Profiling
c1 := inGPU1.writeToBuffer([ 1 2 3 4 ])
copyduration = c1.getProfilingInfo().getWorkDuration()
summary = c1.getProfilingInfo().toString()				  		

The Concurnas GPU implementation provides detailed profiling options. This is useful for both development, in enabling one to debug one's GPU code so as to determine how much time is spent performing certain operations, and for monitoring purposes

Remote computation
//A remote server:
remServer = new com.concurnas.lang.dist.RemoteServer(port = 42001)
remServer.startServer()
//wait until time to terminate
remServer.close()
//A client:
rm = Remote('localhost', port = 42001)
//execute code remotely, returning a ref:
ans int: = {10+10}!(rm.onfailRetry())
rm.close()
//ans == 20

Concurnas provides an easy to use, intuitive and non invasive solution for distributed computing which acts as an extension of the isolate model of concurrent computation. In this way we are able to use the concurrent computing model which we are used to/have already built our software using and can easily extend this to working on a remote basis. In this way as our computational requirements grow we can easily scale our solutions to accommodate.

Concurnas abstracts away much of the hard work Concerning managing distributed computing, allowing us to focus on the more interesting problems which we are trying to solve. For instance, with Concurnas remote servers will cache results and wait for clients to reconnect in the event of network failure.

Automatic state and code distribution
//A remote server:
remServer = new com.concurnas.lang.dist.RemoteServer()//use default port 42000
remServer.startServer()
//wait until time to terminate
remServer.close()
class MyCalculator{//dependent code
  -callCounter = 0
  //greatest common divisor of two integers...
  def gcd(x int, y int){//+ counter and result
    while(y){
      (x, y) = (y, x mod y)
    }
    "count: {++callCounter}, result: {x}"
  }
}

//connect and execute remotely
rm = Remote('127.0.0.1')//use default port 42000
result = try{
  calc = MyCalculator()
  calc.gcd(34, 4756)

  //Dependant code (MyCalculator.class) and object state (callCounter) is also transmitted...
  res String: = {calc.gcd(97, 7081)}!(rm.onfailRetry())
  res:get()
}finally{
  rm.close()
}
//result == count: 2, result: 97

With Concurnas, remote servers are able to be configured such that they can run any code submit to them deemed safe for execution.

The internal Concurnas distributed computing API includes a provision for dependency resolution which itself is quite sophisticated and in the interests of performance is able to perform static code dependency analysis in order to determine and preemptively provide upfront the code that is required in order to execute a request.

In this way clients are able to submit any defined code and have this run on a remote machine with dependent code automatically distributed to the remote machine. This greatly simplifies the usually arduous process of remote code dependency distribution.

Dependant object state is also distributed for use with remote execution, rendering the execution semantic between local and remote computation identical. This makes it very easy for us to start building a solution locally, and to then later scale that solution up on a distributed platform.

Off Heap Stores
from com.concurnas.offheap.storage import OffHeapRAM
from com.concurnas.offheap import OffHeapObject	
msg = ["hello" "world"]//message to store

10meg = 10 * (1024**2)
//our off heap store:
offHeapStore = new OffHeapRAM<String[]>(10meg)
offHeapStore.start()

offHeapObj OffHeapObject<String[]> = offHeapStore.put(msg)//add msg to the store
gotMsg = offHeapObj.get()//extract a copy of the original message
del offHeapObj//remove the object from the store

offHeapStore.close()

assert gotMsg == msg//equal by value
assert gotMsg &<> msg//different by reference	  

Concurnas has support for off heap memory in the form of stores and maps. These allow us to work with datasets which are far greater than that which can be held within the managed garbage collected portion of memory used by Concurnas termed the heap. Additionally by using off heap memory we gain a tremendous amount of control over how we manage memory and can reserve portions of memory separate from garbage collection which is incredibly useful for high performance computing.

Objects are stored off heap in binary format. With Concurnas all objects are quickly serializable and deserializable to and from this format by default.

Concurnas presents two variants of off heap storage, for RAM and disk - for use contingent on the amount of data one is working with. The off heap disk variant is backed by memory mapped files which greatly enhances performance as spatially localized data is cached in memory.

Off Heap Maps
from com.concurnas.offheap.storage import OffHeapMapDisk
import java.io.File
msg = ["hello" "world"]
100meg = 100 * (1024**2)
//our off heap store:
offHeapStore = new OffHeapMapDisk<String, String[]>(100meg, new File('msgDatabase.db'))
offHeapStore.start()

offHeapStore.put('msg', msg)//add msg to the store
gotMsg = offHeapStore.get('msg')//extract a copy of the original message
offHeapStore.close()//msg is persisted to disk in 'msgDatabase.db'

assert gotMsg == msg//equal by value
assert gotMsg &<> msg//different by reference			

The Key-Value pair map has become an industry standard too for persisting data. With Concurnas support is provided in the form of off heap maps. Both a RAM and disk backed variant of these maps is provided.

The off heap map disk variant is backed by memory mapped files which greatly enhances performance as spatially localized data is cached in memory. Additionally, the disk based variant is capable of long term persistence of data.

Schema evolution
//Removing a field:
class MyClass(firstName String, sirName String)
//Later version:
class MyClass(firstName String)
//Adding a field:
class MyClass(firstName String)
//Later version:
class MyClass(firstName String, sirName String)
//Changing the type of a field:
class MyClass(firstName String, sirName String, userid byte)
//Later version:
class MyClass(firstName String, sirName String, userid long)

One of the strongest points of the Concurnas off heap map implementation is its support for schema evolution. We term schema evolution as changes to a classes fields after it has been persisted.

Schema evolution turns out to be a surprisingly normal operation performed in enterprise computing. Traditionally this would cause a problem for us upon deserialization since the persisted version of the class code would not match that of the current 'live' version, but Concurnas is largely able to account for these sorts of evolutionary changes including:

  • Removal of a field
  • Adding a new field
  • Changing the type of a field

Concurnas is able to account for the above changes to object schemas when serializing and deserializing data from persistent storage. This saves us the headache of explicit migration.

Arrays and Matrices
empty = new int[10]//empty array of zeros
ones = new int[10](1)//array of ones

matrix = new int[2, 2]
ndim = new int[2, 2, 2]//a 2 x 2 x 2 3 dimensional array
mones = new int[2, 2](1)//2 x 2 matrix of ones

ar1 = [1 2 3 4 5]//in place array definition
ar2 = ar1 + [ 6 7 8 ]//concatenation: ar2 ==  [1 2 3 4 5 6 7 8]

m1 = [1 2; 3 4]//in place matrix definition

//concatenation:
m2 = m1 + [ 5 6] //m2 == [1 2; 3 4; 5 6]
m3 = [m1 m1]     //m3 == [1 2 1 2 ; 3 4 3 4]

//extracting values
arraGot = ones[1]//getting the second value from an array
got = m2[0,1]//got == 2
got2 = m2[;,1]//taking a slice from a matrix: got2 == [2 4 6]

Concurnas has advanced first class citizen support for not just defining arrays and matrices but working with them to.

A concatenation operator + is provided to aid in joining of n dimensional arrays and matrices as well as various means to extract values.

This functionality is particularly useful for scientists and engineers performing to numerical/scientific computing with matrices

Lists, maps and ranges
//lists
alist = [1, 2, 3, 4, 5, 6, 7, 8, 10]//a list of Integers
objList = ['element', 2, 3.4]//a mixed list of Objects

//operations on lists...
element = alist[0]//get the first item
del alist[0]//remove first item
check = 3 in alist//check to see if a value is in a list
sublist = alist[4 ... 8]//sublist
sublists = alist[4 ... ], alist[ ... 4]//sublist from index 4 to end, and from start to 4

if(alist){//checking for non emptyness
  System.out.println('list is not null or empty!')
}

for(item in alist; idx){//iteration
  System.out.println('item:{idx}={item}')
}
//maps
amap = {12 -> 'twelve', 1 -> 'one'}
defaultmap = {6 -> 20, default -> 0}
got = amap[12]//get an item
got2 = defaultmap[16]//an entry from 16 -> 0 will be created by default
del amap[12]//remove an item
amap[99] ='big'//create a mapping
check = 99 in amap//check for presence of key
check = 23 not in amap//check for absence of key

if(amap){//checking for non emptyness
  System.out.println('map is not null or empty!')
}

for(key in amap; idx){//iteration over map keys
  System.out.println('item:{key}={amap[key]}')
}

//sets
aset = set()
aset.add('item')
aset.add('toremove')
check = 'item' in aset

if(aset){//checking for non emptyness
	System.out.println('set is not null or empty!')
}

for(item in aset){//iteration
	System.out.println('item:{item}')
}
//ranges
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
for(x in 0 to 10){
  System.out.println('sequence item:{x}')
}

Some of the most useful and commonly used data structures are lists, maps, sets and numerical ranges. Concurnas has built in first class citizen support for these essential data structures.

With Concurnas you can define lists, maps and ranges in place with convenient, dedicated syntax.

Concurnas supports the most commonly used operations on these data structures directly, such as adding, removing and checking for the presence of elements

Implied generic types
//implied type lists
mylist = list()//mylist is implied to be a String list based on usage
mylist.add('element')

//implied type map
mymap = map()//implied to be a map of Integer to String
mymap[1] = 'value'

//implied type set
aset = set()//implied to be a set of Integers
aset.add(123)

With Concurnas you can define lists, maps and sets with simple Python like compact syntax

Concurnas leverages the Java JDK implementation of these data structures. They make use of generics in order to support convenient multi typing of structures. Where these generics types are unqualified like here, Concurnas employs usage based type inference in order to infer what these generic types should be qualified as at compilation time based on the usage of the data structure.

List comprehension
mylist = [1, 2, 5, 4, 3, 2]

plusone = x+1 for x in mylist//list comprehension of a list
plusoneEven = x+1 for x in mylist if x mod 2 == 0 //only even numbers!

myeq = (x**2+1) for x in 0 to 10//list comprehension of a range

mymap = {0 -> 93, 1-> 55, 4 -> 34}
lckeys = (key, mymap[keyfor]) for key in sorted(mymap)
//iterate over our sorted map keys and produce a list of tuples 

Iterations over lists, or in fact any iterable data structure, is a very common operation to perform when building both general systems and those which specifically work with data. As such Concurnas has first class citizen support by way of convenient syntax for performing these iterations termed: list comprehensions.

List comprehension can be applied to any iterable object, matrix or array. This of course includes ranges and maps in addition to lists

Vectorizable expressions
myArray1 = [1 2 3 4 5 6 7 8 9 10]
myArray2 = [1 2 3 4 5 6 7 8 9 10]

plus10 = myArray1^ + 10//create new array with 10 added to each element of myArray1
myArray2^ += 10//add 10 to each element of the array in place

assortment = ['aString' Integer(1) Float(22) 'anotherString']
whichStr = assortment^ is String //type checking. whichStr == [true false false true]

mymatrix = [1 2; 3 4]
mymatrix^ **=2//mymatrix now == [1 4 ; 9 16]

Concurnas offers syntax to support Vectorization. This allows us to succinctly and easily write code which will be applied to each element of an array, list, matrix or other n dimensional array.

All major operators are supported.

Element wise method invocation
 class Inca(public value int){
  def inc() => value++;;
  override toString() => 'Inca({value})'
}

incs = [Inca(3) Inca(4) Inca(22)]
incs^inc()//call inc on each element, incs now == [Inca(4) Inca(5) Inca(23)]
values = incs^value//extract values from each object, values == [4 5 23]

In addition to operators, vectorization extends to method invocation and field access.

Chained vectorization
from java.lang.Math import sin, toRadians
mymat = [ 80 170; 260 350]
sins = sin(toRadians((mymat^ + 10) mod 360))

Concurnas allows vectorized elements to be chained together, enabling for some very elegant, intuitive solutions to common array/list/matrix problems

Implicit vectorization
myArray1 = [1 2 3 4 5 6 7 8 9 10]
myArray2 = myArray1 + 10//no ^ needed!

from java.lang.Math import sin, toRadians
mymat = [ 80 170; 260 350]
sins = sin(toRadians((mymat + 10) mod 360))//no ^ needed!

Concurnas is able to infer where vectorization is required to make sense of a program

Classes
open class Person(~name String, ~surname String){
  //Concurnas automatically creates a this(name String, surname String) constructor
  likes = java.util.HashSet<Food>()//field defaults to be accessible from within class only
  def addLike(like Food) boolean => likes.add(like)//a public method
}//open classes can be extended

//an enumeration, a simple class structure for limited choices
enum Food{Tomatoes, Beans, Bread, Grapes, Pizza}

//an abstract class may define methods which need to be implicated by concrete classes
//abstract classes themselves may not have instance objects created of them
abstract class Validating(~name String, ~surname String) < Person(name, surname){
  def validateLike(like Food) boolean//abstract method to be defined by subclass

  override addLike(like Food) boolean {//methods must be explicitly overridden
    super.addLike(like) if validateLike(like) else false
  }
}

//BornPerson extends Validating, pass relevant details to superclass...
class BornPerson(name String, surname String, ~birthYear short) < Validating(name, surname){
  this(surname String, birthYear short){
    this('unknown', surname, birthYear)//call default constructor
  }

  //those born after 1980 don't like fast food...
  def validateLike(like Food) boolean {
    like <> Food.FastFood if birthYear > 1980 else true
  }
}//this is a closed class which may not be extended

//creating an instance object of a BornPerson...
aperson = BornPerson('dave', 'jones', 1991)//new keyword is optional
aperson.addLike(Food.Beans)

Concurnas takes the hard work and boiler plate out of object oriented programming and makes it fun again. It's easy to define classes in Concurnas as the syntax has been designed with speed of creation and readability in mind. Data centric classes can be defined in as little as one line of code.

Classes in Concurnas support single inheritance. This is a really useful way of providing evermore specialist behavior within one's subclasses whilst inheriting the functionality of the extended super class.

Concurnas offers support for abstract classes and methods. These are a really useful way of providing some common functionality across a set of related subclasses extending the abstract class, whilst also requiring the subclasses to have their own definitions of certain abstract methods

Concurnas provides enumerations. These are really useful for representing variables which can only take one of a small range of pre-defined values

Methods and fields in Concurnas have sensible default accessibilities. Unless otherwise defined, fields are protected (accessible by the defining class and any extending children classes), and methods are public (accessible anywhere). All classes in Concurnas are 'closed' by default, that is to say they do not permit extension unless explicitly declared as being 'open' classes. All abstract classes are open by default.

Traits
trait Eater{
  def eat() String => "eating {favouriteFood()}" //concrete method
  def favouriteFood() String //abstract method to be implemented by composing class
}

trait Animal < Eater{
  override def eat() String => "animal is " + super.eat()
  def legCount() int//abstract
}

trait FourLegged ~ Animal{
  def legCount() => 4
  def favouriteFood() String
}

//~ indicates we're composting a class of the defined traits...
class Dog ~ Animal, FourLegged {
  def favouriteFood() => "sausages"
}

fido = new Dog()
fido.eat()//eat returns: "animal is eating sausages"

Traits allow us to define elements of reusable functionality (both methods and state) which can be mixed-in to classes. They are themselves not insatiable as, like abstract classes, they represent incomplete fragments of functionality. Unlike abstract classes however, a class may be composed of (aka mixed-in with) more than one trait. Traits are useful as they encourage and facilitate software creation via composition as opposed to inheritance, which is generally understood as best practice in modern object oriented programming.

When composing classes with traits, multiple traits may be referenced, unlike inheritance.

abstract class Operator{
  def operate(a int) int
}

open class ID < Operator{
  def operate(a int) => a
}

trait PlusOne   < Operator{  def operate(a int) => super.operate(a+1) }
trait Square    < Operator{  def operate(a int) => super.operate(a**2) }
trait MinusOne  < Operator{  def operate(a int) => super.operate(a-1) }
trait DivTwo    < Operator{  def operate(a int) => super.operate(a/2) }

//create a class composed of these stacked traits...
class DMSP < ID ~ PlusOne, Square, MinusOne, DivTwo
DMSP().operate(10)//returns: 17

Traits may be stacked together. The structure of the super references is contingent on the composition order of our defined class (in this case DMSP - divide by two, then take away one, multiply by itself and add one to the result). This is really neat as in one line of code, by switching around the composed traits here we have access to 4! (4*3*2) => 24 variants of our trait functionality.

Generics
open class Pair<X, Y>(-x X, -y Y)//generic class with two type parameters, X and Y

//instances of our generic class
pp1 = new Pair<String, int>("one", 1)
pp2 = new Pair<String, String>("name", "another")

//a generic function with one type parameter X:
def addTwice<X>(addTo java.util.List<X>, what X){
  addTo.add(what)
  addTo.add(what)
}

//a class with upper bounded generic parameters
class NumericalPair<A Number, B Number>(a A, b B) < Pair<A, B>(a, b)
np1 = new NumericalPair<float, int>(0.3f, 1)//A and B must be subtypes of Number

//We can use the wildcard ? where we are not interested in the generic qualification
ppAny = np1 as NumericalPair<?, ?>

//in/out parameters allow us to support Contravariance and Covariance respectfully
trait Source<out T>{//Contravariant generic parameter
  def nextT() T
}

trait Comparable<in T>{//Covariant generic parameter
  def compareTo(other T) int
}

Concurnas supports generics. These are an extremely useful aspect of modern object oriented programming that enable types (classes and traits) to be parameters when defining classes, traits, methods and functions. They enables us to re-use code across different types, which greatly improves the speed at which we can write code, it's readability and innate safety.

Classes, actors, traits, methods and functions can have generic parameters. These must be qualified when making use of these elements

Languages which support Generics on their own can be quite limited, but Concurnas supports wild cards, in/out generics and bounded generics These features greatly improve the usability of generics

//pp1 is inferred to be of type: Pair<String, int>
pp1 = new Pair("one", 1)

//filenames is inferred to be of type: java.util.ArrayList<String>
filenames = java.util.ArrayList()
filenames.add("pic1.jpg")

In writing idiomatic Concurnas code, Concurnas is usually able to infer the qualification of generic types for a class, function etc having generic parameters.

This is achieved examining both the passed parameters to a generic class constructor and also how an instance object is used with respect to those generic parameters.

Getters and setters
class Person(-name String)//- generate a getter for name only
  ~petName String //generate a getter and setter
  +age int//generate a setter only
}

//redirect field access to getters and setters
p1 = new Person('dave')
p1.petName = "lenin"//equivalent to p1.setPetName("lenin")
petname = p1.petName//equivalent to petname = p1.getPetName()

Getters and setters are an essential part of object orientation since they allow us to implementing encapsulation. Concurnas will automatically create setters and getters for all your class fields where designated. A field need only be prefixed with one of:

  • - Generate a getter only
  • ~ Generate a setter and a getter
  • + Generate a setter only
Furthermore, Concurnas will automatically redirect field access operations to getters and setters, whether they are created automatically or manually.

Auto hashCode/equals
class Person(name String)
p1 = Person('dave')
p2 = Person('dave')

assert p1 == p2//equivalent to; p1.equals(p2) - p1 and p2 have the same value
assert p1 &<> p2//p1 and p2 are not the same object

people = set()
people.add(p1)
people.add(p2)

assert people.size() == 1//one item stored by value

Concurnas will automatically generate a hashCode and equals method for every class. Further more, the default equality check in Concurnas, a==b automatically resolves to a.equals(b) for all objects. These two features allow us to easily implement equality by value, as opposed to equality by reference (which most languages provide).

In providing a default hashCode method in addition to a default equals method we are able to store objects in sets and as keys in maps efficiently on a by value basis

The automatically generated default hashCode and equals methods are able to cope with cycles in object graphs.

Annotations
class MyClass1{
  @Deprecated def myOldMethod(){
      //The Deprecated indicates that the method is deprecated
  }
}

annotation MapsTo{//a custom annotation
  name String
  mapTo String
  repeat = 1//annotation field default value
}

class MyClass2{
  @MapsTo(name = "mappingName", mapTo = "anotherName")
  afield int = 99//afield is annotated with MapsTo
}

Concurnas has extensive support for annotations. These are a cool mechanism by which one can augment code for the compiler or other code related tools to make use of.

Chained method calls
class Robot{
  -path = ""
  private def add(dir String) => path +=dir
  def up() 	=> add("U") 
  def down()  => add("D") 
  def left()  => add("L") 
  def right() => add("R") 
}

robot = Robot()
robot..up()..up()..left()..down()..right()//double dot .. returns the Robot object
result = robot.path//UULDR

Concurnas allows us to chain together series of method calls on objects which do not explicitly return references to themselves upon invocation through the use of the double dot .. operator.

With blocks
class Robot{
  -path = ""
  private def add(dir String) => path +=dir
  def up() 	=> add("U") 
  def down()  => add("D") 
  def left()  => add("L") 
  def right() => add("R") 
}


result = with(Robot()){
  up(); up(); left(); down(); right()
  path
}//result == UULDR

with blocks in Concurnas enable us to define blocks of code in which all method calls and variable access is first attempted against an associated object

Dependency Injection
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

Concurnas offers first class citizen support for dependency injection through Object Providers. Dependency Injection (DI) is a modern software engineering technique for building object oriented systems. The core principle of DI is to separate behavior from dependency resolution (the "plumbing"). In doing so one is obliged to compartmentalize one's software into core functionality, making dependent behavior injectable and thus separating that injectable code from the "plumbing". This has benefits of aiding:

  • Reasoning - By compartmentalizing one's software, it makes reasoning about code easier.
  • Testing - DI facilitates the use of mock implementations when we are testing software which allows that testing to take place in isolation, side effect free and with controlled inputs and outputs which we can validate against.
  • Re-usability - By reducing our software into core functionality we are generally more able to re use that functionality within and outside of the original systems we set out to build.

For the above reasons and more it's clear why DI has become such a useful tool for modern software development. Concurnas object providers build upon the successes of the likes of Spring and Google Guice

Concurnas object providers offer a non invasive and concise mechanism by which code may be both marked for dependency injection (via the inject keyword), configured (via object provider's) and provided for further execution with all dependencies (including transitive) resolved. Best of all, this all happens in idiomatic Concurnas code and is validated at compile time!

Scoped Providers
inject class AgeHolder(age Integer)
inject class User(name String, ah AgeHolder)

provider UserProvider{
  single provide User//we mark the provider as single
  String => "freddie"
  AgeHolder => new AgeHolder(22)
}

up = new UserProvider()
inst1 = up.User()
inst2 = up.User()

//both User objects are the same across different calls to UserProvider.User:
assert inst1 &== inst2
class Bean{
  count = 0
  def increment() void => count++ 
}

inject class BeanCounter(-left Bean, -right Bean)

provider CounterProvider{
  provide BeanCounter
  shared Bean => new Bean()//we mark the Bean dependency qualifier as being shared
}

bcProvider = new CounterProvider()
bcInst1 = bcProvider.BeanCounter()
bcInst2 = bcProvider.BeanCounter()

//same bean for both dependencies of the provided object:
assert bcInst1.left &== bcInst1.right
//two beans from two separate invocations of the provider differ:
assert bcInst2.left &<> bcInst1.left

Concurnas offers three scopes for providing objects and satisfying dependencies.

  • 1). By default all provided objects/injected dependencies created by a provider are instance objects.
  • 2). The single scope can be attached to a provide statement or dependency qualification in order to indicate that only one instance of said object should be created per object provider instance.
  • 3). The shared scope can be attached to a provide statement or dependency qualification in order to indicate that only one instance of said object should be created per object provider invocation of a provide statement.
Named dependencies
inject class User(firstName String, sirName String)

provider UserProvider{
  provide User
  'firstName' String => "Freddie"
  'sirName' String => "Brown"
}

uprov = new UserProvider()
user = uprov.User()

Dependency qualifiers may specify a parameter name string to which they will bind their dependencies. This further specializes what dependency they qualify. This is particularly useful in instances where we need to qualify a dependency of the same type but used for different purposes.

Nullable Types
aVariable String = "a String"
aVariable = null//this is a compile time error!

nullableVar String? = "a String"//nullableVar can hold a null value
nullableVar = null//this is now acceptable

len = nullableVar.length()//compile time error! As nullableVar may be null

Concurnas, like other modern programming languages such as Kotlin and Swift has null safety built into its type system. Concurnas requires that a variable's type be explicitly declared as being nullable in order for it to be assigned null, or potentially null value. When working with nullable types, Concurnas enforces a number of restrictions so as to avoid the risk of throwing a NullPointerException. In this way Concurnas enables us to write safe code which is guaranteed to be largely free of NullPointerException's (an unexpected NullPointerException being widely recognized as being one of the most common bugs in modern software engineering).

Checking for null
nullableVar String? = "a String"//nullableVar can hold a null value
//...
res = if(nullableVar <> null){
  nullableVar.length()//OK as we've already established that nullableVar is not null
}else{
  -1
}

Concurnas includes sophisticated logic for working with nullable types to cater for situations in which they have already been established as being not null. This makes working with nullable types easy.

Null Safe Operators
//Safe call operator:
nullableVar String? = null
len = nullableVar?.length()//len is of type Integer?
//Elvis operator
nullableVar String? = null
len int = nullableVar?.length()?:-1//len is of type int
//Not null assertion operator
nullableString String? = null
//throws a NullPointerException if nullableString is null:
length int = nullableString??.length()

Concurnas includes three useful built in operators that can be used when working with nullable types:

The safe call operator. This will execute normally as if it were applied to a non nullable type, except for when it's applied to null - in which case it shall return null.

The Elvis operator. This will execute normally as if it were applied to a non nullable type, except for when it's applied to null - in which case it shall execute and return the expression on the right hand side of the operator. Often used in conjunction with the safe call operator.

The not null assertion operator. This is our final and most dangerous tool for working with nullable types. This operator will deliberately throw a NullPointerException if it is applied to a nullable type of value null, otherwise shall execute normally.

Nullable Generics
class HolderNoNullGeneric<X>(~x X)
class HolderNullGenericOK<X?>(~x X)

nonNull = HolderNoNullGeneric<String>("not null")
nullOK  = HolderNullGenericOK<String?>(null)

Concurnas allows generic type declarations to be declared as being nullable. By default all generic types are not nullable.

Unicode
π = Math.PI
πStr = "π=" + π //πStr == "π=3.141592653589793"
你好 = "Hello!"

Concurnas is a Unicode language. As such variables, method names, Strings etc are expressible in Unicode.

This can be particularly advantageous when working with mathematical constants. This helps to reduce the cognitive gap between the domain in which ones problem exists and the programming language used to instruct ones computer.

All blocks can return
res1 = {a = 12; b= 16
a ** 2 + b}//anonymous block returns a value

//branching if, elif, else blocks may return values
res2 = if(someCondition()){ "value1"} else{"value2"}

//loops return values
fromLoop java.util.List<int> = for(a in 0 to 10){
  a * 2 + 12
}

All code blocks in Concurnas have the capacity to return a value. This is an incredibly useful feature which allows us to write concise, easy to read programs.

This functionality extends to looping control structures including the for and while loops.

Compact classes and functions
//a data class in one line...
class MyClass<X>(-x heldValue, public afield String, ~another int)

//an ordinary function:
def addfunc1(a int, b int) int {
  return a + b
}

//we can use => to compact the function to:
def addfunc2(a int, b int) int => return a + b

//infer the return type:
def addfunc3(a int, b int) => return a + b

//implicit return expression:
def addfunc4(a int, b int) => a + b//most compact form!

Most data oriented classes, will fields, a default constructor and full getter and setter support can be created in as little as one line of code.

When it comes to methods and functions, these two are often definable in one line of code.

Concurnas offers two really useful features which make creating methods and functions an enjoyable experience: function return type inference means that we do not have to explicitly define what the return type of a function is, and implicit function returns mean that it's not necessary to explicitly use the return keyword to return a value from a function or method.

This combined provides us with an incredible amount of flexibility in the way in which we defined code. For simple, obvious cases, we can use the succinct, compact syntax. For more complex cases where more elaboration for the sake of readability is required, a more terse definition can be applied. The decision is given to the programmer, as opposed to it being a language restriction.

Method overloading, named Parameters, default values and varargs
//an overloaded function
def adder(a int, b int) => a + b
def adder(a int, b float) => adder(a, b as int)
def adder(a int) => adder(a, 10)

//a default value
def powerPlus(a int, raiseTo = 2, c int) => a ** raiseTo + c

//call our function with a default value
res1 = powerPlus(4, 10)//second argument defaults to '2'
res2 = powerPlus(4, 3, 10)//second argument provided

//calling a function with named parameters:
def powerAdder(a int, raiseATo = 2, b int, raiseBTo = 2) => a**raiseATo + b**raiseBTo
res3 = powerAdder(2, 4, raiseATo=3)//equivalent to: powerAdder(2, 3, 4, 2)

//varargs:
def sum(elms int...) int {
  res = 0
  for(elm in elms){
    res += elm
  }
  res
}

//call our function with a vararg
thesum = sum(1, 2, 3, 4, 5)

Concurnas offers support for function/method overloading, invocation with name parameters, default values and varargs. Default values are a nice way to overload functions without needing explicitly overload them. Named parameters are a nice convenient way to invoke functions, particularly where many default parameters are involved. varargs are really handy for cases where you wish to support a non restricted number of input arguments to process

Typedefs
//fully defined typedef
typedef People = java.util.ArrayList<String>

//partially defined typedef
typedef NameMap<X> = java.util.ArrayList<java.util.HashMap<String, java.util.HashSet<X>>>

//using typedefs...
nm NameMap<String>= new NameMap<String>()
//above is equivalent to:
//nm java.util.ArrayList<java.util.HashMap<String, java.util.HashSet<String>>> = new java.util.ArrayList<java.util.HashMap<String, java.util.HashSet<String>>>()

Concurnas provides typedefs which offer a convenient way of writing class names, when used as types or as constructors, in shorthand form. This is particularly helpful when one is working with fully or partially qualified generic types.

Tuples
def returnPair(a int, b int) (int, int) => a*b, a+10

obtained (int, int) = returnPair(10, 13)

r1, r2 = obtained//tuple decomposition, r1 and r2 can be used as ordinary variables

Concurnas supports tuples, these are a convenient way to move groups of data around a program and offer an alternative to defining classes specifically for fulfilling this purpose. Tuples have a fixed number of elements and are immutable.

Copying
class MyClass(a int, b int, c String){
  override toString() => 'MyClass({a}, {b}, "{c}")'
}

mc1 = MyClass(12, 14, "hi there")
mc2 = mc1@ //copy mc1

assert mc1 == mc2//same values!
assert mc1 &<> mc2//different objects!

mc3 = mc1@(a = 100)//copy mc1 but overwrite value of a
assert 'MyClass(100, 14, "hi there")' == mc3.toString()

mc4 = mc1@(<a, b>)//copy mc1 but exclude a and b
assert 'MyClass(0, 0, "hi there")' == mc4.toString()

Copying objects in conventional languages can be a real chore, not so in Concurnas! Concurnas offers the copy operator @ in postfix position. This offers a convenient sub language for copying object hierarchies in an efficient way.

All objects in Concurnas have, by default, a deep copier provided implicitly which will handle the copying of all elements of an Object's graph.

The copy operator even caters for cases where object graphs contain loops.

Exceptions
class ArgumentException(msg String) < Exception(msg)

def process(a int) int {
  if(a < 2){
    throw new ArgumentException("a is smaller than 2")
  }
  return a ** 2
}

result = try{
  process(1)
}catch(e ArgumentException){
  //handle exception as appropriate
  0 //return a default value
}catch(e){
  //unexpected exception, handle as appropriate
  throw e//re-throw
}

Exceptions in Concurnas are unchecked exceptions - i.e. one does not have to catch them. The view being that exceptions are meant to be 'exceptional'. As such one is not obliged to catch exceptions, and one is discouraged from using exceptions in order to communicate the normal operational state of a program - alternative mechanism to indicate this, such as using return values are widely recognized as being best practice. However, for exceptional cases, exceptions are provided.

Try with resources
def readFirstLineFromFile(path String)  {
  from java.io import FileReader, BufferedReader
  try ( br = new BufferedReader(new FileReader(path))) {
    br.readLine();
  }//br will now be closed automatically
}

Concurnas supports try with resources, this enables us to automatically tidy up/close resources such as disk I/O files etc once we have finished using them, saving us the effort of having to tidy them up ourselves and potentially having a bug in forgetting to do so.

Pattern matching
//factorial
def factorial(i int) int {
    match(i){
        0 => 1
        n => n * factorial(n-1)
    }
}

//a simple example
def simpleMatcher(x int){	
  match(x){
    0 => "zero"
    1 => "one"
    2 => "two"
    else => "other"
  }
}

//matcher for numbers with complex conditions
def advancedMatcher(a int){
  match(a){
    10 => "ten"
    ==9 => "explicit 9"
    2 or 3 => "or case"
    >2 and >5 and <==10 => "gt 5"
    >3 and <==10 => "gt three not ten"
    >11 => "gt to 11"
    else => "something else" + a
  }
}

//match operating across differing types
def machOnDifferingTypes(a Object){
  match(a){
    x int; x == 2 => "small"
    x String; x == "hello" => "said hi"
    else => "else: "+a
  }
}

//match based on object contents...
class Person(-yearOfBirth int, -name String)
def objContentsMatch(an Object){
  match(an){
    person Person(yearOfBirth < 1970) => "Person. Born: {person.yearOfBirth}"
    x => "unknown input"
  }
}

Concurnas has extensive support for pattern matching. Inspired by languages such as Haskel and Scala this feature of functional programming allows us to easily implement complex branching logic which otherwise would require a complex and difficult to maintain web of if-elif-else statements.

Lambdas and method references
def powPlus(x int, r int, c int) => x**r + c //a normal function

mref (int, int, int) int = powPlus&//create a method reference to powPlus

def takesMethodRef(toCall (int, int, int) int) int{
  toCall(2, 2, 3)//call a method reference
}

//partially evaluated method reference:
partial (int) int = powPlus&(2, 2, int)

//a lambda definition with fully defined arguments:
alambda (int, int) int = def(a int, b int) int { a + b}

//anonymous lambdas:
//a function taking a function reference and applying it to each input argument
def map<X>(inputs list<X>, func (X) X) {
  func(x) for x in inputs
}

//we define a lambda as a => a+10 - this is equivalent to: def(a int) int { a + 10 }
mapped = map([1, 2, 3], a => a+10)

//We define a SAM (Single access method) type:
trait Doer<X>{
  def doOperation(a X) X
}

//a function taking the above SAM type and applying it to each input argument
def SAMmap<X>(inputs list<X>, func Doer) {
  func.doOperation(x) for x in inputs
}

//here our lambda a => a+10, gets converted into the SAM type: Doer
mapped = SAMmap([1, 2, 3], a => a+10)

Another great feature of functional programming is lambdas, method references and partial evaluation. Concurnas offers support for all of these.

Method references and lambdas can be passed around like ordinary objects (because under the hood they are objects). They offer a handy way to pass around functionality as data in our programs.

Concurnas is able to perform type inference of lambda arguments and automatically map to SAM-types (Single Access Method types). This is really useful for being able to create concise programs for working with data.

Lazy variables
lazy slowValue = {sum = 0L; for(x in (1 to 1000000)){ sum += x}; sum}//long time to calc

result = slowValue if (java.util.Random().nextInt() < 0) else "not calculated"

Concurnas has first class citizen support for lazy evaluation of variables.

Like most modern programming languages, Concurnas ordinarily supports eager evaluation of variable assignment. I.e the expression on the right hand size of an assignment is evaluated before its result is assigned to a variable. With lazy evaluation this evaluation is differed until the variable is accessed. After which the value is usable as with the usual eager evaluation

Easy to use
from com.mycompany.myproduct.langs using mylisp, myFortran, myAPL

calc = mylisp||(+ 1 2 (* 2 3))|| // == 9

myFortran || program hello
          		print *, "Hello World!"
       		 end program hello|| //prints "Hello World!"

lotto = myAPL || x[⍋x←6?40] || //6 unique random numbers from 1 to 40

Concurnas has first class citizen support for language extensions.

Language extensions are an incredibly powerful feature of Concurnas which enable guest code to be written in languages other than Concurnas. Language extensions reduce the amount of code one must write and reduce the distance between the problem domain one is operating in and Concurnas, by allowing us to use the most appropriate language for the task at hand.

Concurnas language extensions operate entirely at compilation time and so have no negative impact upon performance, furthermore, as with Concurnas in general, compile time checking of code is employed, which greatly reduces the likelihood of an error at runtime, thus increasing the reliability of software written.

Seamlessly integrate
from com.mycompany.myproduct.langs using mylisp, mySQL, myAPL
class Person(name String, yearOfBirth int)
people list<Person>;
//populate our list of people...

//select from our list of people variable defined above...
millennials = mySQL||select name from people where yearOfBirth between 1980 and 2000||

//create a new function, fact for performing factorial
myAPL||fact{×/⍳⍵}||
fact(10)//use of function defined above. Returns: 3628800

Concurnas Language extensions seamlessly integrate with idiomatic Concurnas code and may be used at any point in a Concurnas program.

Language extensions may use and produce any component of Concurnas syntax, such as variables, methods, classes etc

Easy to implement
from com.mycompany.myproduct.langs using mylisp

aString = "i'm a String!"
invalidCode = mylisp||(+ 1 2 (* 2 aString))||
//results in compilation time error: line 4:35 mylisp: Expected numerical type, not 'String'

moreInvalidCode = mylisp||(+ 1 2 (* 2 3)|| //oops! Missing a closing ')'
//results in compilation time error: line 7:41 mylisp: Parser error, expected ')' symbol

Concurnas Language extensions are easy to create and greatly reduce the amount of work a language implementor must perform in order to bring a language into production.

Concurnas Language extensions are required to output valid Concurnas code in the form of a String. This Concurnas code is then processed as par normal Concurnas code in subsequent compilation phases. One need focus only upon the 'front-end' aspects of language development, i.e. tokenization, parsing and semantic analysis (which tend to be where the most value add resides), leaving the hard work on code generation etc to the core Concurnas compiler.

Concurnas Language extensions provide an elegant and concise API for language integration (to access variables in scope, types etc), which supports streamline interrogation of the core Concurnas compiler and a convenient mechanism for reporting errors back to the code author as par normal Concurnas compilation.

Operator overloading
class Complex(real double, imag double){
  def +(other Complex) => new Complex(this.real + other.real, this.imag + other.imag)
  def +=(other Complex) => this.real += other.real;  this.imag += other.imag
  override toString() => "Complex({real}, {imag})"
}

c1 = Complex(2, 3)
c2 = c1@//deep copy of c1
c3 = Complex(3, 4)

result1 = c1 + c3
c2 += c3 //compound plus assignment

//result1 == Complex(5.0, 7.0)
//c2 == Complex(5.0, 7.0)

Concurnas is the perfect language for building your next Domain Specific Language (DSL). Concurnas provides operator overloading which allows you to apply most of the operators provided by Concurnas to user defined types.

Operator overloading reduces the amount of code one would otherwise be required to write and reduces the distance between the problem domain one is operating in, and the underlying language one is programming in.

Extension functions
def String repeat(n int) String {//this is an extension function
  return String.join(", ", java.util.Collections.nCopies(n, this))
}

res = "hi".repeat(2) //creates a reference
def int meg() = this * 1024L * 1024L

12.meg() //-> 12582912L

Concurnas has support for extension functions. These allow for functionality to be added to classes without needing to interact with the class hierarchy (e.g.extending the class etc). They are a convenient alternative to having to use utility functions/methods/classes which take an instance of a class and often permit a more natural way of interacting with objects.

Extension functions may be defined on any type including primitive types

Expression lists
class Myclass(b int){
  def resolve(a int) => (a+b)*2
}

res = Myclass 4 resolve 4 //basic expression list, res == 16
from java.time import Duration, LocalDateTime

enum Currency{ GBP, USD, EUR, JPY }
enum OrderType{Buy, Sell}

class CcyAmount(amount long, ccy Currency){
  override toString() => "{amount} {ccy}"
}

abstract class Order(type OrderType, ccyamount CcyAmount){
  when LocalDateTime?
		
  def after(dur Duration){
    when = LocalDateTime.now()+ dur
  }
	
  override toString() => "{type} {ccyamount} at {when}"
}
class Buy(ccyamount CcyAmount) < Order(OrderType.Buy, ccyamount)
class Sell(ccyamount CcyAmount) < Order(OrderType.Sell, ccyamount)
//extension functions
def long gbp() => CcyAmount(this, Currency.GBP)
def int mil() => this*1000000L
def int seconds() => Duration.ofSeconds(this)


///////////Expression list:
order = Buy 1 mil gbp after 10 seconds
	
orderStr = "" + order
//orderStr == Buy 1000000 GBP at 2018-02-15T06:32:11.099

Concurnas provides expression lists, this is a neat feature which enables a more natural way of writing expression related code that would otherwise have to be written as a set of chained together calls using the dot operator and/or function invocation brackets.

Expression lists may be combined with operator overloading and extension functions to achieve some very impressive results. Using this feature one can create very succinct and readable DSLs


Supported IDE's


Standalone

Concc Compiler

Jupyter

VS Code

Sublime Text


Join our Mailing List