loop

Contents

Introduction

Welcome to Loop! Thanks for checking out my little research project. This is the programmer's manual, you should be able to see an index of contents on your right.

Enjoy your stay.

A quick note: Please remember that while there are hundreds of tests covering the language runtime, Loop is still a young project and there may be errors you run into along the way--especially with the shell. Any help in reporting them (on the Mailing List) or fixing them (by submitting patches) is greatly appreciated.

Philosophy

As a programming language, Loop prizes readable, compact and elegant syntax. Many design choices are made in favor of clarity over convenience.

Its syntax is strongly influenced by Haskell, Scheme, Ruby and Erlang.

Loop also emphasizes performance over purity or theoretical considerations. This means that practical design choices generally rule the day. One of the key motivations behind running on the JVM is the vast array of existing libraries that are instantly available; and of course the outstanding performance of a VM techonology developed over decades, and runs easily on multiple platforms.

Programs (or scripts) are compiled on-the-fly to optimized JVM bytecode and therefore suffer no performance penalty to interpretation; all while maintaining the quick, edit-and-run responsiveness of interpreted code.

The overall philosophy is to bring together the best features of functional programming with the practical and ease-of-use lessons from modern imperative and OO languages, but to do so in a consistent, pragmatic and elegant form.

Sometimes new languages feel like an endless agglomeration of features, a collection of niche features, or just plain bolted-on, even if they have excellent ideas. Loop's balance many new ideas with performance considerations and features are designed with a comprehensive view right from the start. For example, concurrency is built right in to the language, with consistent design choices across the spectrum of features (e.g. immutable types, software transactional memory).

Being functional in nature, Loop doesn't have any of the baggage of the host platform (Java), but interoperates tightly and borrows semantically from Java where appropriate.

Loop is currently a work-in-progress, I invite you to come and play.

Getting started

You will need Java 1.6 or higher already installed. Check if you have it by running:

$ ~java -version~
Java version "1.6.0_31"
Java(TM) SE Runtime Environment
Java HotSpot(TM) 64-Bit Server VM

Download and unzip the Loop distribution. If using a *-nix operating system (Unix, Linux or Mac OS), in a terminal run:

$ ~tar zxvf loop.tar.gz~
$ cd loop

From the "loop" directory (created after unzipping), run:

$ ~./loop~

this will bring up the Loop shell, from where you can execute simple expressions and functions

loOp (http://looplang.org)
     by Dhanji R. Prasanna

>> ~1 + 2 + 4~
7
>> ~alert('hi there') ~
>> :help

Hint: :help brings up a card of helpful hints

If you want to run Loop from other directories, add loop to the PATH. If using the bash shell:

$ export PATH=$PATH:/path/to/loop
$ loop

Note: make sure you replace /path/to with the actual path =)

You can make this permanent by adding the aforementioned export line to .bash_profile located in your home directory.

Note: On Windows or on platforms where the loop shell script does not work for some reason, you can directly invoke the runtime from the loop directory:

$ java -jar loop.jar

Building from source

If you prefer to build Loop yourself you will need Maven 3 installed. First clone down the git repo, then run the following maven command:

$ git clone https://github.com/dhanji/loop.git
$ mvn clean package assembly:single

This will produce a self-contained JAR in target/ called loop-with-dependencies-1.0.jar, or something like that. You can rename this to loop.jar and launch it with the loop shell script by setting the LOOP_HOME environment variable appropriately.

My first loop

Like all other decent things, Loop programs are stored in their own files. You can recognize a Loop program file by its extension .loop. They can be executed either from the shell (using :run) or directly from the command line:

$ ~./loop myfirst.loop~
hello there

This snippet above looks for a program file called myfirst.loop and runs it. You can create and save this file in any text editor, it contains nothing more than the following:

print('hello there')

Give this a try now. You can print other things too:

print(1 + 2)
print(5 * 50 / 44)
print(new java.util.Date())

These are called free expressions and may appear anywhere inside a loop function or program. Specifically, the function print is being called with different values.

As you will learn soon, everything in Loop is either a function or a value.

You should be able to run all of the example code in this documentation via this method (Loop program files) or via the Loop shell itself. I recommend starting with the shell and moving to program files when working with more complex examples--particularly when declaring your own functions.

Evaluation

So far you have seen how small computations and function calls work in loop. these are known as expressions, and form the basic building blocks of all programs.

$ ~./loop myfirst.loop~
hello there

While free expressions in a file are useful for writing quick scripts, if you write a larger application (consisting of many .loop files) you will want to define a clear entry point for your app. Loop uses the C convention of a function called main, which is called by the runtime if present:

main ->
  print('hello there')

Give this a try now. You should see the same result.

This is the first function we've seen. All functions in Loop are declared with a name followed by an arrow - ->. Any expressions that appear below, belong to the function and will be evaluated when the function is called.

function_name(arg1, arg2 ...) ->
  < expression >

Function main can optionally take arguments:

main(args) ->
  print(args)

...which are passed in via the command line:

$ ~./loop echo.loop ahoyhoy~
[ahoyhoy]

Note that this prints a list of arguments, denoted by the brackets - []. We'll learn about lists and functions in later sections.

Program structure

Loop files are organized into a formal structure which is enforced by the program parser. it is fairly straightforward:

module declaration

import declarations

functions & type definitions

free expressions

Here's an example of a loop program:

module mymodule

require othermod
require yet.another

class Pair ->
  left: 0
  right: 0

main ->
  new Pair()   # comments may appear anywhere


# free expressions must appear last
print('mymodule is go!')

A module declaration appears at the top (usually this is the name of the loop file without the .loop extension), followed by require statements that import other modules or Java types.

Loop type and function definitions come next and may be mixed, but free expressions are always last.

Free expressions in a module are evaluated any time a module is imported and always run before main, which, only executes if you specifically run that file. If you are familiar with static initializers in Java, that's essentially what free expressions are. You are encouraged to use main when writing anything more than trivial programs.

Data structures

In addition to any available Java data structures and types, there are a few built-in types in Loop.

Strings

Strings in Loop are Java strings (Unicode, UTF-16 java.lang.String objects). However there are many special loop-specific extensions for working with them. A normal string as we've seen before is enclosed in single quotes:

print('this is a string of characters')

Double quoted strings are also equivalent, but they may contain expressions that are expanded at runtime. For example:

greet(name) ->
  print("hello, @{name}")

greet('Dhanji')

Running this program prints hello, Dhanji to the console. Any loop expression can be embedded inside orb tags - @{}, similarly.

"26 * 34 is @{26 * 34}"

=> "26 * 34 is 884"

If you want to print the orb tags literally, fall back to single quoted strings:

'this is inside an @{orb tag}'

=> "this is inside an @{orb tag}"

Numbers

Loop has built-in support for most common numeric types. The following Java types are supported natively:

Loop has no support for floats, shorts or bytes, however these may be manipulated using Java primitive wrapper types if needed. By default, any integer appearing in a Loop program is treated as a 32-bit Integer

print(23.getClass())

=> java.lang.Integer

Longs, like in Java, are specified by suffixing 'L' to a number. Note that unlike Java this must be an uppercase L.

print(23L.getClass())

=> java.lang.Long

A special syntax is also available for arbitrary precision numbers (numbers too large or too precise to fit into 64 bits):

print(@23129038102938102938102938)
print(@2.3129038102938102938102938)

=> 23129038102938102938102938
=> 2.3129038102938102938102938

Notice the @prefix denoting arbitrary precision

You can add, subtract, multiply, divide, remainder or compare any two values if they are of the same numeric type:

3 + 4
=> 7

46L / 2L
=> 23

@2.0 > @1.9999999999999999
=> true

However, unlike Java, Loop forbids mixing numeric types in expressions. You must convert all values to the same, compatible type before computing with them.

\3L + 3\

=> \#error\

This is done to prevent loss of information when converting between types of different precision. You can convert any number to the largest precision type easily:

@3 + 4.embiggen()
=> 7

@3.0 + 4.0.embiggen()
=> 7.0

Note: embiggen() is a built in function that converts integers and longs to BigInteger and double to BigDecimal.

