Docs Examples Standard Library Contribute GitHub Reddit


Where your fantaZies come true

Why create a new programming language?
If you want to know why Z is designed the way it is, go here. But if you want want to know why Z was built in the first place, keep reading. Transpile to JS is pretty popular these days. JS has become the de facto assembely of the web, because virtually no one codes in "Vanilla JS" these days. A plethora of languages (CoffeeScript, LiveScript, Elm, ClojureScript, TypeScript) have all emerged on the sole basis of compiling to JS. However, we can seperate the languages that transpile to JS into two categories:

1. Syntatic Sugar

Languages that add new compile-time features to JavaScript, but no knew runtime features. These languages interop with JavaScript nearly seemlessly, but they fail to deliever certain features that require runtime additions, including operator overloading, pattern matching, better concatenation rules, etc.

Examples:

  • CoffeeScript
  • LiveScript
  • ESNext + Babel
  • TypeScript

2. JVM Languages (JavaScript Virtual Machine)

These languages add new compile-time and run-time features to JS, and use JavaScript more like assembely code than a target language. While these languages can add any features they want, they often have limited and clunky interop with JS, and are all are limited to the frontend, whereas Syntatic Sugar languages can be used anywhere.

Examples:

  • Elm
  • Haxe
  • ClojureScript
Z mixes these two sides of transpiled languages. It has it's own runtime, with custom operators, a standard library, goroutines, and other cool out-of-the-box features. However, it also can call any JavaScript method, uses the same APIs as JavaScript, and has a generally similar syntax, making it easy for JavaScript users to pick up. Z is also one of the only langauges that transpiles to JS, comes with a custom runtime, and functions on the backend!
So what are you waiting for? Are you ready to transform your backend experience with amazing features and a cool runtime and standard library? Jump in and learn some Z!

*Z is still in rapid development. There may be bugs that pop up in the Z Compiler as you develop your application. Report them here.

If you still have any problems with Z, or want any new features added, email n8programs@gmail.com.

Getting Started

This tutorial assumes that you have both node and npm installed. If you don't, you can install node here and npm comes with node. If you prefer Yarn (the faster, prettier alternative, you can download it here)

To start, enter a terminal session. Every Z package on npm is namespaced under @zlanguage. Install the Z compiler as so (make sure to install version 0.4.5, as it is the last stable release):

$ npm install -g @zlanguage/zcomp@0.4.5
Now, in order for the compiler to function, you must also install globby:
$ npm install -g globby

Or, with Yarn:

$ yarn global add @zlanguage/zcomp@0.4.5 globby

That wasn't too hard! Now, to experiment with Z, let's launch a REPL.

You can launch a Z REPL with:

$ zcomp repl

If all goes well, you should see the following:

zrepl>

Basic Expressions and Math

All expressions defined here are meant to be executed from a REPL.

Let's start by creating a simple math expression:

3 + 2

The REPL will print back 5.

Order of operations works:

3 + 2 * 7

The REPL gives you back 17, not 35.

The following mathematical operators are supported at the following orders of precedence:

^ pow

* / %

+ -

pow is the same as ^ except that ^ is left-associative while pow is right-associative.

Number Extensions

Besides typical JavaScript number literals, Z supports the following extensions to them: A number can have:

  • 1_000 Underscores
  • 0x100 Hexadecimal, Binary, and Octal prefixes!
  • 10bytes Trailing characters
  • 1.somePropertyHere Trailing refinement

More Expressions

To start, you may have noticed that inputting a raw number without any math is considered an error in the Z REPL. While this may seem peculiar, this is to avoid "useless expressions", like randomly putting a string or a number on some code.

Single line Comments in Z are denoted with #:

# I'm a single line comment!

Block comments are denoted with /* and */

Strings in Z are easy enough to reason with:

"Hello" ++ " World" # ===> "Hello World"

Z performs coercion, but its coercion rules make more sense than JavaScript's:

"9" + 2 # ==> 11
"9" ++ 2 # ==> "92"

Booleans also make sense:

true and false # ==> false
false or true # ==> true

Symbol literals are denoted with @, so @x is the same as Symbol.for("x"), and @@iterator is the same as Symbol.iterator.

