Skip to content

Extensions

joakim edited this page May 18, 2023 · 270 revisions

These are some possible extensions to the language.

Mutation

The mutation directive allows block names to be redefined and collections to be mutated.

kesh 2021 + mutation

Block names

Names in lexical scope may be marked as variable with let and redefined with set.

let mutated: false
set mutated: true

Object fields

Object fields may also be marked as variable, and redefined using dot notation.

joe: [ let name: "Joe", age: 27 ]
set joe.name: "Joseph"

Mutable objects

The entire object may be marked as mutable with the * unary operator.

joe: *[ name: "Joe", age: 27 ]
set joe.age: 28
set joe.nickname: "Joey"

Mutable collections

Arrays and other indexed collections may also be marked as mutable.

people: *array[]
people.push joe

Keyed collections are always mutable.

Unpacking

Multiple object fields can be redefined in one go by unpacking to the object.

set joe.(name, age): ('Joey', 28)

The above line achieves the same as:

(name, age): ('Joey', 28)
set joe.name: name
set joe.age: age

Unpacking also works with objects and arrays.

set joe.[name, age]: [ name: 'Joey', age: 28 ]
set joe.(name, age): ['Joey', 28]

It's also possible to unpack all of another object's enumerable fields with the spread/rest operator ....

set joe.[...]: [ name: 'Joey', age: 28 ]

This is equivalent to ECMAScript's Object.assign.

Augmented assignment

Within a set's expression, the placeholder operator _ refers to the name's existing value, enabling augmented assignment.

let count: 0
set count: _ + 1
--> #number 1

Existential assignment is also useful.

set options.duration: _ ? 100  -- provide fallback by setting to 100 only if #none

Increment and decrement

One can also use helper functions increment and decrement on mutable #numbers.

increment count
--> #number 2
decrement count
--> #number 1

Functional programming

The fp directive enables compose and pipe operators, and imports all functions from rambda into the module scope.

kesh 2021 + fp

Compose operators

The forward >> and backward << composition operators compose unary functions.

square >> negate >> print  -- forward composition
print << negate << square  -- backward composition

Pipe operator

The pipe |> operator allow values to be piped into expressions.

The ^ placeholder is used to place the piped value inside the right-hand expression.

Pipes are equivalent to the TC39 proposal for smart pipelines.

answer: [1, 2, 3, 4]
    |> sum            -- unary function application
    |> ^ * 4 + 2      -- expression
--> #number 42

Piping into a composed function:

answer |> square >> negate
--> #number -6.48074069840786

Placeholder

The placeholder directive enables extra functionality for the _ placeholder.

When unpacking, it can be used to ignore a value.

kesh 2021 + placeholder

(_, answer): (true, 42)

Globals

The globals directive imports all fields of the global object into the module scope.

kesh 2021 + globals + mutation

set global.answer: 42
answer
--> #number 42

Math.sqrt answer
--> #number 6.48074069840786

Return

The return directive enables the return statement for explicit or early termination of functions.

kesh 2021 + return

sqrt: (number) ->
    if number < 0 then return
    -- todo: calculate the square root of number

Async

The async directive enables the async and await keywords.

kesh 2021 + async + globals

sleep: async (seconds) ->
    Promise (resolve) -> set-timeout(resolve, seconds * 1000ms)

print "Hold up"
await sleep 1s
print "Hey!"

Pattern matching

The match directive enables pattern matching using the match keyword.

kesh 2021 + match

stage: match person.age
    0        -> 'infant'
    1 | 2    -> 'toddler'
    3..17    -> 'kid'
    18..59   -> 'adult'
    60..123  -> 'senior'
    124..    -> 'kurzweil'
    #any     -> 'invalid'

Imperative loops

The loop directive enables imperative loop statements with support for break and continue statements.

kesh 2021 + loop + async + mutation

-- infinite loop
loop
    await client.listen()

-- repeat a block x times
loop 3
    print "hey"

-- loop over an iterable's elements
loop 1..10 as number
    if number mod 3 = 0 then continue
    print number

-- loop over a collection's own enumerable fields
loop score as (player, points)
    print("{ player.name }: { points } points")

-- traditional while loop
loop while everything = 'ok'
    print "It's all good"

-- untraditional until loop
loop until done
    -- todo: process something
    if nothing-to-do then set done: true

-- traditional for loop
loop for { let i: 0, i < 9, increment i }
    print i

loop iterates an iterable or a #number by default.

loop while and loop until loops on the basis of a predicate.

loop for is equivalent to ECMAScript's for loop, except using a block to specify iteration parameters.

Loops are statements, not expressions, and don't return a value. However, they can be named, making it possible to break or continue a specific loop by name. Yes, this happens to be identical to ECMAScript's label statement.

outer: loop {
    print "Entered the outer loop"
    
    inner: loop {
        print "Entered the inner loop"
        
        -- This would break only the inner loop
        --break
        
        -- This breaks the outer loop
        break outer
        
        print "This point will never be reached"
    }
    
    print "Exited the outer loop"
}