To convert a long down to an integer, you can use the intValue() method that Java provides:

3 + 4L.intValue()
=> 7

Note: Remember that this conversion necessitates a loss of information as you're going from 64 to 32 bits

Operators

Operators in Loop are symbols that combine two values to produce a resultant value, for example the + (plus) operator adds two numbers and produces their sum. Operators need not necessarily produce the same type of value as the values they combine. For instance, the > (greater-than) operator takes two numbers and returns true if the first is greater than the second.

The following operators are provided by loop:

+, -, /, *, %, >, <, >=, <=, ==, not, and, or

Some of these operators work on more than one data type. For instance, the + operator can be used to concatenate strings together:

'1' + '2'

=> '12'

In fact, as long as the string appears on the left, any value may be concatenated with it:

'1' + @2

=> '12'

Note that no loop operator ever mutates its operands. It simply reads and combines them to produce a third, resultant value. (the assign operator is an exception, but we'll deal with that later).

Lists

Lists are an essential component of programming in Loop, as as such they are supported natively in syntax. All loop lists are perfectly compatible with java.util.List and like String, come with some nifty, loop-specific extensions.

Lists may contain any (or many) type of object. Here is a simple list of integers:

[1, 2, 3, 4, 5]

Lists are zero-based and can be indexed using familiar brackets syntax - []

third(ls) ->
  ~ls[2]~

third([1, 2, 3])

=> 3

You can also use a similar syntax to slice portions off lists:

pick(ls) ->
  ~ls[0..2]~

pick([1, 2, 3, 4, 5, 6, 7])

=> [1, 2, 3]

Leaving out a lower or upper bound slices the list from or to the given index:

pick(ls) ->
  ~ls[2..] + ls[..1]~

pick([1, 2, 3, 4])

=> [3, 4, 2, 1]

Note that i used the + operator here to add the two sub-lists together. It is also sometimes useful to generate lists without typing out each entry. You can use the list range syntax to accomplish this:

pick(ls) ->
  ls[0..2]

pick(~[1..7]~)    # generate a list of 1-7

=> [1, 2, 3]

The range syntax is very similar to the slice syntax except that slices must operate on some existing list (in the above cases, a list variable, ls). So keep that in mind when reading Loop code.

Sets

Similarly, Loop provides a natural syntax for working with sets. A set is similar to a list except that it is a collection of unique items and that they are present in no particular order:

~{1, 1, 2, 2, 3, 3}~

=> {1, 2, 3}

Loop sets are fully compatible with java.util.Set classes.

Maps

Maps are an extremely useful data structure. Sometimes called hashtables or just hashes, a map is basically an collection of name (or key)/value pairs. They are supported natively by loop and created using a familar json-like syntax:

{ 'name' : 'Tyrion',
  'house' : 'Lannister' }

=> { name=Tyrion, house=Lannister }

Maps may contain any type or many types as either keys or values:

{ 1 : 'one',
 @2 : 'two' }

=> { 1=one, 2=two }

Read values out of maps using simple dot syntax (like in json):

{ 'name' : 'Tyrion',
  'house' : 'Lannister' }.~house~ + ' pays his debts'

=> "Lannister pays his debts"

Dot syntax can be chained to read from nested maps (maps inside maps) too:

~mymap.city.name~

=> "Sydney"

Note that the map keys that occur after the '.' must be strings to use this syntax.

For keys that are not strings, or not known until runtime, you can use the bracket syntax, similar to indexing into a list:

read(map, key) ->
  ~map[key]~

read({ 'name' : 'Tyrion'}, 'name')

=> "Tyrion"

Here the [key] part tells loop to look for an entry in the map whose value is equivalent to the variable, key. Later, when we call read(), the variable key contains "name" and so prints "Tyrion" You can also write to maps, similarly:

starkify(map) ->
  ~map.house: 'Stark'~

starkify({ 'name' : 'Tyrion'})

=> { name=Tyrion, house=Stark }

This is the first time we've seen the assign operator ':' (interchangeable with '='). We'll encounter it a lot more further on but for now, read it as assigning the value to the right of the : to the slot represented by its left.

Like sets and lists, maps are also fully compatible with Java's java.util.Map type.

Trees

Like sets are to lists, loop provides a simple syntactic abstraction to represent binary trees. A binary tree in loop is also compatible with java.util.Map and is used to associate keys to values. Its keys are, however, kept in natural sorted order which may be realized during iteration. We'll see more about this when looking at list comprehensions.

['Sansa': 'Stark',
'Catelyn': 'Stark',
'Brandon': 'Stark',
'Arya': 'Stark',
'Robb': 'Stark',
'Eddard': 'Stark'].keySet()

=> [ Arya, Brandon, Catelyn, Eddard, Robb, Sansa ]

Note that the keys went in in an arbitrary order and when printed produced a sorted result according to alphabetical order.

Symbols

Strings are extremely useful to represent data, however they are somewhat cumbersome to represent particular kinds of data--map keys for example. Like ruby, Loop ships with support for interned strings called symbols. They are more concise to write and perform marginally better at runtime:

{ @name: 'Tyrion',
  @house: 'Lannister' }

In loop symbols are prefixed with the @ character and unlike ruby are completely interchangeable with strings:

@popsicle == ('pop' + 'sicle')

=> true

All these data types and structures we've learned about can be combined to model rich ontologies to suit your needs.

{ @houses: { 'Lannister', 'Stark', 'Targaryen' },
  @protagonists: { 'Jon', 'Dany', 'Tyrion' },
  @locations: [ 'Winterfell'    : 'Stark',
                'Targaryen'     : 'Dragonstone',
                'Casterly Rock' : 'Lannister' ]}

String manipulation

Just as with lists, Loop provides a number of special syntax extensions for working with strings. These are just normal Java strings underneath. Let's look at a few: indexing works with strings exactly as it does with lists

'hello'[2]

=> 'l'

Note that the object returned is also a string (rather than a java.lang.Character). And like lists you can also slice strings:

str: 'hello'
print(str[2..4])
print(str[2..])
print(str[..3])

=> llo
=> llo
=> hell

You can also test if a string is a subsequence of another string with the index-of syntax:

sub?(str, smaller) ->
  str[smaller] > -1

'yellow'.sub?('ell')
'madam im adam'.sub?('mima')
'yellow'['lo']

=> true
=> false
=> 3

These tools make working with strings more declarative and readable, in line with loop's functional philosophy.

Basic expressions

Ok, now we're armed with numeric, string and structure types and know a tiny bit about how to call functions with arguments. Let's delve a bit deeper

Calling functions

A function is one of the most essential constructs in loop. A function takes in values (called arguments) and returns a single value, always. There are no void functions in loop.

At runtime, loop functions are compiled directly into Java functions. You call a loop function by writing its name followed by an argument list enclosed in parentheses - ()

print('hi')

Not all functions have arguments, but the parentheses must always be present:

hi ->
  print('hi')

hi()

=> hi

Loop functions can take anything as an argument: numbers, strings, objects, or even other functions:

meta(func) ->
  func.@call('hi')   # calls print()

meta(print)

=> hi

Note the special syntax to call a function via reference. The variable func contains a reference to the print() function. To call it, we use the special form @call, which calls the references function print, with the given arguments.

A function can be treated like any other value in loop, it can be stored in a variable or passed around to other functions as we've just done.

As we saw earlier you can also call methods on Java objects. This happens in exactly the same way as in Java:

print('hi'.toUpperCase())

=> HI

Here, we're calling the Java method String.toUpperCase(), and then passing its result to print(). You can call methods on any object in Loop this way.

print('hi'.toUpperCase().toLowerCase())
print(22L.intValue())
print(new java.util.Date().getClass().getName())

=> hi
=> 22
=> java.util.Date

Loop also provides a form for calling loop functions the way you call methods. This is known as the call-as-method feature. Let's define a simple loop function triple() that takes an integer and multiplies it by 3.

triple(i) ->
  i * 3

triple(2)

=> 6

This is the standard way to call loop functions, but you can also write it like this:

triple(i) ->
  i * 3

2.triple()

=> 6

In this instance we're treating the left hand side of the function as its first argument. You can call any loop function 'as a method' this way, including ones that take multiple arguments:

multiply(x, y) ->
  x * y

5.multiply(6)

=> 30

Call-as-method provides a nice scheme for 'extending' the functionality of existing objects. All these values we've dealt with so far are standard Java strings and integers. You can even use this form to override existing Java methods with your own functionality:

toLowerCase(str) ->
  str.toUpperCase()

'hi'.toLowerCase()

=> 'HI'

Of course, you'll want to be careful when doing this to avoid confusing yourself later ;). Loop provides an escape hatch in case you want to bypass overrides and ensure that the underlying Java method is being called.

toLowerCase(str) ->
  str.toUpperCase()

'HI'<-toLowerCase()

=> 'hi'

The reverse arrow - '<-' operator signifies that a Java method on the object is needed. You are encouraged to use clear conventions to keep these schemes clear. For instance, I like to use the underscore naming convention for Loop functions to distinguish them from Java methods:

# loop function
'Now is the winter of our discontent'.to_lower()

# Java method
'Now is the summer of our content'.toLowerCase()

You may instead, prefer to use the '<-' reverse arrow to distinguish them too.

# loop function
'Now is the winter of our discontent'.toLowerCase()

# Java method
'Now is the summer of our content'<-toLowerCase()

Loop doesn't mandate one or the other, although I prefer using the reverse arrow only when I want to clarify a potential override.

Navigating objects

We've looked at calling functions, now let's look at the values that functions manipulate and operate on: objects. Values in loop are generally compatible with Java objects and vice versa. Often objects are more than just a simple scalar value, they consist of a structure of values--as we saw earlier in the case of maps and lists.

We also saw that maps could be navigated by using the dot syntax, in other words, we can read values from the deep structure of the map by specifying a path to the desired slot:

food: {
  @name: 'Pancake',
  @nutrition: { @calories: 810, @protein: 0.2 }
}

print(~food.nutrition.calories~)

=> 810

In this example, we pick out calories by specifying a path down the graph structure of the map. This should be familiar to you if you've used ruby or python (or any modern object-oriented language, really). Loop provides an identical scheme for navigating all types of objects:

now: new java.util.Date()

print(~now.time~)

=> 1335878239084

Here, I'm reading the time property out of a Java Date object using the same dot-syntax. This 'property', in Java, is represented by the pair of methods: getTime() and setTime(). The Java convention is to declare method pairs like this to expose properties. (in Ruby they are called attribute accessors.)

Loop will correctly translate this to a Java method call as required. Let's look at a more realistic example:

# Java code
public class Person {
  private Person parent;
  private String name;

  public Person getParent() {
    return parent;
  }

  public String getName() {
    return name;
  }

  // etc..
}


# loop code that reads Java object
print("Gran's name is")
print(~person.parent.parent.name~)

=> Gran's name is
=> Grantastic E. Gran

The property navigation syntax becomes extremely useful as you write complex programs that interact with existing Java code and libraries. Loop objects can also be navigated similarly, as we'll see in the section on classes & objects.

If/Then/Else

Branching in loop is similar to branching in Haskell or Scheme; because all expressions must produce a value, an if-then statement must also have an else clause. The if portion takes any boolean expression and evaluates the then clause if true, or the else clause if not.

if ~3 > 4~ then 'hooray' else 'booray'

=> booray

Or more generally:

if < boolean > then < expression > else < expression >

The < boolean > part may consist of any expression that results in a true or false value. The entire statement should generally appear on a single line:

classify(val) ->
  if val > 1000 then print('big') else print('small')

99.classify()

=> small

But you can split it across multiple lines if the whole expression is wrapped in parentheses:

classify(val) ->
  (if (val < 1000) and (val > 100)
   then print('middling')
   else print('unknown'))

101.classify()

=> middling

Notice I snuck in an and keyword here. you can similarly use or and not operators to combine boolean expressions, arithmetically.

List comprehension

We've already seen how lists can be sliced and their indexes read; but we can do a lot more with them. Loop provides a class of expressions known as list comprehensions to quickly transform and make sense of lists and sets. Comprehensions replace the traditional do-while constructs of C and Java. Take a look:

i * 2 for i in [1, 2, 3]

=> [2, 4, 6]

Here i am saying, for every item in the given list [1, 2, 3], multiply that item by 2. List comprehensions act on any kind of collection (all instances of java.util.Collection) not just lists or sets; and they always produce lists of equal or smaller size.

i for i in [1..10] ~if i % 2 not 0~

=> [1, 3, 5, 7, 9]

Here i am filtering the list of integers from 1 to 10 by whether or not they are odd (i.e. i % 2 not 0). The resulting list contains only items that passed the filter. You can even combine the filter and transformation expressions:

~i * 10~ for i in [1..10] ~if i % 2 not 0~

=> [20, 40, 60, 80, 100]

Expressed more generally, a list comprehension is,

< expression > for < var > in < list > [if < filter >]

The first expression is evaluated once for every item in the list that passes the filter. The result of that expression is added to the output list. And the filter clause is optional.

List comprehensions must also appear on a single line but can be multiline if enclosed in parentheses, just like if/then/else expressions.

Null-safety

Loop is a null-safe language. This means that loop expressions will not throw a NullPointerException on evaluating null values. There are some exceptions to this, but as a general rule, Loop strives to acheive a practical reduction of NPEs and the use of null in code.

Since there is no null keyword, in the loop type system, we have what is known as a bottom type. If Object is the top type, meaning every type inherits from object, then Nothing is the bottom type--meaning that it inherits from all types. No types may inherit from Nothing, and there is only one global instance of it, which is also referred to as Nothing, in expressions.

Nothing

=> Nothing

This bit of theory aside, in practical terms you can rest assured that calling a method on a Nothing will not break your code--it simply returns Nothing again

Nothing.some_method().some_other_method()

=> #nothing

empty: {:}
empty.name

=> Nothing

This even holds for Java code that returns null:

''.getClass().getResource('doesnt_exist').toString()

=> #nothing

If you need to pass null into Java apis for some reason, you can use Nothing instead:

person.parent = Nothing
city.setName(Nothing)
j_option_pane.showMessageDialog(Nothing, 'hi there')
# ..

Loop will correctly translate this when calling into Java, without any performance penalty.

Of course, Loop cannot prevent NullPointerExceptions from being thrown in Java code(!), so code that already does will continue to behave as expected. Loop does not try to catch and translate them into Nothings. Instead, you will have to handle them as you do any other exception (more in the section on control flow).

Additionally, Loop does not permit Nothing in computations. It is a type error to attempt to compute with Nothing.

1 + \Nothing\

=> \#error\

This is somewhat of an escape-hatch to the bottom-type rigor. While, strictly speaking, 1 and Nothing are type-compatible, loop prevents mixed-type computations involving the bottom-type. This is to prevent absurd questions like determining the value of Nothing in the context of another type. Nothing is both the sentinel value for all types and the is contravariant with all types. This is the sane choice when dealing with a bottom-type.

Functions

We've already been declaring and using functions throughout the examples thus far. Let's look at them in a bit more depth.

Sequences

A function generally takes zero or more arguments and returns a value. So far, all the functions have contained only one expression, but sometimes this is not sufficient. Sometimes you need to do a sequence of things in a go, this is very easy:

increment(num) ->
  print(num),
  num + 1

3.increment()

=> 3
=> 4

Here, we're executing two expressions in sequence, first we print the value of the number, then we return the number plus one. A sequence may contain as many expressions as you need:

do_stuff() ->
  print('hit any key to continue...')
  pause()
  print('no, hit the ANY key to continue...')
  pause()
  print('done!')

=> hit any key to continue...
=> no, hit the ANY key to continue...
=> done!

Note that the last expression must not end in a comma. Haskell programmers may be familiar with this as a do block, and for Schemers, this is the equivalent of a begin sequence. However, unlike Haskell, in Loop, a sequence is guaranteed to execute in order.

Local scope

It is often useful to define a set of values and functions specifically for a purpose. Loop provides a lightweight and simple system to accomplish this:

minutes_in(num) ->
  num * year
  where
    hour: 60
    day : 24 * hour
    week: 7 * day
    year: 52 * week

print("there are @{minutes_in(2)} minutes in 2 years")

=> there are 1048320 minutes in 2 years

The local variables hour, day, week, year are only available inside the scope of the minutes_in() function.

minutes_in(num) ->
  num * year
  where
    hour: 60
    day : 24 * hour
    week: 7 * day
    year: 52 * week

print(\year\)

=> \#error\

This is exactly equivalent to the following, more familiar looking code:

minutes_in(num) ->
  hour = 60
  day  = 24 * hour
  week = 7 * day
  year = 52 * week
  num * year

print(\year\)

=> \#error\

You can even declare functions in local scope:

initials(name) ->
  first(words[0]) + first(words[1])
  where
    words: split(name)
    ~first(word) ->~
      if word.isEmpty() then '' else word[0]

initials('Theon Greyjoy')

=> 'TG'

\first\('Elric')

=> \#error\

Here the function first() is scoped locally to the parent function initials(). It's useful to save on duplicating code within the parent function, but not needed elsewhere, so it goes out of scope

Locally-scoped functions can also have local scopes of their own. There is no limit to the nesting depth. So please use it wisely =)