Regexps literals are like JavaScript, but they are denoted with ` rather than /. So `x`g is the same as /x/g

Now that you have touched the surface of Z, it's time to take it up a notch and start writing files in Z.

Your First File

Now that you've tested Z out, create a directory, call it ztest:

$ mkdir ztest

For each directory you create that will use Z, you must install the Z Standard Library:

$ npm install @zlanguage/zstdlib --save

That really wasn't that much setup.

Now, create a new file, call it hello-world.zlang:

$ touch hello-world.zlang

Launch your favorite IDE, open helloworld.zlang, and type:

log("Hello World")

Execute the file with:

$ zcomp run helloworld.zlang

It should print out Hello World to the terminal.

Files can hold more advanced expressions than the REPL, and have statements in them two. From now on, all examples assume that they are being typed in a file. Some examples will contain features that don't work in the REPL.

Variables

Variables in Z can hold any value, they are not constrained to one type of value.

Reassignable variables can be declared with let:

Note that : is the assignment operator.

let x: 5 # ==> x is now 5
x: "Hola Mundo" # ==> x has changed types
let y: undefined # ==> You must assign a variable when you initialize it.
y: Math.random() # ==> Put something in y

Constant variables are declared with def. Constant variables cannot be reassigned, but their internal value can still change:

def x: 5 # ==> This looks familiar.
x: "Hola Mundo" # ==> Runtime error.
                

Finally, hoisted variables (akin to variables declared with var in JavaScript) are declared with hoist:

log(x) # I can see x from all the way up here!
hoist x: 5
                

So to map Z's assignment statements to their equivalents in JS:

  • let - let
  • def - const
  • hoist - var

Invocations, Arrays, and Objects

You've already seen some invocations in Z (of log and Math.random)

As will all built-in data types in Z, Z functions map right to their JavaScript equivalents. Which means calling a function in Z with () transpiles to calling a function in JavaScript with ():

log("Hola mundo!") # Log acts like console.log.
console.log("Hola mundo!") # Does the same thing.
Math.random() # Let's call another JS function
Date.now() # They all work fine!

Collections in Z

Z supports numerous flexible collection literals to represent objects and arrays, the simplest being brackets:

[1, 2, 3] # Arrays literals are just like JavaScript
let x: 5
[
    "hola": "mundo", # Objects are a bit different, object properties in Z are computed, so quotes are required.
    x # Expands to "x": 5 if there are other properties in the object
]
[] # Empty Array
[:] # Empty Object

Parentheses can also be used to denote arrays, and brackets can denote arrays or objects. Arrays constructed from parentheses and objects constructed from brackets can be used in destructuring (which will be covered later):

(1, 2, 3) # Array Literal
{1, 2, 3} # Array Literal
{
    "x": 3
} # Object Literal
() # Empty Array Literal
{} # Empty Array Literal

Range literals correspond to arrays. They can be written in several different fashions:

1 to 5 # [1, 2, 3, 4, 5]
1...5 # [1, 2, 3, 4, 5]
1 til 5 # [1, 2, 3, 4]
1 to 5 by 2 # [1, 3, 5]
1 til 5 by 2 # [1, 3]
                

                

When creating range literals, you can

When invoking a function, you can emulate named parameters with an implicit object (like in ruby):

Point(x: 3, y: 4) # Is like...
Point([
    "x": 3,
    "y": 4
])

Property Access is akin to JavaScript:

x.y # Alphanumeric property access
x["y"] # Computed property access (any expression can go in the brackets)
x..y # Conditional property access (only works if x isn't falsy, akin to x && x.y)

Control Flow

Z supports very simple control flow. It may seem primitive, but for most imperative constructs, it's all you need. When coding Z procedurally (rather than functionally) these control flow statements will be your best friends.

Z supports typical if, else if, and else statements:

let x: 3
if x = 3 { # Check for equality
    log("Three times is the charm!")
} else if x > 0 { # Check for positivity
    log("X is positive.")
} else {
    log("X is feeling negative today.")
}

Z also has the ternary operator, though its syntax is more readable than most programming languages:

# An easier way to write the above example
let x: 3
log(
    if (x = 3) "Three times is the charm!"
    else if (x > 0) "X is positive."
    else "X is feeling negative today."
)

You can simplify one line if statements by putting the if at the end of the line (Ruby-style):

log("I'm feeling lucky!") if Math.random() > 0.5
                

In the looping department, Z supports loop, which is the equivalent of a while(true) loop. You exit a loop with break:

let i: 0
loop {
    if i > 9 {
        break
    }
    log(i)
    i: i + 1
}

That's it. No fancy potpourri. Just one conditional structure and one type of loop. However, this section only covered Z's imperative control flow structures. You'll see more functional ones soon.

Intro to Functions

Functions in Z are created with the func keyword. Z supports anonymous functions only, like CoffeeScript. You can name functions by binding a function to a constant variable. Otherwise, parameters and return statements are rather similar:

def add: func (x, y) {
    return x + y
} # Declare a simple function
setTimeout(func () {
    log("Around one second has passed!")
}, 1000) # Passing a function as a parameter
def something: func () {
    # Return something awesome
}() # IIFE
def boundFunction: func () {

}.bind(someThisValue) # Functions can have properties accessed on them because they are just objects will an internal [[Call]] property
                

Default parameters are created with the : operator, and rest parameters are created with the ... operator.

def add: func (x: 0, y: 0) { # Defaults
    return x + y
}
def sum: func (...xs) {
    return xs.reduce(func (t, v) { # We'll make this example more concise later.
        return t + v
    })
}

If a function only consists of one return statement, the curly brackets and return may be omitted:

def sum: func (...xs) xs.reduce(func (t, v) t + v)

You can mark variables declared within a one-line function (a function with an implicit return statement) to be inferred, by ending them with an exclamation point:

def sum: func (...xs) xs.reduce(func t! + v!) # We'll see how to make this even more concise later.

You can pipe a value through multiple functions via |>:

3 |> +(1) |> *(2) |> log # Logs 8

You can use >> and << for function composition (as in Elm).

You can partially apply functions with @

def square: Math.pow(@, 2)
def logSquare: log >> square
logSquare(10) # Logs 100

A standalone . creates an implied function for property access and method invocations. Currently, implied functions and partial application via @ cannot be mixed. For example:

users.map(.name) # Get the name property of users
[1, 2, 3, 4, 5].map(.toString(2)) # Get the binary representation of these numbers (in string form).
                

That's pretty much all there is to know about basic functions in Z.

Exceptions

The first thing Z clarifies is that Exceptions are not baseballs. For some reason, rather than "raising" an issue, you would "throw" it. That makes no sense at all. And then, to resolve the issue, someone would not "settle" it, but "catch" it. You can't play a friendly game of catch with exceptions. Z's choice of keywords is more intuitive than throw and catch:

try { # Attempt to do something
    raise "Something contrived went wrong" # String coerced into an error object
} on err { # When something bad happens
    settle err # Explicitly tell Z that the error has been settled/resolved.
}

At this point you are probably asking: why explicitly settle an error? The reason is, explicitly settling an error allows you to put time and thought into how to settle it, and what countermeasures to take. If you forget to settle an error, Z will raise a runtime error. This helps with making Plan Bs when something goes wrong.

A try can only have one on clause. Handle type checking of the exception in the on clause.

Z has exception handling for JavaScript interop, but please don't overuse it. Failing to parse an integer should not cause an exception.

Modules

Z's module system is closely related to JavaScript's. A simple example will demonstrate this. Create a file called exporter.zlang in your test directory, and another file called importer.zlang in that same directory. Now, in exporter.zlang, type:

export 3

In importer.zlang, type:

import num: "./exporter"         
log(num)

Now, to transpile exporter.zlang, and not immediately run it via the compiler, use the command:

$ zcomp transpile exporter.zlang

And:

$ zcomp transpile importer.zlang

To run the code:

$ node importer.zlang

You should see a 3 printed out.

To further elaborate, each module in Z can export one thing, which is implicitly stoned (Z's version of Object.freeze) when exported.

Imports in Z are similar to JavaScript ones, except that from is replaced with ::

    import something: "./somewhere"
    import fs # This shorthand becomes:
    import fs: "fs"
    import ramda.src.identity # Becomes:
    import identity: "rambda/src/identity"

In order to export multiple things, you can just export an object:

export [
    "something": cool,
    "very": cool,
    cool,
    "some": other.thing
]

As you can see, Z modules are (pretty) easy to work with. We'll see a cool way to import multiple things from a module that exports an object in the next section.

Pattern Matching

Z comes with a default ternary operator:

let happy = true
let mood = if (happy) "good" else "bad" # if (cond) result2 else result2
let moodMessage = 
    if (mood = "good") "My mood is good."
    else if (mood = "bad") "I'm not feeling good today."
    else "Unknown mood." # Chaining ternary operators.

However, for advanced conditional checks, this fails to be sufficient. That's where Z's pattern matching comes into play. The match expression at its simplest can match simplest can match simple values:

let moodMessage = match mood {
    "good" => "My mood is good",
    "bad" => "My mood is bad",
    _ => "Unknown mood" # _ is a catch-all
}

Patten matching is more powerful than this though. It's not limited to matching primitives. You can also match exact values that are arrays and objects:

let whatItIs: match thing {
    [1, 2, 3] => "An array of [1, 2, 3]",
    ["x": 3] => "An object with an x value of 3",
    _ => "I don't know what thing is."
}

You can also match types with pattern matching:

let contrived: match someExample {
    number! => "A number.",
    string! => "A string.",
    array! => "An array",
    _ => "Something else."
}

If you want to capture the value of a certain type, use ! like an infix operator:

let times2: match thing {
    number!n => n * 2,
    string!s => s ++ s,
    _ => [_, _]
}

Now, to capture elements of arrays, use ( and ):

def arrResult: match arr {
    (number!) => "An array that starts with a number.",
    (string!s, string2!s2) => s ++ s2,
    (x, ...xs) => xs, # xs represents the rest of the array, which excludes the first element in the array
    _ => []
}

Objects can be matched with the { and } characters:

def objResult: match obj {
    { x: number!, y: number! } => "A point-like object.", # Match an objects with x and y properties
    { 
        name: string!name, 
        age: number!age, 
        car: { 
            cost: number!, 
            brand: string!brand
        }
    } => "A person named " ++ name ++ " that is " ++ age ++ " years old. He/She owns a " ++ brand ++ " type of car.",
    _ => "Some other thing"
}

To match a number in between other numbers, use range literals:

def typeOfSquare: match square {
    { size: 1...10 } => "A small square.",
    { size: 11...20 } => "A medium square.",
    { size: number! } => "A big square.",
    _ => "Something else."
}

The object and array delimiters in pattern matching work as destructuring too:

def (x, y): [3, 4] # x is 3, y is 4
def {x, y}: [
    "x": 3,
    "y": 4
] # x is 3, y is 4
                

You can define blocks to be associated with different patterns, for example:

match num {
    1 => {
        log("Num is 1.")
        log ("I love the number 1.") # You can put multiple lines in a "block"
        return "YAY!" # Blocks are wrapped into functions, so you can return from them.
    },
    _ => "Not 1 :("
}
                

You can define your own custom pattern matching conditions with predicates. To start, define some functions that return a booleans:

def even: func x! % 2 = 0
def odd: func x! % 2 = 1

Then, use the ? at the end of the function name inside a match body to use the predicate:

match num {
    even? => "An even number.",
    odd? => "An odd number.",
    number! => "Some other number.",
    _ => "Something else."
}
                

The most advanced form of custom pattern matching is the extractor. It allows you to not only perform a conditional check on data, but to perform custom matching on it.

Let's start by defining a simple email function:

def Email: func user! ++ "@" ++ domain!
                

Then, we can defined a extract method on email. This extract method should return an array if there is a pattern to be matched, or undefined, if there is no match:

Email.extract: func (str) if (str.includes("@")) str.split("@") else undefined
                
def myEmail: "programmer@cloud.com"

match myEmail {
    Email(user, domain) => log(user, domain), # Logs programmer, cloud.com
    _ => log("Invalid email.")
}
                

As you can see extractors and predicates add greater flexibility and power to pattern matching.

Runtime Types

Z supports numerous ways to create runtime type checks. Each object in Z can specify its "type" by having a function called type:

[
    "x": 5,
    "y": 5,
    "type": func "Point"
]

You can find out something's type using the built-in typeOf function:

typeOf(3) # ==> "number"
typeOf([1, 2, 3]) # ==> "array"
typeOf([
    "x": 5,
    "y": 5,
    "type": func "Point"
]) # ==> "Point"

You can check that a parameter passed to a function is of a certain type at runtime (checking is done behind the scenes with typeOf):

def add: func (x number!, y number!) { # Note that you can't mix type annotations with default values and rest/spread
    def res: x + y
    return res
}

! isn't actually part of the type. It just denotes that a type is present.

You can also add return type annotations:

def add: func (x number!, y number!) number! {
    def res: x + y
    return res
}

You can also validate that the right-hand side of an assignment is of a certain type:

def add: func (x number!, y number!) number! {
    def res number!: x + y
    return res
}

This works great for simple functions, however you may need to implement more complex ones. This is made possible by the enter and exit statements:

def readPrefs: func (prefs string!) {
    enter {
        prefs.length < 25
    }
    def fileHandler: file.getHandler(prefs) # Some imaginary file system.
    # Do some stuff
    return something
    exit {
        fileHandler.close() # Clean up the file handler, exit is like finally and must be the last statement in a function.
    }
}

enter is a block of code that contains comma-separated conditions, all of which must be true when the function starts:

def readBytes(bytestream Bytestream!, amount number!) { # fictional type Bytestream
    enter {
        bytestream.size < amount,
        amount < 100,
        amount > 0,
    }
    # Do stuff...
}

exit pretty much the same as enter, except it is executed at the end of the function, to see if certain conditions have been met. exit must be the last statement in a function.

A function may only have one enter statement and one exit statement.

loop Expressions

loop expressions are directly inspired by Scala. They are based on Scala's for expressions, and they may resemble list comprehensions in some languages.

To start, use the operator <- to map over a list:

def xs: [1, 2, 3]
def result: loop (x <- xs) x * 2 # Result is [2, 4, 6]

You can add predicates using a form of if:

def xs: [1, 2, 3]
def result: loop (x <- xs, if x % 2 = 0) x * 2 # Result is [2, 6]

You can iterate over multiple lists by inserting multiple <-s:

# Range literals: 1...5 is [1, 2, 3, 4, 5]
def result: loop (x <- 1...10, y <- 1...10) [x, y] # Matrix of numbers 1 to 10
                

Using all of this, you could define a flatMap function:

def flatMap: func (f, xs) {
    return loop (
        x <- xs,
        y <- f(x)
    ) y
}

Note that you cannot start a line with a loop expression, as it will be confused with the imperative loop statement.

The final ability of the loop expression is that you can place assignments in it. For example:

def strs: ["Hello", "World"]
def res: loop (s <- strs, l: s.length) l * 2 # res is [10, 10]

Operators

You've already seen use of plenty of operators in Z. You've seen addition, subtraction, comparison, equality, and more. But for complete reference, below is a list of operators that come with the Z runtime, and their precedence:

The Left Overload is a method you can define on an object to overload the operator on the left-hand side:

x + y
becomes
x.+(y)
if x defines a + method.

The Right Overload is a method you can define on an object to overload the operator on the right-hand side:

x + y
becomes
y.r+(x)
if y defines a r+ method.

Operator Associativity Precedence Function Left Overload Right Overload
pow Right Infinity Performs exponentiation NA (overload * instead) NA (overload r* instead)
til Left 555 Exclusive range prev & succ & < NA
to Left 555 Inclusive range prev & succ & < NA
by Left 444 Used to specify the step of ranges NA NA
^ Left 333 Performs exponentiation NA (overload * instead) NA (overload r* instead)
% Left 222 Performs modulus % r%
/ Left 222 Performs division / r/
* Left 222 Performs multiplication * r*
+ Left 111 Performs addition + r+
- Left 111 Performs subtraction - r-
++ Left 111 Performs concatenation concat NA
>> Left 1 Left-to-right composition NA NA
<< Left 1 Right-to-left composition NA NA
|> Left 1 Pipe NA NA
< Left -111 Less-than < r<
<= Left -111 Less-than or Equal-to NA (Define < instead) NA (Define r< instead)
> Left -111 Greater-than NA (Define < instead) NA (Define r< instead)
>= Left -111 Greater-than or Equal-to NA (Define < instead) NA (Define r< instead)
= Left -222 Compares Structural Equality = r=
and Left -333 And boolean comparison NA NA
or Left -333 Or boolean comparison NA NA

The negative precedence and non-consecutive precedence numbers will be explained soon.

First Class Operators

Z has first-class operators, meaning the operators aren't special. They can be created, stored in variables, and in fact, are just ordinary functions.

+ is just defined as an ordinary function! Functions (like +) can then be called with infix syntax (Note that in Z 0.4.0+, operators MUST HAVE ALL SYMBOL NAMES):

However, operators are left associative and can have custom precedence:

def +': func x! + y!
3 +' 4 * 2 # ==> 11 +' has no precedence, defaults to 1, evaluates after multiplication
                

You can define a custom precedence for your operators:

# Continuing from the last example:
operator +': 1000 # Give it a high Precedence
3 +' 4 * 2 # ==> 14

Now, all the large precedence numbers should make sense. Operators having large gaps in precedence allows for insertion of operators in between precedence levels.

Since operators are functions, they can be curried. All the built-in operators actually are:

3 |> *(2) |> +(1) |> to(1) # [1, 2, 3, 4, 5, 6, 7]

The following symbol characters are allowed in identifiers: +, -, *, /, ^, ?, <, >, =, !, \, &, |, %, '

Since operators are just functions, you can use them like ordinary functions:

# Add function from before:
def add: +
# Sum an array
[1, 2, 3].reduce(+)
                

Macros

Note that dollar directives have been removed. The new macro system is much more capable.

Macros are compile-time "functions" that operate on AST nodes. Let's start by looking at a very simple macro:

macro $hello () {
    return ~{
        "Hello World"
    }~
}
                

There's a lot going on here. First, we define a macro called $hello with the macro keyword. All macros in Z start with the $ symbol, because this makes them "pop out" - so you know what's a macro and what isn't. Then, we return a template, denoted by ~{ and }~ which contains some Z code. This template in this case just contains the string "Hello World", meaning that any call to this macro is immediately replaced with "Hello World" at compile time. For example:

log($hello)
                

With the macro above, it will now print "Hello World". Now, macros can also take parameters. This simple $id macro takes one expression and returns it:

macro $id (~x:expr) {
    return ~{
        {{~x}}
    }~
}
                

You can see what happens. The tilde denotes a macro parameter, which in this case is of type expr. You call this parameter x. Then, when you return a template, you use double braces and the tilde operator again, to "spread" the value of x into the template. For example:

log($id [1, 2, 3])
                

It just logs what you passed to the macro. However, parameters passed to any macro are also available in AST form. For example:

macro $addFour (~arr:expr) {
    arr.push(4)
    return ~{
        {{~arr}}
    }~
}
log($addFour [1, 2, 3])
                

It will log [1, 2, 3, 4]. However, because AST nodes are passed to macro by reference, you're not allowed to reassign them without loosing the reference. So arr: arr.concat(4) would make not change the value of the AST node.

You can also pass blocks to macros - this makes for a natural looking syntax:

macro $while (~cond:expr, ~body:block) {
    return ~{
        loop {
            if not({{~cond}}) {
                break
            }
            {{~body}}
        }
    }~
}
$while true {
    log("MACROS ARE AWESOME!!!!")
}
                

As one can see, blocks spread into their execution context, so the block passed to $while becomes part of the loop block.

Let's say we wanted to make a for-loop macro. It would iterate over a collection, like JavaScript's for-of loop. The parameter list looks like this:

macro $for (~l:expr, of, ~r:expr, ~body:block) {

}                
                

The standalone of in the parameter list defines a conextual keyword. So of can act like a keyword in the context of a $for macro. Now, since we want to iterate over all iterators, we are going to use Z's loop expression (from the last section): <-. We are going to define a callback, and for each element of the iterator we're going to call the function for it. Since Z's macros are are non-hygenic, and since we don't want to corrupt the local environment, we are going to use psuedohygiene for a variable names. This means Z will randomly generate ids for a variable names that keep them readable while leaving only a one in one million chance of a name conflict with another generated identifier. You accomplish this by using double brackets without the tilde:

macro $for (~l:expr, of, ~r:expr, ~body:block) {
  return ~{
    def {{callback}}: func ({{~l}}) {
      {{~body}}
    }
    {} ++ loop ({{i}} <- {{~r}}) {{callback}}({{i}})
  }~
}
                

You can use it like this:

$for x of [1, 2, 3] {
    log(x)
}
                

Now, macros can also take keywods as arguments via id parameters:

macro $asgn(~type:id, ~lvalue:expr, =>, ~rvalue:expr) 
{
    return ~{
        {{~type}} {{~lvalue}}: {{~rvalue}}
    }~
}
$asgn def x => 3
$asgn let y => 3
                

Finally: marco varargs capture groups within a certain pattern. You can then use ...(){} to generate statements for each captured argument:

macro $logEach (...(~toLog:expr)){
    return ~{
        ...(){
            log({{~toLog}})
        }
    }~
}
$logEach (1 2 3 4 5)
                

This logs each number. A trailing comma can be added to indicate comma seperated values:

macro $logEach (...(~toLog:expr,)){
    return ~{
        ...(){
            log({{~toLog}})
        }
    }~
}
$logEach (1, 2, 3, 4, 5)
                

Using all of this, you can define a $switch macro:

macro $switch (~val:expr, ...{case, ~test:expr, ~body:block}){
    return ~{
        ...(){
          if {{~test}} = {{~val}} {
            {{~body}}
          }
        }
    }~
}
                

Then you can use it like this:

$switch 1 {
  case 1 {
    log("1")
  }
  case 2 {
    log("2")
  }
  case 3 {
    log("3")
  }
}
                

Since macros aren't runtime constructs, you can't export them. Instead, in an old-timey fashion, you include macros into your program. The include statement copies and pastes the contents of one Z file into another, during compilation, which allows macros to be "imported":

include "./for.zlang" # A file with the $for macro we defined before
$for x of [1, 2, 3] {
    log(x)
}
                

Macros can also be grabbed from the standard library via includestd:

includestd "imperative" # Grabs all the macros from the imperative.zlang file stored in the standard library.
                

Note that macros are still under development. Report any bugs you find here.

Enums

A note: Enums are only available in Z 0.3.1+. A stable, non-buggy implementation of enums is only available in 0.3.5+.

While Z doesn't support classical OOP, Z mixes OOP and FP in Rust-Style enums, which are akin to the algebraic data types of functional languages.

We are going to create a classic cons-list. You may also know this as a linked list.

The general idea of a cons-list is that each "node" of the list could either be Nil, the empty/end of a list, or a value, and the rest of the cons-list. For example, the cons-list equivalent of [1, 2, 3] would be:

Cons(1, Cons(2, Cons(3, Nil())))

To implement this, let's look at enums. Enums in Z aren't a special new kind of type, they're just a special way to define certain types of functions. To start, let's make a simple enum representing a color:

enum Color {
    Red,
    Orange,
    Yellow,
    Green,
    Blue,
    Purple
}

We can construct new members of an enum simply by calling its possible states:

Red() # Constructs the "Red" member of the color enum.
Orange() # Constructs the "Orange" member of the color enum.
                

You can also refer to the enum collectively via its name:

# This is the same as the example above.
Color.Red()
Color.Orange()
                

The = operator is automatically defined on each state of Color. For example:

def col: Red()
col = Red() # true
col = Color.Red() # Also true
col = Orange() # false
                

Now that you've seen the basics of enums, let's start defining our cons-list enum. We'll call it List and give it two possible states: Cons and Nil:

enum List {
    Cons,
    Nil
}

However, there's a problem. Cons needs to store two pieces of data: the first value and the rest of the list. In order to do this, we need fields. Let's look at a simple example of fields with a Point enum:

enum Point {
    Point(x, y)
}
# This could also be written as the following:
enum Point(x, y)
# Because the Point enum has only one constructor with the same name as it

Now, we can construct point objects using Point, or even Point.Point. They will have read-only x and y properties defined on them:

def myPoint: Point(3, 4)
log(myPoint.x) # 3
log(myPoint.y) # 4
def anotherPoint: Point(x: 3, y: 4) # Named fields can be used to increase readability.
def thirdPoint: Point(y: 4, x: 3) # Named fields can be in any order.
log(myPoint = anotherPoint) # This is true, fields are taken into account in equality too.

We can also use pattern matching to extract fields:

match something {
    Point(x, y) => x + y, # Only runs if object is constructed via Point.
    _ => "Not a point"
}
                

Using fields, we can create a working implementation of the cons-list:

enum List {
    Cons(val, rest),
    Nil
}

Now, we can create cons-lists, and test if they are equal:

def li1: Cons(1, Cons(2, Nil()))
def li2: Cons(1, Cons(3, Cons(4, Nil())))
def li3: Cons(val: 1, rest: Cons(val: 2, rest: Nil()))
li1 = li2 # False
li1 = li3 # True
                

Now, to easily iterate and apply transformations to cons-lists, let's define a consForEach function that takes a function and a cons-list as a parameter and will pass the function each value in the cons-list:

def consForEach: func (f, list) {
    loop {
        if list = Nil() {
            break
        }
        f(list.val)
        list: list.rest
    }
}

However, shouldn't we be able to associate consForEach with List itself. Say hello to the where block. Add the following to your List definition:

enum List {
    Cons(val, rest),
    Nil
} where {
    forEach(f, list) {
        loop {
            if list = Nil() {
                break
            }
            f(list.val)
            list: list.rest
        }
    }
}

Now, you can use forEach like this:

List.forEach(log, Cons(1, Cons(2, Cons(3, Nil()))))

It will print out the elements of the cons-list, one by one.

Now, how can we add types to the fields of Cons? Let's start by observing types of fields in action:

enum Point(x: number!, y: number!)
Point(3, 4) # All good!
Point("hola", 4) # Error!
enum Line(start: Point!, end: Point!) # Enums can also be used as types
Line(Point(3, 4), Point(3, 4)) # All good!
Line(3, 4) # Error!

Because of this, you'll probably try something like:

enum List {
    Cons(val, rest: List!),
    Nil
}

However, you'll get an error. Currently, all of an enum constructors must be typed or all must be untyped. There's no in-between. To get around this, you can use the _! type, which is a work-around for enums:

enum List {
    Cons(val: _!, rest: List!),
    Nil
}

Now, you'll receive an error (at runtime) when rest is not of type List, but val can be of any type.

Note that _! only works with enums. In function definitions, you just leave out the type to imply _!.

While the forEach functioned defined above is useful, what if we wanted to print out the cons-list as a whole? If you were programming in JavaScript, you might write a custom implementation of the toString method. However, enums can derive traits, and unlike in other languages traits/interfaces/protocols in Z are just normal functions given context via the derives keyword. Let's look at how this works. Start by importing the standard library's traits module traits:

importstd traits

Now, extract the Show trait from traits:

def {Show}: traits

Now, alert your definition of List to use the derives keyword:

enum List {
    Cons(val: _!, rest: List!),
    Nil
} derives (Show) where {
    forEach(f, list) {
        loop {
            if list = Nil() {
                break
            }
            f(list.val)
            list: list.rest
        }
    }
}

Now, you'll find that any cons-list constructed has a toString method:

log(Cons(1, Cons(2, Cons(3, Nil()))).toString()) 
# Logs "Cons(val: 1, rest: Cons(val: 2, rest: Cons(val: 3, rest: Nil())))"

Now let's look at another kind of way to implement a trait: statically. Traits implemented with the static keyword are automatically applied to each instance of an enum, but to the enum itself. To demonstrate, take the Curry trait from the trait module

:
def {Show, Curry}: traits

Now, add static Curry to the derives expression in the definition of list:

enum List {
    Cons(val: _!, rest: List!),
    Nil
} derives (Show, static Curry) where {
    forEach(f, list) {
        loop {
            if list = Nil() {
                break
            }
            f(list.val)
            list: list.rest
        }
    }
}

Curry makes all of an objects methods curried, and sicne we derived Curry on List, we can do:

def logger: List.forEach(log)

logger(Cons(1, Cons(2, Cons(3, Nil()))))         
                

Below is a list of all the traits defined by the traits module:

Show

Defines a toString method on an instance of an enum, which provides a more meaningful string to work with than "[object Object]".

Read

Defines a read method on an enum that attempts to parse a string and return an instance of that enum. However, parsing is limited and only numbers and built-in constants will be converted to their equivalents. Should be implemented with static.

Ord

Makes an enum comparable by overloading <. Starts by comparing constructor order:

enum Color {
    Red,
    Orange,
    Yellow,
    Green,
    Blue,
    Purple
} derives (Ord)
Yellow() < Blue() # True
Orange() > Red() # True
Yellow() <=  Orange() # False
                    

If both the left-hand operand and the right-hand operand have the same constructor, it will check to see if the left-hand operand's field is less than the right-hand operand's field:

enum Maybe {
    Just(thing),
    None
} derives (Ord)
Just(3) < Just(5) # True

It is not recommended to use Ord on enums that have constructors that have more than one field.

Copy

Copy defines a copy method on each instance of an enum:

enum Point(x: number!, y: number!) derives (Show, Copy)
log(Point(3, 4).copy(y: 2).toString()) # Logs Point(x: 3, y: 2)
                    

Enum

Enum defines the methods prev, succ, and to on each instance of an enum to allow for creation of ranges and the like:

enum Color {
    Red,
    Orange,
    Yellow,
    Green,
    Blue,
    Purple
} derives (Show, Enum)
log(Red().succ().succ().succ().prev().toString()) # "Yellow()"
log(Red().to(Yellow()).toString()) # "Red(),Orange(),Yellow()"
log(Yellow().to(Red()).toString()) # "Yellow(),Orange(),Red()"

By deriving both Enum and Ord you can overload range literals (the to type, not the ... type):

enum Color {
    Red,
    Orange,
    Yellow,
    Green,
    Blue,
    Purple
} derives (Show, Ord, Enum)
log((Red() to Yellow()).toString()) # Red(),Orange(),Yellow()
log((Purple() til Red() by 2).toString()) # Purple(),Green(),Orange()

PlusMinus

PlusMinus overloads the + and - operators for every instance of an enum. If both operands have the same constructor, and returns a new instance of that constructor with all the fields added. Otherwise, it adds/subtracts the relative order of the constructors in the enum declaration, and returns an instance of the constructor at that index.

Json

Makes each instance of an enum JSON serializable.

Curry

Curries each method of a certain object. That means if it's implemented with static it curries the methods of an enum. If it's implemented without static, it will curry every method on every instance of the enum.

All the traits above are great, but what if we wanted to build our own trait? We're going to be constructing a Sum trait that defines a sum method to add all of a traits fields together.

The implementation of the trait is below:

def Sum: func (obj) {
    obj.sum: func () {
        let sum: 0
        obj.fields.forEach(func (field) {
            sum: sum + obj[field]
        })
        return sum
    }
    return obj
}

First off, you may notice there's no new trait keyword. It's just a function. First, the function takes an object representing the newly constructed instance of an enum. It then adds a method to that object: sum. Every instance of an enum has a read-only fields property which holds an array containing the string names of every property defined by the enum constructor. By iterating over that array, we can pull out each field name, and then the value of each field. It adds them, and then returns the object, which now has a sum method. Now, to use this, let's revisit the Point enum from earlier, and derive Sum on it:

enum Point(x: number!, y: number!) derives (Sum)
                    

Try using a sum method on a point instance:

log(Point(3, 4).sum())

It should log 7.

When you derive a trait statically, the object passed to the function is the enum itself, for example the Point object would be passed to the Read trait if it were derived statically.

To add "reflection" to each enum instance, every instance contains the following metadata:

instance.fields # Array of all fields the constructor defined on the instance
instance.constructor # Reference to the constructor of the instance (which is just a function)
instance.parent # Reference to the overarching enum that the point's constructor belongs to (which is just an object)
parent.order # Array of the order in which the constructors for an enum were defined. Useful for creating traits like "Ord" and "Enum"
                

Advanced Compiler Commands

There are three commands in the compiler that have not yet been covered: dirt, watch, and wdir:

Directory Recursive Transpilation or DIRT:

The dirt command will transpile an entire directory to an "out" directory, maintaining file structure. So if you have a directory called src, and you want to transpile everything in it to dist, use:

$ zcomp dirt src dist

That's it.

Watching Files

If you have a file, say iwillchange.z, use the watch command to monitor it for changes, and transpile the file when changed:

$ zcomp watch iwillchange.z ../dist/iwillchange.js

Directory Watching

The wdir command will watch a directory for changes, and then use dirt to transpile it when changes occur. This is useful for production, where you have complex nested directories that you need to transpile all at once. For example:

$ zcomp wdir src dist

In 0.3.8+, the Z REPL has additional capabilities. First off, it will allow you to enter multiline statements when the first statement ends in {, ( or [. When you close the block, invocation, or array/object literal, all that code will be evaluated via the repl.

You can also load and gain access to the functions in a Z file using the :l command. If you have a file called add.zlang, which contains a function that adds two numbers, load it via:

zrepl>:l add

And then you can use it as if you had typed it into the REPL yourself.

Runtime Overview

Below is a list of all the built-in non operator functions included in the Z runtime:

isObject(val)

Returns true if val is not a scalar primitive. Otherwise, returns false.

typeOf(val)

Returns the type of val according to the following algorithm:

  1. Is val undefined? If so, return "undefined".
  2. Is val null? If so, return "null".
  3. Does val have a function? Does that function return a string? If so, return the result of calling val's type function.
  4. Is val NaN? If so, return "NaN".
  5. Does Array.isArray return true for val? If so, return "array".
  6. Return the result of calling typeof on val.

typeGeneric(val)

Returns the type of val according to the following algorithm:

  1. Does val define typeGeneric function? Does that function return a string? If so, return the result of calling that function.
  2. Is val an array? If so, return a string in the format "array<types>", where types is equal the result of joining a unique set of calling typeGeneric on all the elements in val with "|"

stone(val)

Returns the result of recursively calling Object.freeze on an object and its properties. Returns the object, which is now deeply immutable.

throws when passed a circular data structure.

copy(val)

Returns a deep copy of val, except for functions, for which it will return val itself.

throws when passed a circular data structure.

log(...vals)

Alias for console.log.

not(val)

Coerces val to a boolean, then returns the negation of that.

both(val1, val2)

Applies the JavaScript && to val1 and val2, coerces the result to a boolean, and then returns that.

either(val1, val2)

Applies the JavaScript || to val1 and val2, coerces the result to a boolean, and then returns that.

m(...vals)

Returns the result of calling vals.join("\n")

chan()

Makes a channel.

send(val, ch)

Sends a value to a channel.

curry(f)

Curries a function (loosely).

JS

The following methods are defined on the global JS object:

Method JS Equivalent
JS.new(constructor, ...args) new (constructor)(...args)
JS.typeof(val) typeof val
JS.instanceof(val, class) val instanceof class/td>
JS.+(x) +x
JS.+(x, y) x + y
JS.-(x) -x
JS.-(x, y) x - y
JS.*(x, y) x * y
JS./(x, y) x / y
JS.**(x, y) x ** y
JS.%(x, y) x % y
JS.==(x, y) x == y
JS.===(x, y) x === y
JS.!=(x, y) x != y
JS.!==(x, y) x !== y
JS.>(x, y) x > y
JS.<(x, y) x < y
JS.<=(x, y) x <= y
JS.>=(x, y) x >= y
JS.&&(x, y) x && y
JS.||(x, y) x || y
JS.!(x) !x
JS.&(x, y) x & y
JS.|(x, y) x | y
JS.^(x, y) x ^ y
JS["~"](x) ~x
JS.<<(x, y) x << y
JS.>>(x, y) x >> y
JS.>>>(x, y) x >>> y

Standard Library

Z's standard library is small, but growing. It contains 15 modules, detailed below. Each module (except the matcher and constructs modules) also has its own section, after this one.

Modules in Z's Standard Library:

  • Template - A module that performs string templating, complete with encoding functions and nested object templating.
  • Tuple - An implementation of fixed-size immutable collections (i.e. Tuples) in Z.
  • constructs - A module containing multiple control flow structures implemented as functions.
  • matcher - The behind-the-scenes implementation of Z's pattern matching. Not for use. Use match expression instead
  • utf32 - An implementation of Unicode in Z, with proper character indexing, Unicode-aware slicing, and more.
  • actors - A (primitive) recreation of the Actor Model in Z.
  • F - A functional utility module that is akin to Rambda.
  • gr - Utility methods for goroutines.
  • traits - Traits to derive on enums.
  • Decimal - A big decimal implementation for Z.
  • Rational - A bigrational number implementation for Z.
  • Complex - A complex number implementation for Z.
  • taylor - Taylor series used to approximate irrational numbers.
  • mercen - A web scraper that asynchronously fetches a list of all discovered Mersenne primes.
  • scrapy - A web scraper designed to extract a webpage's text content, and then allow you to execute queries on it.
  • gen - Generators based of Douglas Crockford's generators in How JavaScript Works.
  • objutils - Tools for managing object references.

Z's standard library also defined the following macro files:

  • imperative - Imperative constructs from languages like Java and Ruby

Template

Template is Z's way to perform advanced string interpolation. To start, you use the Template constructor to make a Template. Then, you resolve the template by calling resolve with data:

importstd Template
def nameTemplate: Template("{{person.name:upper}} is a nice name.", [
    "upper": func (str) {
        return str.toUpperCase()
    }
])
log(nameTemplate.resolve([
    "person": [
        "name": "Joe"
    ]
])) # ==> "JOE is a nice name."

Tuple

Z's Tuple module allows you to create Tuples not exceeding 4 elements:

importstd Tuple
def red: Tuple(255, 0, 0)
def green: Tuple(0, 255, 0)
def yellow: ++(red, green)
log(yellow._1, yellow._2, yellow._3) # ==> 255 255 0

Unicode Support

Z's utf32 module allows for basic Unicode support. It exports three things:

utf32.quote

utf32 provides a quote constant that contains the character ".

utf32.points(...points)

Creates a string from the code points specified by points, then passes that string to utf32.string.

utf32.string(str)

Returns an immutable ustr, an immutable string capable of accurately representing Unicode characters. Documentation for methods of ustr is below:

ustr#type()

Returns "ustr"

ustr#toString()

Returns u"str", where str is a string consisting of the ustr's code points.

ustr#toJSON()

Returns the result of calling toString, but without the "u".

ustr#at(index)

Returns a new ustr representing the code point found at index

ustr#codeAt(index)

Returns the code point found at index.

ustr#points()

Returns a list of the ustr's code points.

ustr#concat(other)

Returns the result of concatenating the ustr with other by joining their code points.

ustr#length(other)

Returns the amount of code points the ustr has.

ustr#=(other)

Returns true if the ustr's code points are equal to other's code points, otherwise returns false. Coerces arrays and strings into ustrs for comparison.

Functional Programming

Z's F module has plenty of available functional programming constructs: 99 in fact.

There are too many to cover here in detail, but here is the full list of all the functions the F module exports:

  • curry
  • unary
  • map
  • filter
  • reject
  • reduce
  • flatMap
  • >>
  • <<
  • |>
  • |
  • prop
  • invoke
  • reverse
  • reduceRight
  • every
  • some
  • constant
  • add
  • sub
  • mul
  • div
  • mod
  • neg
  • append
  • cat
  • inc
  • dec
  • T
  • F
  • N
  • U
  • I
  • NN
  • id
  • predArrayTest
  • all
  • any
  • bothTwo
  • complement
  • contains
  • count
  • zero
  • one
  • allButOne
  • methodInvoke
  • startsWith
  • endsWith
  • indexOf
  • find
  • findIndex
  • eitherTwo
  • equals
  • flatten
  • forEach
  • fromEntries
  • entries
  • has
  • head
  • tail
  • double
  • triple
  • indentical
  • identity
  • ifElse
  • init
  • isNil
  • join
  • keys
  • last
  • lastIndexOf
  • length
  • max
  • merge
  • min
  • pipe
  • compose
  • prepend
  • propEq
  • range
  • sort
  • sortBy
  • split
  • sum
  • take
  • takeLast
  • test
  • toLower
  • toUpper
  • trim
  • toPairs
  • toString
  • unique
  • values
  • without
  • takeWhile
  • dropWhile
  • zip
  • zipWith

Concurrency

In recent versions of Z (0.2.20+) the go keyword is inferred and you do not explicitly have to type it.

Z implements a dynamic and event-loop based form of Go-style concurrency. To start, all asynchronous actions in Z start with a go function, short for goroutine, which is capable of using channels to perform async actions:

def main: go func () { # Note the "go" keyword

}
main() # Returns a promise, like an async function.

Now, import the gr module from the standard library:

importstd gr
def main: go func () {
                            
}
main() # Returns a promise, like an async function.

Use destructuring assignment to get the line function out of gr:

importstd gr
def {line}: gr
def main: go func () {

}
main()

Use the get keyword with line to get a line from process.stdin:

importstd gr
def {line}: gr
def main: go func () {
    def someLine: get line
    log(someLine)                
}
main()

That wasn't too hard. Now, let's talk about channels, and how they work. To start, construct a channel with the chan() function:

def channel: chan()
def main: go func () {

}
main()

A channel can send and receive values. The send function sends values to a channel. The get keyword blocks in a goroutine until a value is sent to the channel, where get will return the sent value. If there are already values in the channel, get will give you the first. In this way, channels can act like queues:

def channel: chan()
def main: go func () {
    log(get channel) # Logs 3
    # Don't forget that get is asynchronous   
}
send(3, channel) # Send is synchronous
channel.pending() # Number of values still waiting (not received with get) in the channel
main()

This, for example, uses the gr module to feed a line to a channel:

importstd gr
def channel: chan()
def main: go func () {
    log(get channel) # Logs whatever line you entered
}
gr.line(channel) # Send is synchronous
main()

gr uses readline behind the scenes to send to a channel.

Because chan is just a normal function, you can return a channel from a function and then proceed to use it in a get expression. So you can do:

importstd gr
def channel: chan()
def main: go func () {
    log(get gr.line()) # gr.line() implicitly creates and then returns a new channel, and then, after you have entered a line into stdin, sends the line to that channel, prompting get to return that line.
}
main()

You can design a custom _from method that returns a promise to overload the get operator. This is the custom _from method defined by gr.line:

To understand this example you should be familiar with the readline module in node. If not, check it out here.

import readline
line._from: func JS.new(Promise, func (resolve) {
    def rl: readline.createInterface([
        "input": process.stdin,
        "output": process.stdout
    ])
    rl.question("", func (line) {
        rl.close()
        resolve(line)
    })
})

This is the actual line function defined by gr:

import readline
def line: func (prompt: "", ch: chan()) {
    def rl: readline.createInterface([
        "input": process.stdin,
        "output": process.stdout
    ])
    rl.question(prompt, func (line) {
        rl.close()
        send(line, ch)
    })
    return ch
}

If you're writing a small script, you can omit the go wrapper function and use get at the top level. However, if you are using get at the top level, the export statement is not allowed. Also note that top-level get does not work too well in the REPL. For example:

import gr
def ln: get gr.line("What's your name?")
log(ln ++ " is a nice name.")

To launch a standalone block of code asynchronously, use go in statement position:

go {
    # You can use "get" in here without turning the outside function into a goroutine.
}

You can also handle failure of asynchronous code that sends a gr.gerror to a channel, with else after the get expression:

get myAwesomeChannel else {
    log("Something went wrong " ++ err)
}

Here's a list of all the functions defined by gr:

gr.gerror(err, ch)

Sends a wrapped error containing err to ch.

gr.wrapNodeCB(context, f)

Returns a function that takes any number of arguments. The channel is the last argument. If only one argument is provided, the channel is set to chan(). Then, f, bound to context is called on the new arguments, and a callback function which sends the result (or error) to the channel.

gr.readfile

Equivalent to gr.wrapNodeCB(fs, fs.readFile)

Example:

get gr.readfile("doodad.txt") # Gets the content of doodad.txt

gr.writefile

Equivalent to gr.wrapNodeCB(fs, fs.writeFile)

Example:

get gr.writefile("doodad.txt", "doodad", ch()) # Writes "doodad" to doodad.txt.

gr.json(url, ch: chan())

Gets the json at the specified url.

Example:

get json("https://yesno.wtf/api") # Gets a JSON object that contains an answer property equal to "yes" or "no".

gr.page(url, ch: chan())

Gets the HTML at the specified url.

Example:

get page("https://www.google.com/") # Gets the HTML at google.com.

gr.line(prompt: "", ch: chan())

Reads a line from stdin, using prompt as input. If called with no arguments, its parentheses may be omitted. (as in get line)

gr.wrapPromise(prom, ch: chan())

Wraps prom so that:

get gr.wrapPromise(prom)

Is like (in JS):

await prom

gr.all(chs, ch: chan())

Like Promise.all, but for goroutines.

gr.race(chs, ch: chan())

Like Promise.race, but for goroutines.

gr.status(chs, ch: chan())

With get, it gives back an array of the results of chs. Each result will contain a state property that is either "succeeded" or "failed". If it has succeeded, the result will be inside the result property. If it failed, the error will be inside the error property.

gr.any(chs, ch: chan())

Will send the first channel in chs to succeed, or a list of errors if none succeed.

gr.wait(ms, ch: chan())

Waits ms milliseconds before sending Symbol() to ch.

gr.waitUntil(cond, ch: chan())

Waits until cond() is true before sending Symbol() to ch.

gr.give(ch: chan())

Equivalent to gr.wait(10). Useful for passing control between goroutines.

gr.select(chs, ch: chan())

For each element of chs, will see which element[0] resolves first, and when it does, executes element[1](what element[0] resolved to)

get gr.select([
    [
        first,
        func (val) log("First " ++ val) # If first channel recieves a value first.
    ],
    [
        second,
        func (val) log("Second" ++ val) # If second channel recieves a value first.
    ]
])

More Numbers

Z's standard library has support for a numerous amount of Number types, which all implement the following methods:

+, r+, -, r-, *, r*, /, r/, =, r=, <, r<

Note that Complex numbers do not implement < nor r<

This allows you to use these number types in place of everyday IEEE floating point numbers seamlessly. Because all of these number types rely on BigInt, they'll only work in Node 10+. Let's start with the Big Decimal number type:

Big Decimals

Big Decimals are contained within the Decimal module. Here is a sample of their use:

importstd Decimal
def myBigDecimal: Decimal(3) # Decimals can be ordinary numbers
def myBiggerDecimal: Decimal("3.238142194382154234120973481276") # Decimals can be parsed from strings.
log(myBigDecimal + myBiggerDecimal |> .toString()) # Logs "6.238142194382154234120973481276"
log(myBigDecimal + "0.238142194382154234120973481276" = myBiggerDecimal) # Logs "true". Decimal can be compared for equality. Operations with decimals coerce their operands.
                    

Decimals also support all other common operations that you can do with numbers, so they can be added, subtracted, multiplied, divided, and compared. When doing division with decimals, you can specify a precision to divide to:

decimal1 / decimal2 # Default precision of 20 digits
decimal1./(decimal2, -100digits) # One hundred digit precision
                    

The Decimal object also defines the following constants and methods, which are equivalent to their Math object counterparts:

E, LN10, LN2, LOG10E, LOG2E, PI, SQRT1_2, SQRT2, abs, ceil, floor, round, sign, random

Big Rationals

Like Big Decimals, Big Rationals support all the common numerical operations. They are represented with a numerator and a denominator, and they can be constructed via the // operator:

importstd Rational
def { // }: Rational
operator //: 1000
log(3 // 4 |> .toString()) # Logs "3 // 4"
                    

Common operations with Rationals:

importstd Rational
def { // }: Rational
operator //: 1000
log(2 // 6 = 1 // 3) # true
log(1 // 3 + 1 // 2 |> .toString()) # 5 // 6
log(2 // 3 > 5 // 6) # false
log("0.5" = 1 // 2) # true, because strings containing decimal values can be coerced into Rationals.
                    

In addition to supporting common operations, like Decimal, Rational defines several constants and functions akin to their Math counterparts:

E, LN10, LN2, LOG10E, LOG2E, PI, SQRT1_2, SQRT2, abs, sign, random

Complex Numbers

Complex numbers are essential parts of some mathematical applications. For this reason, Z provides minimal complex number support.

Complex numbers support all basic arithmetic operations and equality checking, however, they do not support < nor r<. See an example of complex numbers below:

importstd Complex
log(Complex("12 + 7i") + "8 - 2i" |> .toString()) # 20 + 5i
log(Complex(12, 7) = "12 + 7i") # true
                    

Complex trigonometry may be added at some point in the future.

The Complex object also comes with a static random method that generates a random Complex number.

Web Scraping

Z's web scraping module, scrapy is a powerful way to extract text data in sentence form from webpages. Let's get all the sentences from the Wikipedia page about London:

importstd scrapy
def text: get scrapy("https://en.wikipedia.org/wiki/London")
log(text.sentences()) # This will print out an array of all the sentences on the London Wikipedia page
                    

While there's plenty of sentences about London that pop up, there's also sentences that are completely unrelated. Let's find the first 30 sentences with only the string "London" in them:

importstd scrapy
def text: get scrapy("https://en.wikipedia.org/wiki/London")
text.thingsAbout("London").slice(0, 30).forEach(func log("- " ++ x!))
                    

The thingsAbout method takes a string which it then coerces into a regular expression, and returns an array of all sentences contained within the source text that match that regular expression.

EDU Modules

Some modules in the Z compiler exist for educational purposes only. They are documented here.

Taylor Series

Taylor series are effective ways for approximating rational numbers. The taylor module comes with all you need to build Taylor series and several built-in ones. To start, you can create an infinite series via taylor.sum or taylor.product:

importstd taylor
def factorial: taylor.prod(1, func x!) # This multiplies all the numbers up to the number specified by the approx function.
factorial.approx(5) # 120 (in Decimal form)
                    

Taylor series can also be used to approximate irrational numbers like e. In fact, taylor has a prepackaged e series:

taylor.E.approx(100tries, -40digits).toString() # "2.7182818284590452353602874713526624977552"
                    

If you're interested in how more irrational numbers and functions can be implemented as Taylor series, check out the source code here.

Mersenne Primes

The mercen module dynamically fetches a list of all known Mersenne primes for you. A Mersenne prime is a prime number that can be represented as two raised to an integer power minus one. For example:

importstd mercen
def primes: get mercen()
log(primes.count()) # Returns the number of Mersenne primes
log(get primes.number(3)) # Get the third Mersenne prime, which is 31
def aBigOne: get primes.biggest() # Get the largest Mersenne prime in the list
log(aBigOne.slice(0, 10)) # Display the first 10 digits of the biggest Mersenne prime
                    

If you wish, you can verify the answers Z gives you on this website.

Imperative Macros

These macros are designed to model the imperative constructs of other languages. Get access to them with:

includestd "imperative"
                    

They function as follows:

$while cond {
    body
}
$unless cond {
    body
}
$until cond {
    body
}
$do {
    body
} while cond
$for lvalue of rvalue {
    body
}
                    

Examples

This section compares, Z, CoffeeScript, and JavaScript code side by side in common examples.

Hello World:

Z:

log("Hello World")

CS:

console.log "Hello World"

JS:

console.log("Hello World")

Fibbonaci:

Z:

def fib: func (n)
    if (n < 2) n
    else fib(n - 1) + fib(n - 2)

CS:

fib = (n) -> if n < 2 then n else fib(n - 1) + fib(n - 2)

JS:

function fib(n) {
    if (n < 2) return n;
    return fib(n - 1) + fib(n - 2);
}

First 25 Squares:

Z:

log(loop(i <- 1...25) i ^ 2)

CS:

console.log([i ** 2 for i in 1..25])

JS:

const squares = [];
for(let i = 0; i < 26; i++) {
    squares.push(i ** 2);
}
console.log(squares)

Parameter Type Checking (Runtime):

Z:

def sum: func (init number!, list array!) {
    return list.reduce(+, init)
}

CS:

sum = (init, list) ->
    throw new Error("Init must be number") if typeof init isnt "number"
    throw new Error("List must be array") if not Array.isArray(list)
    list.reduce (t, v) -> 
        t + v
    , init

JS:

function sum(init, list) {
    if (typeof init !== "number") throw new Error("Init must be number.");
    if (!Array.isArray(list)) throw new Error("List must be array.");
    return list.reduce((t, v) => t + v, init);
}

Runtime Polymorphic Functions:

Z:

def double: func (val) match val {
    { value: number! } => v * 2,
    { number: number! } => n * 2,
    (number!n) => n * 2,
    number! => val * 2
    string! => val ++ val,
    array! => val ++ val,
    _ => [_, _]
}

CS:

double = (val) -> 
	if Array.isArray(val)
		if typeof val[0] is "number"
			return val[0] * 2
		else
			return val.concat(val)
	if typeof val is "object"
		if val.number isnt undefined
			return val.number * 2
		else if val.value isnt undefined
			return val.value * 2
	if typeof val is "string"
		return val.concat(val)
	if typeof val is "number"
		return val * 2
	return [val, val]
        

JS:

function double(val) {
	if (Array.isArray(val)) {
		if (typeof val[0] === "number") {
			return val[0] * 2;
		} else {
			return val.concat(val);
		}
	}
	if (typeof val === "object") {
		if (val.number !== undefined) {
			return val.number * 2;
		} else if (val.value !== undefined) {
			return val.value * 2;
		}
	}
	if (typeof val === "string") {
		return val.concat(val);
	}
	if (typeof val === "number") {
		return val * 2;
	}
	return [val, val];
};

Point Objects via Classes/Enums:

Z:

importstd traits
def {Show, PlusMinus}: traits

enum Point(x: number!, y: number!) derives (Show, PlusMinus) where {
    dist(p1, p2) {
        return Math.sqrt(-(p1.x, p2.x) ^ 2 + -(p1.y, p2.y) ^ 2)
    }
}

CS:

class Point
	constructor: (x, y) ->
		throw new Error("Point.x must be number") if typeof x isnt "number"
		throw new Error("Point.y must be number") if typeof y isnt "number"
		@x = x
		@y = y
	equals: (p) ->
		p instanceof Point and @x is p.x and @y is p.y
	plus: (p) ->
		new Point @x + p.x, @y + p.y
	minus: (p) ->
		new Point @x - p.x, @y - p.y
	toString: ->
		"Point(x: #{@x}, y: #{@y}"
	@dist: (p1, p2) ->
        Math.sqrt (p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2

JS:

class Point {
	constructor(x, y) {
		if (typeof x !== "number") {
			throw new Error("Point.x must be number");
		}
		if (typeof y !== "number") {
			throw new Error("Point.y must be number");
		}
		this.x = x;
		this.y = y;
	}

	equals(p) {
		return p instanceof Point && this.x === p.x && this.y === p.y;
	}

	plus(p) {
		return new Point(this.x + p.x, this.y + p.y);
	}

	minus(p) {
		return new Point(this.x - p.x, this.y - p.y);
	}

	toString() {
		return `Point(x: ${this.x}, y: ${this.y}`;
	}

	static dist(p1, p2) {
		return Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2);
	}

}

Concurrently fetching JSON from an API (on node):

Z:

importstd gr
importstd F
def results: F.map(
    func result!.answer, 
    get gr.all(
        Array.from(Array(10), func gr.json("https://yesno.wtf/api"))
    )
)
log(results)

CS:

https = require 'https'
results = []

getAnswer = ->
	https.get 'https://yesno.wtf/api', (res) ->
		body = ''
		res.on 'data', (chunk) ->
			body += chunk
			return
		res.on 'end', ->
			results.push JSON.parse(body).answer
			if results.length is 10
				console.log results

for i in [1..10]
	getAnswer()

JS:

const https = require("https");
const results = [];

function getAnswer() {
    https.get("https://yesno.wtf/api", res => {
        let body = "";
        res.on("data", chunk => {
            body += chunk;
        });
        res.on("end", () => {
            results.push(JSON.parse(body).answer);
            if (results.length === 10) {
                console.log(results);
            }
        });
    })
}

for (let i = 0; i < 10; i++) {
    getAnswer();
}

The Why of Z

Why Z? There are already plenty of functional languages out there that transpile to JS. Z provides an alternative if you're looking for something more multi-paradigm. Some languages take the functional paradigm so far that the imperative version is actually more readable in certain simple cases. For example, let's look at a "Hello World" program in Elm:

import Html exposing (text)
main = 
    text "Hello World"

This is a relatively simple example. However, you can see some things that don't have anything to do with logging "Hello World". First off, the import statement is clutter. Why can't the text function be built-in, perhaps? And do we have to place our call to the text function inside of main?

These questions all have satisfactory answers. However, in a simple program, it makes a lot more sense to drop the clutter. For example, "hello world" in Z is very simple:

log("Hello World")
                    

In more complex cases, there would be obvious reason to have modules, and a main function. But in simple cases, an imperative script would be easier. You'll encounter the this situation with many different constructs: there are times when loops make more sense than their recursive counterpart and when references are easier than pass-by-value.

Z is about letting you choose between functional and imperative, taking whichever one suits you current needs. If you look through the Z standard library, you'll see that it's predominantly functional on a line-by-line basis, but uses imperative constructs now and then.

Z is about balance. About equilibrium. About letting you create modules just for the fun of it. About letting you choose the paradigm best for the situation.

Contribute

If you want to contribute to Z, you should check out the following links:

Z Compiler
Z Website
Z Standard Library