--> #text "Entered the outer loop"
--> #text "Entered the inner loop"
--> #text "Exited the outer loop"

Generators

The generator directive enables generator functions, which are regular function producing values with the yield keyword.

kesh 2021 + generator + loop + mutation

id-maker: gen () ->
    let index: 0
    loop
        yield index
        increment index

generator: id-maker()
generator.next().value  --> 0
generator.next().value  --> 1
generator.next().value  --> 2

The yield-from keyword delegates to another generator or iterable, equivalent to ECMAScript's yield*.

func-1: () \-> yield 42
func-2: () \-> yield-from func-1()
generator: func-2()
generator.next().value  --> 42

With

The with directive enables with statements for generators, similar to Python's with statement.

This is useful in a number of scenarios. A common one is to ensure that a resource is released after use.

kesh 2021 + with

open: async gen (path) \->  -- async non-returning generator function
    file: await fs.open(path)
    print "file opened"

    yield file

    file.close()
    print "file closed"

with await open("./hello.txt") as file
    print file.contents

--> #text "file opened"
--> #text "Hello, world!"
--> #text "file closed"

Regular expressions

The regex directive enables regular expressions as an embedded language.

kesh 2021 + regex

Regular expressions are written as XRegExp expressions within code or text literals tagged with regex.

Code literals compile down to native RegExp objects.

re`(?im)^[a-z]+$`  -- leading mode modifier
re('gi')`\b(?<word>[a-z]+)\s+\k<word>\b`  -- flags as argument

Text literals are evaluated at runtime, which require the XRegExp module to be automatically imported.

hours: re'1[0-2]|0?[1-9]'
minutes: re'(?<minutes>[0-5][0-9])'
time: re"\\b { hours } : { minutes } \\b"  -- interpolation

Exceptions

The exception directive enables exceptions and try…avert statements.

The fail keyword is used to "throw" or "raise" an exception.

The avert keyword is used to "catch" an exception thrown/raised within a try block.

kesh 2021 + exception

check: (roentgen) ->
    if roentgen > 3 then fail Error "Dangerously high radiation levels"

try { check 3.6 } avert (cause)
    console.info Error("Well, that's not great, but it's not horrifying", [cause])

As the keywords imply, an exception will cause the program to fail unless averted.

As the example implies, a fail should not be handled carelessly.

Contracts

The contract directive enables Eiffel like require and ensure statements within functions.

Each of the statements evaluate one (inline) or more (block) conditions (expressions) that must be fulfilled.

For the ensure statement, result refers to the return value of the function.

kesh 2021 + contract + fp

sqrt-int: (x) ->
    require x >= 0
    ensure
        result * result <= x
        (result + 1) * (result + 1) > x
    
    x |> sqrt >> floor

Defer

The defer directive enables the defer statement, similar to that of Go.

kesh 2021 + defer + loop

count: () ->
    print 'counting'
    
    loop 1..3 as number
        defer print number
    
    print 'done'

count()
--> #text "counting"
--> #text "done"
--> #number 3
--> #number 2
--> #number 1

Order of operations

The order directive enables explicit order of operations for arithmetic operators, also known as PEMDAS or BODMAS.

It activates a source code formatter that enforces order of operations by inserting parens, respecting any existing groups.

This improves readability by removing ambiguity when order of operations is expected.

kesh 2021 + order

-- before save:
print 1 + 2 * 3 - 4 / 5
print (1 + 2) * 3 - 4 / 5

-- after save:
print 1 + (2 * 3) - (4 / 5)  --> 6.2
print (1 + 2) * 3 - (4 / 5)  --> 8.2

There's no implicit order of operations.

Without order enabled, mathematical expressions are evaluated left-to-right, with parens used for grouping.

print 1 + 2 * 3 - 4 / 5    --> 1
print 1 + 2 * 3 - (4 / 5)  --> 8.2

Profiles

Directives may also be grouped into a named profile. The following are provided by the compiler.

Standard

The standard profile enables all the standard extensions for the active version of the language.

kesh 2021 standard

Some candidates for inclusion:

Web

The web profile imports window from the global object.

kesh 2021 web

alert "Hello, web!"

To open the print dialog, window.print() must be used, as print is an alias for console.log in module scope.

Strict

The strict profile enforces certain restrictions.

kesh 2021 strict

Archaic

The archaic profile enables obsolete keywords that went out of fashion a long time ago.

kesh 2021 archaic

const answer: 42

function query(question): {
    if (~question) {
        return answer
    } else {
        throw new Error('No question, no answer')
    }
}

Please don't.

Definition

The define directive enables definition of language features such as keywords and operators.

It could be done something like this (nothing here is written in stone):

kesh 2021 + define

define statement 'return': (exp) ->define postfix-operator '?': (lhs) ->define binary-operator '?': (lhs, rhs) ->define statement 'loop': (exp) ->