minutes_in(num) ->
  yearify(num)
  where
    yearify(num) ->
      weekify(num)
      where
        weekify(num) ->
          dayify(num)
          where
            ...
    

Module scope

By default all top-level functions in loop are public. That means any function that is not part of a where block of another function is visible to (and callable from) all other parts of the program. Now, local-scope is useful for helper functions that are private to a single function, but what if you want to share helpers between top-level functions?

Loop provides a third scope known as module scope for exactly this purpose. Functions scoped at the module level are visible to all other parts of the same module, but nowhere else. In this way you can reuse important blocks of code easily, and still hide unnecessary implementation details by sectioning them off from other parts of your app.

@last(ls) ->
  ls[ls.length() - 1]

func1 ->
  [1, 2, 3].@last()

func2 ->
  [30, 20, 10].@last()

=> 3
=> 10

# a different module
func3 ->
  [10, 20].\@last()\

=> \#error\ 

You declare a function as module-scoped by simply prefixing its name with @. Loop will ensure that this function is restricted to the current module. Note that this is not merely a naming convention: @-prefixed functions have their visibility enforced by the runtime.

Closures

Anonymous functions, are quite useful in a language like loop where functions are a first-class construct. Because they can be held in variables and passed around, it is sometimes useful to create small blocks of functionality that aren't necessarily declared as top-level or scoped functions. These blocks of code capture the variables in the nearest surrounding scope (i.e. they "close over" the current lexical scope). These are called closures:

manipulate(ls, func) ->
  func.@call(i) for i in ls

manipulate([1, 2, 3], @(x) -> { x * 5 })

=> [5, 10, 15]

In this example, I am passing a code block in the form of an anonymous function (@-sign followed by an argument list and -> arrow) to the meta-function manipulate(), which takes a list of items and calls the given function for each item in the list.

Notice that the closure's expression is housed inside braces - {}. This is done to clearly distinguish code belonging to the anonymous function from outer code. Using braces also means you can put the expression on multiple lines and retain readability:

manipulate(ls, func) ->
  func.@call(i) for i in ls

manipulate([1, 2, 3], ~@(x) -> {
                        x * 5
                      }~)

=> [5, 10, 15]

The nice thing about closures is that they can refer to values in scope at the time of their creation long after the scope is lost:

make(num) ->
  @() ->
    print(num)

make(10).@call()

=> 10

Pattern matching

So far we've looked at simple functions and sequences--functions that essentially contain a single expression to manipulate and respond to input values. This is well and good for a lot of use cases, but loop provides a more powerful, declarative construct called pattern matching.

Pattern matching in loop is inspired by the same idea in Haskell and closely mirrors the latter's concepts. When used properly, pattern matching can be extremely powerful, giving you concise, elegant programs that read like a solution rather than a list of instructions.

Pattern matching functions in loop are denoted by a => arrow and embody one or more pattern rules that follow:

speak(num) =>
  1   : 'one'
  2   : 'two'

speak(2)

=> two

This is a naively simple pattern matching function that tests if the value passed in is either 1 or 2 and returns the appropriate string. Note that it reads like a table of mappings between inputs and outputs. This is much nicer to work with than the equivalent standard form

speak(num) ->
  if num == 1 then 'one' else (if num == 2
                               then 'two' else ...)

speak(2)

=> two

...much less readable.

Now let's go back to the pattern matching form:

speak(num) =>
  1   : 'one'
  2   : 'two'

speak(\ 3\ )

=> \#error: Non-exhaustive pattern rules\

Notice that we don't have a rule for values other than 1 or 2. What happens if we call the pattern matching version of speak() with 3?

Since there is no behavior specified, Loop complains that the rules are insufficient. In the standard if/then case we can handle this with an additional else, however in pattern matching we use a wildcard rule

speak(num) =>
  1   : 'one'
  2   : 'two'
  ~*   : 'other'~

speak(3)

=> other

The asterisk - * denotes any value, and is matched last, if all the other rules fail.

Of course this will match anything not just other numbers:

speak(num) =>
  1   : 'one'
  2   : 'two'
  *   : 'other'

speak(~'dont say a word!'~)

=> other

If you want the rules to be stricter (so the function only accepts integers), you can impose a type pattern rule instead:

speak(num) =>
  1       : 'one'
  2       : 'two'
  ~Integer : 'other'~

speak(3)

=> other

speak(\'say no more'\)

=> \#error: Non-exhaustive pattern rules\

Note that the order in which the rules appear is important.

Polymorphism

As you may have guessed, loop's alternative to function overloading is the use of mixed pattern rules. A single function can be made to perform different actions depending on the input type, using pattern matching:

times(val, num) =>
  Integer, *   : val * num
  String, *    : val for val in [1..num]

1.times(4)

=> 4

'hi'.times(2)

=> [hi, hi]

Here we've introduced two new concepts:

Polymorphism in this instance is simply allowing you to call the same function with an integer and string respectively. With multiple arguments, the second argument is a wildcard rule and is separated by a comma after the first rule. All argument rules must appear on the same line.

A pattern matching function must have a rule for each of its arguments. If you dont care what an argument is, use the wildcard rule (as we've just done). Otherwise remember that all the rules you declare are tested in the order the appear.

You can of course, freely mix type rules with other kinds of rules to easily match a variety of different inputs.

List patterns

The simplest form of list pattern matching is literally matching the empty list:

empty?(ls) =>
  []      : true
  *       : false

empty?([])

=> true

In this instance the empty list matcher matches ahead of the wildcard. You may also match a single element list:

only_item(ls) =>
  ~[x]~   : x

[88].only_item()

=> 88

The neat thing here is that not only do you match the pattern rule but it also lets you extract items from the list conveniently. the variable x holds the only list element, and can be used in the right-hand side conveniently, as though it were an argument itself. This is known as list destructuring.

List patterns are among the most useful of pattern matching capabilities in loop. The basic idea is to pull apart (destructure) a list into individual parts and process the items declaratively. As we'll see, this makes list processing code really easy to write and read.

reverse(ls) =>
  []         : []
  [x:rest]   : reverse(rest) + [x]

reverse([1, 2, 3, 4, 5])

=> [5, 4, 3, 2, 1]

This function as its name implies, reverses any list passed in. The way you read this is as follows.

The reverse of the empty list is the empty list:

reverse(num) =>
  ~[]         : []~
  [x:rest]   : reverse(rest) + [x]

The reverse of any list that has at least one item is the first item (x) appended to the reverse of the remaining items (rest).

reverse(num) =>
  []         : []
  ~[x:rest]   : reverse(rest) + [x]~

This is what's known as a recursive function, meaning that it calls itself (recurses) in order to complete its task. Let's look at how this function destructures a simple list like [1, 2, 3]

reverse([1, 2, 3]):  1:[2, 3]  : reverse(~[2, 3]~) + [1]
reverse([2, 3]):     2:[3]     : reverse(~[3]~) + [2]
reverse([3]):        3:[]      : reverse(~[]~) + [3]
reverse([]):         []        : ~[]~

=> [3, 2, 1]

Each line above represents a call of the function. On the left are the arguments it is called with, and on the right the pattern rule that matched and its values, after destructuring. What happens is that each additional recursive call gets a step closer to the empty list and when you unravel this stack of calls, you get the list in reverse order.

Read the rightmost part of the expression (in bold) from the bottom up to illustrate the unraveling:

reverse([1, 2, 3]):  1:[2, 3]  : reverse([2, 3]) ~+ [1]~
reverse([2, 3]):     2:[3]     : reverse([3]) ~+ [2]~
reverse([3]):        3:[]      : reverse([]) ~+ [3]~
reverse([]):         []        : ~[]~

# reading upwards:
~[] + [3] + [2] + [1]~ => [3, 2, 1]

Object patterns

Any object (including maps and Java objects) can also be pattern matched, like lists. The primary use is to destructure object graphs and enable more declarative code that is easier to write and read.

gran(person) =>
  ~{ g <- person.parent.parent }~   : g.name

This will print person's grandparent's name, if you have an object graph that looks something like this:

billy: { @name: 'Billy',
         @parent: { @name: 'Willy',
                    @parent: { @name: 'Silly' }
                  }
}

gran(billy)

=> Silly

Let's look at this code in a bit more detail:

gran(person) =>
  ~{ g <- person.parent.parent }~   : g.name

First we have a new type of pattern rule, encased in braces - {}. This signals to loop that the function should expect an object to destructure. Next there is a variable, g, followed by a reverse arrow <-, which indicates that g should receive the result of the destructuring.

gran(person) =>
  { ~g <-~ person.parent.parent }   : g.name

On the right side of the reverse arrow is the path expression in dot syntax. Recall path expressions from the section on object navigation. In this instance we are asking for the parent of the parent of argument person.

gran(person) =>
  { g <- ~person.parent.parent~ }   : g.name

Finally on the right hand side of the rule itself, is another path expression g.name, which, is the result we want to return if this pattern rule matches.

gran(person) =>
  { g <- person.parent.parent }   : ~g.name~

This is a rather simple example, and it can be rewritten as a standard function as follows:

gran(person) ->
  person.parent.parent.name

But for more complex object patterns, or for polymorphism, the pattern matching form is quite useful. You can add as many destructurings as required in a single object pattern rule:

gran(person) =>
  { g <- person.parent.parent,
    ~n <- person.name~ }    : "@{n}'s gran is @{g.name}"

=> Billy's gran is Silly

And you can also mix object pattern rules with other kinds of patterns.

String patterns

String pattern matching is one area where loop really shines. There are a variety of useful tools for matching patterns in text. These can be combined to make quite powerful parsers and text processors, that are readable and concise.

First, as you might guess, any string literal can be matched as a simple rule:

numberize(str) =>
  'one'     : 1
  'two'     : 2
  'three'   : 3

numberize('two')

=> 2

This scheme will also match symbols:

numberize(str) =>
  'one'     : 1
  'two'     : 2
  'three'   : 3

numberize(@two)

=> 2

Of course, for clarity you should use symbols directly in your pattern rule where appropriate:

numberize(str) =>
  @one     : 1
  @two     : 2
  @three   : 3

numberize(@two)

=> 2

But this is just the tip of the iceberg. Strings can be destructured in a manner very similar to lists:

initial(name) =>
  (~i:rest~)     : i

initial('Megatron')

=> M

Here the destructuring works by pulling apart the leading character of the string. Note that we use parentheses - () instead of brackets - [] in the pattern rule. This is necessary to inform loop that we are matching a string and not a list.

But strings can be destructured in even cooler ways. By specifying a delimiter, you can tell loop to split a string around a particular sequence:

flip(name) =>
  (~first~ : ' ' : ~last~)  : "@{last}, @{first}"

flip('Optimus Prime')

=> Prime, Optimus

Note that in this example, we use a single space to pattern match around. All the characters before it are destructured into first and those after it into last.

There is no limit to how many delimiters you can add to create a pattern:

select(str) =>
  ('A':a:'B':b:'C':c:'D':d)   : a + b + c + d

select('A1B2C3D4')

=> 1234

And there is no restriction on the length of the delimiters:

extract(str) =>
  ('Now is the ': ~season~ :' ':ignore)  : season

extract('Now is the winter of our discontent')

=> ~winter~

extract('Now is the summer of your content')

=> ~summer~

You can use this technique recursively to process several lines, from a text file for example:

uncomma(str) =>
  ''                   : ''
  (word: ',' : ~rest~)   : word + '\n' + ~uncomma(rest)~

uncomma(read('bots.csv'))

# given a file, 'bots.csv' containing:
Perceptor,Astrotrain,Starscream,Soundwave

=> Perceptor
=> Astrotrain
=> Starscream
=> Soundwave

Notice the base case for the empty string, which is needed to terminate the recursion when the end of the file text is reached.

On top of this, loop also provides powerful regex pattern matching:

judge(str) =>
  /(t|w)alk(ing)?/   : print('Good')
  *                  : print('Lazy!')

judge('talk')
judge('walking')
judge('idle')

=> Good
=> Good
=> Lazy!

In this instance, the regular expression (signified by bounding slashes - //) is used to match whether or not the first rule should apply. It is slightly simpler than the earlier examples in that it does not destructure the string in any way, instead is used to accept a more flexible range of inputs.

But! If you really want to destructure using a regex pattern, that is also possible using a regex standard known as named capturing groups. A named capturing group is any capturing group occurring inside a regular expression (enclosed in parentheses - ()) that also is tagged with a name. This is done using a special syntax. the name is then extracted and made available as a variable:

extract(activity) =>
  /(?< ~verb~ >(t|w)alk)(ing)?/   : verb

extract('walking')
extract('talk')
extract('talking')

=> walk
=> talk
=> talk

See this website for more information on named capturing groups and how to write them. But do use them sparingly, excessive use of named groups can make regexes (which are, let's face it, already quite messy) totally illegible.

Guards

Guards are an addendum to pattern matching and can be seen as an extra line of defense for declarative code before resorting to if/then/elses function bodies. Whereas pattern rules focus on the type and structure of the data, guards are meant to be able to test specific conditions using arbitrary logic. Let's take a look:

gender(name) =>
  String     | name.startsWith('Mr.')    : @male
             | name.startsWith('Mrs.')   : @female
             | name.startsWith('Ms.')    : @female
             | else                      : @unknown
  *                                      : @error


gender('Mr. Rogers')
gender('Ms. Marple')

=> male
=> female

The trick here is that a guard is just any boolean expression that allows the execution of the right-hand expression if true. You can place arbitrary logic in a guard expression, as long as it evaluates to true or false:

gender(name) =>
  String     | name.startsWith('Mr.')    : @male
             | name.startsWith('Mrs.')   : @female
             | ~name.startsWith('Ms.')    :~ @female
             | else                      : @unknown
  ...

In our case we're testing the salutation of a name against a number of literal values (Mr, Mrs and Ms). But notice that we have an extra clause at the end, an else clause. This is necessary if the pattern matches but none of the other guard expressions do. All guarded patterns must have an else expression (if you recall what we said about if/then/else expressions, this will make sense).

Furthermore, it is important to distinguish between the else expression, which is effectively the default case against a matched pattern and the wildcard pattern--which is a catchall if no patterns matched.

In the example case we can trigger the else clause by specifying any string that is missing a salutation:

gender('Pat')

=> unknown

This is different from the wilcard rule, which matches anything that failed the previous patterns (and is effectively polymorphic):

gender(\ 22 \)

=> \#error: non-exhaustive pattern rules\

In a sense, you can think of guards as analogous to a switch statement in C or Java, except that they can handle any kind of expression, and do so in linear order. A better analogue might be an if-elseif-elseif-elseif-... style block, with a final else. Except that guards are far easier to read and far simpler to write.

Here is a guarded function that determines if an integer falls into predefined ranges:

range(x) =>
  *           | x > 100     : @large
              | x < 0       : @negative
              | x < 100     : @small
              | else        : @exactly100

range(224)
range(4)
range(-6)

=> large
=> small
=> negative

Notice how the order of evaluation played a part in our function, which correctly classifies values between 0 and 100 as small, even though the @small rule is only written to check for x < 100. Using guards this way makes complex branching easy to visualize and reason about.

Sometimes people are confused between when you need to use a guard and when you use just another pattern rule. There are no strict guidelines about this, but one way to think of it that patterns are about the structure of objects, and guards about their value (or data).

This distinction becomes clear when you consider that pattern rules do not cause any functions to be called, whereas guards often do. Meaning that pattern rules describe the static behavior of the program (what types are accepted, what structure of objects, or pattern of strings), and guards describe the dynamic behavior of the program, i.e. what result is produced if a condition is satisfied.

Guards are useful in a variety of situations, and they may be applied on any kind of pattern rule. Look at some Loop examples to get a feel for how guards can be used.

Control flow

Since loop is a JVM language, it honors the Java system of exceptions for handling control flow in error cases. Loop exceptions are just Java exceptions except that it does not support the concept of checked exceptions. all exceptions in loop are unchecked, usually a kind of RuntimeException.

Raising exceptions

Exceptions in loop are thrown using the raise() function which is always available in all programs:

divide_by(x, y) ->
  if y not 0 then x / y else ~raise~('divide by zero')

main ->
  1.divide_by(0)

=> \#error: divide by zero\

This simplistic function checks if the divisor is equal to 0 and raises an exception if so. The function raise() terminates execution immediately and unravels the function stack until it is handled or the program exits in error.

The familiar JVM stack trace showing each function call in the chain of execution that led to this exception is generated properly by all Loop programs. Here's an example:

java.lang.RuntimeException: ~divide by zero~
    at prelude.raise(prelude.loop:9)
    at _default.divide_by(test.loop:3)
    at _default.main(test.loop:6)

By default, raise() throws a java.lang.RuntimeException with an embedded cause message. (this example presupposes that the divide_by() function is saved in a file called test.loop, which is called from the command line using the loop runtime.)

Otherwise, it is nice to see that the trace points correctly to the offending line of code, line #3 in test.loop:

java.lang.RuntimeException: divide by zero
    at prelude.raise(prelude.loop:9)
    ~at _default.divide_by(test.loop:3)~
    at _default.main(test.loop:6)

You may also note that the stack trace is clean, and only presents Loop functions with none of the intermediate Java gunk that other JVM languages sometimes leave behind.

Of course, if your loop code calls third-party Java methods you may see it after all ;)

Handling exceptions

Exceptions in loop are handled via a specialization of pattern matching functions called handler forms. These are pretty much the same as a pattern matching function, but with some additional constraints imposed. Let's try to handle the divide-by-zero error from the previous example:

divide_by(x, y) ~except handler~ ->
  if y not 0 then x / y else raise('divide by zero')

~handler~(e) =>
  *      : e.message

1.divide_by(0)

=> divide by zero

There are a couple of changes we've made here. Firstly, there is a new function simply called handler() (you can call it whatever you want). Secondly, we have added a clause to the divide_by() function which reads except handler. This is the exception handler clause which tells loop to catch exceptions thrown by the declaring function and hand them off to the specified function for processing.

Furthermore, we see that this time there is no error, instead we simply get back the message divide by zero, which was extracted from the exception object itself:

divide_by(x, y) except handler ->
  if y not 0 then x / y else raise('divide by zero')

handler(e) =>
  *      : ~e.message~

1.divide_by(0)

=> ~divide by zero~

Since the exception is passed as an argument to handler(), we can use pattern matching to provide the appropriate recovery response.

This lets you handle any exception thrown using raise(). You can even use guards here to do interesting things:

divide_by(x, y) except handler ->
  if y not 0 then x / y else raise(~'dbz'~)

handler(e) =>
  { msg <- e.message }  | ~msg == 'dbz'~  : 0
                        | else          : raise(e)

1.divide_by(0)

=> 0

Here we're using a combination of object pattern matching (see earlier section) and guards to check if the exception contains a particular message. If it does, we return zero as if nothing abnormal happened. Otherwise, the exception is rethrown.

Handling Java exceptions

Of course, loop's simple exceptions aren't the only kind you'll have to deal with. Java exceptions come in a variety of types that don't all extend from RuntimeException. To handle these properly, we can use a type pattern rule:

io_handler(e) =>
  IOException      : e.message
  *                : raise(e)

In this example, we're catching any instance of IOException and returning the message. For all other kinds, we rethrow the exception. The wildcard rule will catch any subtype of java.lang.Exception including all RuntimeExceptions.

Loop will correctly generate the Java bytecode to catch only the exceptions specified by type pattern rules, in the order they are specified. So if you want to you can even write:

io_handler(e) =>
  FileNotFoundException   : raise(e)
  IOException             : e.message

This handler will rethrow the exception if a file is not found, otherwise it simply returns the error message, catching the exception. However, note that since we have only specified two exception types, anything that's thrown of a different type, say NumberFormatException, won't be caught or handled.

Loop is smart enough to catch only the specific exception types--note that there is no catch/analyze/rethrow penalty, as the correct bytecode is generated underneath.

Please also remember that the wildcard rule will only catch Exceptions, not java.lang.Errors or other types of java.lang.Throwable. If you wish to catch these types, you must specify them explicitly:

io_handler(e) =>
  FileNotFoundException   : raise(e)
  ~OutOfMemoryError        : exit(1)~
  *                       : e.message

Classes and objects

In addition to working with Java classes and objects, Loop also provides its own, more ideal type system. Classes in Loop are simple graphs of data that have no private state and no methods. Formally, they define a grouping of properties identified with a common purpose or model. All properties are publicly accessible by all functions. You can think of Loop classes like structs in C or C#, or as a formalization of the map data structure we encountered earlier.

Constructors

Loop objects are simply instances of these classes. They are created using the new keyword and all have implicit constructors.

class Star ->
  name
  mass

new Star()

This will create an empty object, which, is no different from the empty map:

new Star() == {:}

=> true

Like maps and Java objects, reading the properties of a Loop object is done via dot-syntax:

star: new Star()
~star.name~

=> Nothing

Every Loop type automatically has a dynamic set of constructors for providing its initial set of properties:

class Star ->
  name
  mass

new Star(name: 'Proxima Centauri',
         mass: 0.123)

Note that these are constructors that take named arguments. Each argument is tagged with the name of the property for which you are specifying the value. This makes it clear what you're passing in when you have large constructors.

Of course, if you don't know a value at construction time, you can simply leave it out:

class Star ->
  name
  mass

new Star(name: 'Proxima Centauri')

This object will return nothing when you query its mass property, but its name has been set.

class Star ->
  name
  mass

star: new Star(name: 'Proxima Centauri')
star.name
~star.mass~

=> Proxima Centauri
=> Nothing

Setting (or re-setting) a property is done with the assign operator:

class Star ->
  name
  ~mass~

star: new Star(name: 'Proxima Centauri')
~star.name: 'Errai'~
star.name

=> Errai

Defaults

Loop has no explicit constructor function, instead if you want to specify certain default values that are preset on all instances of a class, you can directly specify it in the class definition:

class Star ->
  name
  ~mass: 0.123~

star: new Star(name: 'Proxima Centauri')
star.mass

=> 0.123

Any expressions specified in the class definition will be evaluated along with the constructor and stored in the appropriate property when the object is instantiated. You are free to invoke methods or specify any other kind of expression here. The only restriction is that you may not refer to other properties of the same object.

class Star ->
  name
  default_mass: 0.123
  \mass: default_mass\

=> \#error\

If you want to share a value like this, you must specify it explicitly in a constructor.

While Loop objects resemble freeform maps, there are some important restrictions:

Immutability

Immutability, or the idea of an object whose structural data cannot change once created, is a very important precept in any language that allows concurrency (multi-threaded programming). Loop is no exception. In fact, since Loop has a strong focus on concurrency, immutability is integrated into the core of the language itself.

Threads in Loop are not allowed to share mutable state (except via memory transactions, which we'll see later). In order to allow them to share fixed data, however, Loop provides a special construct called immutable types.

immutable class Star ->
  name
  mass

star: new Star(name: 'Proxima Centauri',
               mass: 0.123)
star.name
\star.name: 'Errai'\

=> Proxima Centauri
=> \#error\

Note the addition of a new keyword to the class definition: immutable. This tells Loop that instances of class Star may not be altered after they are created. So any properties must be set via the constructor.

In this case, an attempt to mutate the value name resulted in a mutation error. This ensures that we can share objects like this between threads, safely, without worrying about their data becoming corrupt or inconsistent. We'll see how this is done in much greater detail, in the section on Concurrency.

Duck typing

We've briefly seen that objects in Loop can be compared with maps in a compatible fashion. This is part of a much broader feature known as duck typing. It involves the idea that an object whose structure resembles that of another is equivalent to it.

Let's say we have a function that prints the name and age of a Star:

name_age(star) ->
  print("@{star.name} is @{star.age} years old")

name_age(new Star(name: 'Proxima Centauri',
                  age: 40000000))

=> Proxima Centauri is 40000000 years old

Now, this function expects an argument called star, but really it expects any object that has two properties: name and age. So we can easily write:

class Person ->
  name
  age

name_age(new Person(name: 'Nikola Tesla',
                    age : 142))

=> Nikola Tesla is 142 years old

This is completely legal and works as expected because of duck typing. In other words:

if it walks, talks and quacks like a duck, then it is in fact, a duck.

Java interop

As said earlier, Loop interoperates well with Java. So far we've seen how to call instance methods on Java objects seamlessly; and create new Java objects by calling their constructors. Now let's take a quick look at some other conveniences.

Static methods

Loop has a special syntax for referring to static methods of Java classes:

~`java.lang.System`~.currentTimeMillis()

=> 1337221827894

Note the backticks - `` around the type reference to java.lang.System. This tells loop that we are referring to a Java type and must resolve any method calls as a static member of the class. The function currentTimeMillis() returns the current system clock time in number of milliseconds since the beginning of the epoch.

Static fields

Class member (or static) fields are similarly resolved on Java types, but must be specified using a special operator:

~`java.lang.System::out`~.println('hi')

=> hi

Notice the :: operator which designates the name of the static field (in our case, out). The entire field reference must appear inside the backticks and the resolved field left to be evaluated against whatever expression you put around it. In this example, I'm calling the method println() which simply outputs a string to the console. In fact, this is exactly how Loop's own print() function works internally.

Imports

This code can be written shorter with a type import from java-space:

require java.lang.System

~System::println('hi')~

=> hi

Type references

It is sometimes useful to resolve a Java class itself. Some libraries take classes as arguments, or you just may want to ensure that a Java class is available in the current process. As you might guess, you can refer to a Java type using the same backticks syntax we've seen so far:

print(`java.util.Date`)

=> java.util.Date

This is the equivalent of the following Java code fragment:

System.out.println(java.util.Date.class)

Modules

Modules in Loop are ways of packaging and shipping utility programs for use with other Loop programs. Modules may also be used as a simple way of organizing a large project and are usually composed of cohesive groups of functions and classes.

The process of making such a module available for use in your program is called importing. In Loop, you use the require keyword to import a module by name:

require file

print(read('file.txt'))

Here, the module file is imported for use by our program. It provides the function read(), and we can thus use it without declaring it specially. This function reads the contents of a file named autobots.txt and returns a string.

require file

print(~read('autobots.txt')~)

Go ahead and create yourself a text file (in any editor) with a list of autobots and run the program. For me this prints out:

Hot Rod
Arcee
Ironhide
Perceptor
Optimus Prime

Nice! We didn't have to call any Java code or define any additional functions. The file module did all the work for us. This module ships with loop, and is part of its core library. It provides many other functions useful for manipulating and working with files.

Now, all the functions in the file module are available in your program. However, this can present a problem if we already have functions with the same names. Let's take read as an example:

require file

~read(val)~ =>
  /[0-9]+/    : val.to_integer()
  *           : val

print(read('autobots.txt'))

=> \autobots.txt\

Now we have declared a function that converts strings to integers that is also called read(). When we run the program, we dont get any errors, rather we get the wrong behavior. One way to fix this problem is to rename our function to something like @read(), but this is not a great solution. Both functions are legitimately called read() and there is no reason why one should have to give up the name to the other.

Loop provides a feature known as import aliasing to solve this problem:

require file as f

read(val) =>
  /[0-9]+/    : val.to_integer()
  *           : val

print(f.read('autobots.txt'))

In this example, we have added a suffix to the require declaration, as f. This tells Loop to box all the functionality in module file into a namespace called f (you can call this whatever you like, even file). Now, when we use functions from the file module, we prefix the function call with the alias:

require file as ~f~

read(val) =>
  /[0-9]+/    : val.to_integer()
  *           : val

print(~f.read~('autobots.txt'))

=> Hot Rod
=> Arcee
=> Ironhide
=> Perceptor
=> Optimus Prime

Now when we run the program, Loop correctly resolves the read() function against module file and we get our desired list of autobots.

First module

So far, all the programs we've written have not been in any particular module. By default, Loop puts your program into a module called _default (inside the Loop shell, this is the _shell module). This is a convenience that works well for a small script, but isn't really practical once you have an application consisting of several interacting loop scripts.

To name your module, place a module declaration at the very top of the script:

~module botreader~

require file as f

read(val) =>
  /[0-9]+/    : val.to_integer()
  *           : val

run ->
  print(f.read('autobots.txt'))

Here, we've taken the last example and added a module declaration. To make this work properly, you must save this file as botreader.loop. The module name must match the file name in most cases (there are some exceptions which we will look at later).

Now we can use the botreader module from other loop programs, easily:

require botreader

run()

=> Hot Rod
=> Arcee
=> Ironhide
=> Perceptor
=> Optimus Prime

Note that this only works if both loop scripts are in the same directory. Modules bundled with loop (such as file) or in packages do not need to be in the same directory. We'll see what this means in the next section.

Packaging

To be documented.

Prelude module

In a lot of these examples, we've used functions like print() and last() quite liberally. These are not declared anywhere, nor are we explicitly importing them from any module with a require directive. How then are they popping up in our code?

The answer is the prelude module. All Loop programs implicitly import a built-in module called prelude, which provides a bunch of common functionality for convenience. These are some of the functions that are part of prelude:

General functions

List & String functions

You can inspect the full source code of the prelude module here: https://github.com/dhanji/loop/blob/master/resources/loop/lang/prelude.loop

Just as we saw with the file module and our custom read() function, you can also have collisions with the prelude module. While you can't prevent the import of prelude, you can definitely alias it to prevent function name collisions.

require prelude as ~pre~

print(val) ->
  ~pre.print~(val.toUpperCase())

print('hi there')

=> HI THERE

Hierarchical modules

To be documented.

Importing jars

We've seen how to refer to Java classes, constructors and static methods in the previous sections. These suffice for any of the APIs provided with the JDK itself, however if you want to import third-party Java libraries, you need to provide them to your programs first.

If using the loop shell script bundled with the launcher, this is very simple. First, create yourself a directory called lib under the directory where your Loop program files are (this can be anywhere in your system, and can be completely independent of where you unzipped the Loop distribution). Now, place any third-party .jar files in the lib directory and they will automatically be loaded when running your program.

$ ls lib/
joda-time-2.0.jar

In the above example, I have placed the third-party jar for joda-time in my lib directory. Now when I run any Loop program that refers to joda-time classes, it will load them from the provided jar file. Here's a sample program (called time.loop):

require `org.joda.time.DateTime`

print(new DateTime())

To run it, use the same command line as you are familiar with (but make sure to do it from the same directory as the time.loop program file):

$ loop time.loop
~2012-05-31T21:40:44.554+10:00~

The output is a string representation of joda-time's DateTime object, holding the current system time. You can add any number of jar files this way, to create a library for your Loop program.

Classpath

If for some reason you don't want to use the lib directory or want to import jars in another location, you can directly launch the required jars using a java command line as follows:

$ java -cp /path/to/loop.jar:[deps] loop.Loop time.loop

Note that the deps must be separated by a : and remember to replace /path/to/ with the real path to your unzipped loop distribution.

Concurrency

Much of Loop's design is based around the idea that programs are increasingly running on multiple CPUs (or cores) and that there is a pressing need for robust and simple tools to create concurrent programs. Loop takes the position that these tools are best provided by the language itself, and that the nitty-gritty of particular low-level concurrency constructs (mutexes, semaphores, CAS instructions, etc.) should not intrude on everyday programming.

This is not to say that you should not familiarize yourself with these concepts, and know them in reasonable detail, but that you should not have to work with them for most practical uses. One analogy might be that while an astronaut should be reasonably familiar with the physics of space flight, she should not have to work out the Newtonian propulsion equations to ignite each stage of a rocket during flight. A lever or button would suffice.

There are two principal constructs in loop for working with concurrency: channels and memory (or cell) transactions. Both are well-known, and well-established tools for writing concurrent programs, but Loop takes them a step further and integrates them deeply into the language itself, and with each other.

Channels and message-passing

You may be familiar with message-passing as a framework for coordination between multiple concurrent processes. Many languages and libraries provide something like this. The theory is that if concurrent processes only ever communicate by sending messages to each other, then there is no shared mutable state, and limited potential for memory corruption, deadlocks and starvation.

Channels

Channels are an abstraction over message passing and thread pools that allows you to work in terms of events and event handling functions. A channel consists of an event queue and a handler function, backed by a pool of threads. Let's take a look:

require channels

print_message(msg) =>
  -1        : @shutdown
  *         : print(msg)


main ->
  channel(@printer, print_message, {:}),
  @printer.send(i) for i in [1..10],
  @printer.send(-1)

The first thing you'll notice is that we're importing a module called channels; this is a built-in module that ships with loop to provide all the concurrency tools you'll need for working with channels.

Second, we have a pattern-matching function, print_message() that takes a single argument and in the general case, just prints it out. If this argument is equal to -1, however, the function returns a symbol named @shutdown. This is a special symbol that the channels API recognizes and responds by shutting down the channel.

Finally we have a few lines in main() to establish a channel and send it some events to process:

main ->
  ~channel(@printer, print_message, {:}),~
  @printer.send(i) for i in [1..10],
  @printer.send(-1)

Calling the function channel() establishes a channel called printer, whose event function is print_message(). Any events received by this channel will be dispatched to this function. Lastly, the empty map - {:} is used to say that we want the default channel options (we'll see how to use some of these options later).

More generally, this function has the following signature:

channel( < name >, < function >, < options > )

Finally, to send events to the channel we've just created, we use the send() function. This function takes two arguments, the name of the channel and a message to send. This message can be any object, but it must be immutable. Loop will complain if you try to send a mutable object to a channel.

main ->
  channel(@printer, print_message, {:}),
  ~@printer.send(i) for i in [1..10]~,
  @printer.send(-1)

When the messages arrive, they are dispatched as individual events to the event function. This is done in a fair manner over a thread pool that is shared between all channels. Note that in this example, I'm sending a sequence of numbers from 1 to 10, individually.

This produces the following output:

6
4
3
2
9
5
7
8
10
1

The numbers appear in a random order, which might be surprising, but in fact this is channel fairness in action. When we send the 10 numbers, they get pumped into the channel's event queue, and then the channel's event function goes hell for leather trying to process the queue. Since these events are being processed in parallel, the order of output is unpredictable (and random).

Serialized channels

If we take the previous example, and make one small change to the channel options:

require channels

print_msg(msg) =>
  -1        : @shutdown
  *         : print(msg)


main ->
  channel(@printer, print_msg, { @serialize : true }),
  @printer.send(i) for i in [1..10],
  @printer.send(-1)

Then we get the following output instead:

1
2
3
4
5
6
7
8
9
10

Perfectly ordered. This is because we specified that the channel is a serialized channel, meaning that events are dispatched exactly one at a time. Even if there are many threads available in the worker pool, a serialized channel will only use a maximum of one thread ensuring that all events in its queue are processed serially.

This is an extremely useful design pattern, particularly when you are dealing with large numbers of users, for example. Since channels are lightweight (much lighter-weight than threads), you can allocate one channel per user and fire off events as they arrive. You don't have to worry about synchronizing code for a single user, because the serialized channel ensures that it happens in sequence.

Channels can be shutdown and re-established as many times as you like. But it is recommended that you hold a channel open for the lifetime of the application. Coordinating their orderly shutdown and restart in the middle of a program's life can be tricky, so only do it if you really have no other way.

Channel memory

Serialized channels also provide the ability to keep some data around for the next event that arrives. This allows you to accumulate state incrementally as each event arrives and use this for internal processing. Since this state is mutable (i.e. you can modify it), it is only visible inside the channel. Here's an example of a simple counter using serialized channels and memory:

require channels

increment(msg) =>
  -1        : @shutdown
  @print    : print(mem[@count])
  *         : mem[@count] = mem[@count] + 10
  where
    mem     : ~channel_memory()~


main ->
  channel(@printer, increment, { @serialize : true }),
  @printer.send(i) for i in [1..10],
  @printer.send(@print),
  @printer.send(-1)

Notice the function channel_memory(), which obtains a reference to the current channel's memory. We save this to a local reference, mem for easy access.

Now, we've changed the printer slightly from the previous iteration, instead of merely printing the message, we increment a counter (a slot in mem called @count) by 10 each time.

Finally, before shutting down, we send a special message @print, which just dumps the value of @count in the channel memory to the console. Running this function produces:

100

You can store anything you like in channel memory, it acts just like a map. And the data will stick around until you either set it to Nothing or shut down the channel. But remember that this will not be accessible in other channels or even in main():

require channels

increment(msg) =>
  -1        : @shutdown
  @print    : print(mem[@count])
  *         : mem[@count] = mem[@count] + 10
  where
    mem     : channel_memory()


main ->
  channel(@printer, increment, { @serialize : true }),
  @printer.send(i) for i in [1..10],
  print(\channel_memory()\.count)
  @printer.send(-1)

=> \#error: illegal shared memory request\

Of course, each serialized channel gets its own channel memory, so whatever data you set in one serialized channel won't be available in another serialized channel's memory either.

Concurrent channels

Let's go back to non-serialized (or concurrent) channels. These run as fast as possible and in parallel with one another. It is important to remember they have no channel memory. If you want to keep state around and modify it in parallel you'll have to use cell transactions, which we'll look at later.

Now, all channels run on the same shared thread pool. This thread pool starts with a small number of threads and expands with the rate of events being sent to all channels. If a particular (concurrent) channel is receiving too many messages and using up the majority of threads, the Loop runtime will attempt to balance the workload by making busy channels yield some time.

However, it is not always desirable to rely on this--the yielding only occurs between events, and you may wish to have a more rigid (or dedicated) balance of threads per channel. Loop allows any concurrent channel to have its own dedicated worker thread pool by specifying the workers configuration option:

require channels

print_msg(msg) =>
  -1        : @shutdown
  *         : print(msg)


main ->
  channel(@printer, print_msg, { @workers : 4 }),
  @printer.send(i) for i in [1..10],
  @printer.send(-1)

Here, I'm ensuring that channel printer gets its own dedicated thread pool consisting of 4 worker threads. All events sent to this channel will then be dispatched only on this thread pool. Note that you can simulate a serialized channel by specifying only 1 worker:

require channels

print_msg(msg) =>
  -1        : @shutdown
  *         : print(msg)


main ->
  channel(@printer, print_msg, { ~@workers : 1~ }),
  @printer.send(i) for i in [1..10],
  @printer.send(-1)

Then the output produced is:

1
2
3
4
5
6
7
8
9
10

This incidentally ensures that only one event gets processed at a time, which is similar to what we saw with serialized channels. Of course serialized channels are much more sophisticated than this--they have channel memory, and consume less resources, balancing fairly on the global thread pool, but it is still an interesting similarity.

Cells and memory transactions

To be done. This feature is still a WIP and will be documented when complete.