Yak

The first language of the funject paradigm.

Use Shift+Enter to add a line

Welcome

Yak is a remarkably powerful scripting language that leverages the new concept of a funject—a programmatic construct with properties of both functions and objects. Funjects allow you to say anything you could with a function or object, but they also allow you to express things you couldn't before.

Yak comes in three flavors: an Racket-based executable, an npm package, and an HTML include that interprets yak inline or included in the HTML. The executable supports parallelization and pretty much anything you'd find in Racket. The npm package gives you access to node.js through require statements and allows you to write entire servers. The third interpreter, once included on your webpage, interprets all script tags with the attribute type='text/yak'.

Install

The simplist way to get Yak is to include the following line in your HTML:

<script type=text/javascript src=http://yak-lang.org/yak.js></script>

This will interpret all <script> tags written after it that contain the attribute type=text/yak. Any libraries you include (such as jQuery) you can access from Yak.

You can also download the Racket-based interpreter for Mac OS X. I haven't compiled Yak on other operating systems just because I haven't had access to computers with other operating systems. If you have a Window or Linux, please download the source, compile it, and send to me it so we can post it here!

To use Yak with node, first download node, then run:

npm install yak-lang

You can then evaluate Yak strings with yak.eval and evaluate files with yak.evalFile. In Yak, you can require node's http, fs, or your favorite node library. For more detail, see the package.

Learn

Contents

  1. Contents
  2. Getting Started
  3. What is Yak?
  4. Builtin types
  5. Applications and Parameters
  6. Intermezzo: Comments
  7. Inheritance and own(ership)
  8. Intermezzo: Conditionals
  9. Variables and Assignment
  10. Intermezzo: Sequences
  11. Patterns & Inverses
  12. Classes & the Standard Library

Getting Started

About this Guide

  • This Guide assumes you already have some experience with programming. If you don't, I recommend reading this excellent guide for beginners.
  • This Guide also assumes you have a reasonable appetite to learn Yak. This does not feature lots of "in-depth" examples, but aims for concision by enumerating the precepts of the language with a few, short examples. It is the ideal guide for a programmer who wants to learn the whole language quickly.

How Do I get Yak?

Follow these great instructions!

What is Yak?

Yak is the first language to conform to the funject paradigm. In the funject paradigm, neither functions nor objects exist; rather, the roles of these two concepts are merged into the idea of a "funject". Absolutely everything in Yak is a funject, including numbers, strings, booleans, and the "nil" funject.

Builtin types

Yak has eight builtin types: Number, String, Boolean, Symbol, Nil, Unknown, List, and the "plain" Funject. Any funject of any type is also a type of Funject. Each of these builtin types has its own literal syntax.

Numbers

Yak does not distinguish between different types of numbers—integers, floats, doubles. Rather, everything is simply a number. Number literals contain a number of digits followed by some optional decimal digits and then optional scientific notation. The following are all valid numbers.

1

3.14159

1e80

2.997e+8

1.213e-5

Strings

Strings are written as a series of characters enclosed in double or single quotes. Strings can contain some standard escape sequences beginning with the backslash character (\): newline \n, tab \t, carriage return \r, and backslash itself \\. Valid Strings include:

'yak'

"5479"

'By the rude bridge\nThat marked the flood...'

Booleans

The two Booleans are the values true and false. Yak ascribes meaning to case, so True does not evaluate to true.

Symbols

Symbols are like Strings but you can't edit the characters in them as easily. They are more often less important for the characters they comprise than the concepts they represent—hence their name "symbols". They are like symbols in Ruby or Lisp. You write symbols as a dot (.) followed by a series of numbers, letters or other characters: _ (underscore), - (minus sign), ?, !, +, *, /, %, ~, $, <, and >. Symbols may not begin with a number. Valid symbols include:

.i-am-a-symbol

.$

.yak+ice-cream->hooray!

Nil

The only funject of type Nil is nil. It represents "nothingness", much like Java's null, C's NULL pointer, Python's None, or Ruby's nil. For example, a funject that returns the index of an element in an list and fails to find an element in a particular list would probably return nil.

Unknown

The only funject of type Unknown is unknown. You use it in pattern matching, so I will discuss it later.

Lists

Lists—like many language's Arrays—represent a series of funjects. You write lists as a series of expressions separated by commas and enclosed in square brackets. Valid lists include:

['one', 2, 'three']

[]

['hooray!', [['nesting!']]]

Funject Literals

In addition to all the aforementioned types—which are themselves funjects—you can also write "plain" funjects with the funject literal syntax:

{
    pattern1: consequent1
    pattern2: consequent2
    ...
}

A funject literal is a series of any number of pattern-consequent pairs separated by newlines and enclosed in curly braces. A pattern-consequent pair is a pattern followed by a colon followed by an expression. A pattern is a restricted set of Yak syntax that I will explicate more fully later. When writing a funject literal, you must indent all the pairs the same distance. Valid funject literals include:

{}

{'pattern': "consequent"}

{
    0: {}
    1: {'pattern': "consequent"}
    [0, 100, ['string']]: {0: {1: 2}}
}

{
    [1]: 'one'
    [2]: 'two' }

You can write a curly braces on the same line as a pair or you can separate it from the first (or last) pair with any number of newline characters.

Applications and Parameters

When you write two expressions next to one another, the interpreter interprets it as an invocation and invokes the lefthand expression with the righthand expression as its argument.

exp1 exp2

Funjects always take one argument, and everything is a funject, so placing two expressions next to one another is always a syntactically legal invocation. Invocation is left-associative, so the following...

exp1 exp2 exp3 exp4 ...

...is parsed as...

(((exp1 exp2) exp3) exp4) ...

The interpreter invokes a funject in three steps:

  1. It attempts to match the argument to one of the funject's patterns
  2. When it finds a match, it sets up a new enviroment.
  3. It evaluates the consequent of the pair and returns the result.

For example, consider the following invocation:

{
    0: 'zero'
    1: 'one'
    2: 'two'
} 1

The interpreter first identifies the funject literal as the receiver (to be invoked) and 1 as the argument. It then looks at each of the patterns in the receiver in the order in which they were defined. Next, it tries to match the first pattern, 0, to the argument, 1, but this fails. It then tries to match the second pattern, 1, to 1. Since this succeeds, it evaluates the consequent, 'one', and returns this as the result of the whole invocation.

This may remind you of function (or procedure or method) application from languages like Java and Ruby. Like functions, funjects can contain parameters. You write a function an at sign (@) optionally followed by a sequence of characters the same as the ones you can put in a symbol. Valid parameters include:

@I-am-A-pArAmEtEr

@yak+ice-cream->hooray!

@

A parameter matches any value. Moreover, when the interpreter matches a parameter to a value, it assigns the parameter that value in the scope of the consequent. For example:

{@x: [@x, @x]} 4

evaluates to [4, 4], because the interpreter matches @x to the argument, 4, and simultaneously assigns @x the value 4. Then, when it evaluates the expression [@x, @x], it replaces all occurances of @x with 4. It therefore evaluates the entire invocation as [4, 4].

Parameters can also appear as components of patterns:

{[@x, 'plumblum']: @x} ['blog', 'plumblum']

This evaluates to 'blog': the interpreter tries to match [@x, 'plumblum'] to ['blog', 'plumblum'] by matching each of the List's elements. It matches @x to 'blog', 'plumblum' to 'plumblum', and evaluates the consequent—@x—as 'blog'.

Funject supports relatively logical pattern matching, so the following:

{
    [@x, @x]: @x
    [@x, @y]: [@y, @x]
} [4, 5]

will evaluate to [5, 4] because the first pattern matches only a list of two same elements. Since the parameter @x appears twice in the pattern, the interpreter needs to match both occurrences to the same value. Accordingly, the interpreter will evaluate the following:

{
    [@x, @x]: @x
    [@x, @y]: [@y, @x]
} [2, 2]

as 2, because [2, 2] does match a list of two same elements.

Intermezzo: Comments

Yak supports single and multiline comments. Single line comments begin with a pound (#), like in every other damn scripting language.

#I'm a comment!

'the interpreter will evaluate me!'

#the interpreter won't evaluate me, though.

You delinate multiline comments with #| and |#. You can nest comments, like in this example:

#|
    I'm in a comment
        #|
            I, too, am in a comment
            I'm yet another line.
I have hokey, but legal, identation.
                    |# 
    I get the last word (budum-cshh). |#

Inheritance and own(ership)

What if none of the patterns match?

Every funject has an parent funject; we say that a funject "inherits" from its parent. By default, funjects created with funject literals inherit from a special default parent of the type Funject. This default parent contains a number of useful pattern-consequent pairs (hence called "rules").

If the interpreter invokes a funject with an argument and fails to match each of the patterns to the argument, then it invokes the funject's parent with the argument. Since all funjects inherit—directly or indirectly—from the default parent, if the interpreter continues to fail to find a match for an argument it will eventually invoke the default parent. The default parent contains a special, catch-all pattern that signals an error complaining that the interpreter has found no match.

You can set the parent of a funject with the inheritance operator (<<).

heir << inherited

When the interpreter fails to find a match on heir, it will invoke inherited. You may reset a funject's parent any number of times.

Own

During the execution of a consequent, a funject has access to a special keyword own. own represents the funject itself. Using code, a funject can invoke itself, pass itself as an argument, and even return itself. Compare own to JavaScript's arguments.callee. For example, we can write the famous recursive factorial algorithm as a funject:

{
    [0]: 1
    [@n]: @n * own[@n - 1]
}

This definition also uses the mathematical operations times (*) and minus (-). The interpreter actually handles mathematical operations in an interesting way; the interpreter does not handle them as a special case, operation, or syntax but handles them as standard library funjects. Suffice it to know for now that the interpreter evaluates a + b as the sum of a and b, a - b as the difference of a and b, a * b as the product, and a / b as the quotient.

Intermezzo: Conditionals

Yak supports conditionals (if-expressions) for convenience. They add no expressive power. They have two syntaxes and look much like CoffeeScript's conditionals:

Form 1:

if false
    'this will not evaluate'
else if 4 is 4
    'Why yes, 4 is 4!'
else print['Why include a print statement in a condition?']
    'print statements always evaluate to nil'
else
    'this, too, will never evaluate'

Form 2:

if 8 is 2 then "8 is 2?" else "8 isn't 2!"

In both forms you may omit the else clause. If expressions are expressions, and evaluate to the evaluated clause. If the condition of a conditional evaluates to anything but true or false, the interpreter attempts to coerce it to a Boolean by invoking it with the symbol .to-boolean: condition.to-boolean. If this fails to match—just as when any argument fails to match—the interpreter throws an error. If the condition of a conditional evaluates to false and has no else clause, the entire conditional evaluates to nil.

Variables and Assignment

The Simple Assignment Operator

Yak has a rich variety of assignment operators. The simplest is the simple assignment:

x = 4

When the interpreter encounters this expression, it identifies the left hand side as a variable, evaluates the right hand side, and sets the variable x to point to the value, 4 in whatever scope the interpreter encountered the assignment. It then evaluates the entire expression as the right hand side, and substitutes the value set in place of the assignment. This allows you such nice syntax as:

x = y = 'pomegranate'

The assignment y = 'pomegranate' evaluates to 'pomegranate' itself, and the interpreter sets x equal to 'pomegranate'.

When assigning a variable, you need not declare its type; futhermore, you may reset it to a value of a different type. In many ways, the notion of a "type" is meaningless because everything is a funject.

The Lazy Assignment Operator

You can also assign values lazily. The := operator denotes a lazy assignment:

x := 2 + 2

When the interpreter encounters this expression, it identifies left hand side as a variable and sets it equal to the delayed expression on the right hand side in the scope where it encountered the assignment. Instead of returning the right hand side (which it hasn't evaluated yet) it simply returns nil. The interpreter evaluates the expression not when you assign the variable, but when you reference it. Thus to compute the expression:

x + x

The interpreter evaluates the first x by looking up the variable in the current scope, finding that it points to a lazy expression, evaluating that expression, and then substituting the result of the evaluation for the variable. Everytime you reference the variable, the interpreter evaluates the expression again. In the example above, the interpreter evaluates x + x twice for each reference to x.

You can use lazy assignments to great effect such as by tracking the value of a variable. Consider the following:

x = 0
y := x
x = 1
y

In this case, the interpreter first sets x equal to 0, sets y equal to the lazy expression x, resets x to 1, and finally evaluates y. Because it evaluates the expression x when it evaluates the last line, it evaluates y as 1. When we say that "y is x", we seem to mean that y is whatever x is. Because we used lazy assignment, we will find that y always equals whatever x does. But if we had used strict assignment, as many other languages solely support, the interpreter would have evaluated y as 0.

Scope

The interpreter evaluates the consequent of a funject in a scope that is newly created for each execution but that has access to the scope in which the funject was defined. For example:

x = 'outer'
{ []: x = 'inner' } []
x

When the interpreter evaluates this, it first sets x equal to 'outer' in the outer scope, then matches the pattern of the funject to its argument, and evaluates the consequent of the funject by creating a new scope and setting x equal to 'inner' in that scope. When it then looks up the variable x in the outer scope, and finds it equal to 'outer'. Lazy assignments work the same way:

x := 'outer'
{ []: x := 'inner' } []
x

On the last line, the interpreter evaluates x as 'outer'.

If you want to reset the variable of an outer scope, you need to use the reset assignment operators. You can reset simple values with |=

x = 'outer'
{ []: x |= 'inner' } []
x

When the interpreter evaluates x |= 'inner', it searches for the variable x in an enclosing scope—not in the current scope—beginnign with the "closest" scope and working up the "scope chain" (successively enclosing scopes). If it fails to find such a variable, it signals an error. Otherwise, it resets the first variable it finds to the result of evaluating the left hand side. In this example, it searches outside of the scope of the consequent and finds the variable created in the assignment x = 'outer'. It then resets this to 'inner'. When it lookups x on the last line, it finds it equal to 'inner'.

The interpreter evaluates the lazy reset assignment operator (|:=) in much the same way, but it delays the evaluation of the right hand side until you attempt to reference the variable.

Intermezzo: Sequences

A sequence is simply an indented series of expressions, each on their own line. A sequence can appear anywhere an expression can. Because all syntactic constructs in Yak are expressions, this is everywhere.

Consider an example:

y = 0
x := 
    y = y + 1
    'this string accomplishes nothing'
    y + 10

Upon encountering a sequence, the interpreter evaluates each of the sub-expressions, in order, and then evaluates the result of the sequence as the last expression. Since sequences do not have their own scope, the following calls will each increment y.

x #this is 11

y is now 1

100 + x #this is 112

y is now 2

1000 + x #this is 1013

y is now 3

10000 + x #this is 10014

y is now 4

Patterns & Inverses

Yak supports extensive pattern matching. For example, you can match a deeply nested list:

{ [1, [[@x], [3]], 4]: @x } [1, [[2], [3]], 4]

The interpreter matches the parameter @x to 2 and evaluates the entire expression as 2.

You can also match invocations. To do so, you must define the inverse of a funject with the <- operator. The <- operator sets its right hand side to be the inverse of its left hand side. Inverse funjects must conform to certain criteria.

plus = {
    [a, b]: a + b
}

plus <- {
    [@result, [unknown, @b]]: [@result - @b]
    [@result, [@a, unknown]]: [@result - @a]
}

The second of these two expressions sets the inverse of plus. You will understand its rules when I explain how the interpreter uses them. When the interpreter encounters an application in a pattern, it attempts to evaluate it. If it finds that the argument of the application contains a parameter, it checks to see whether the argument contains other parameters of a different name. If it does, the interpreter throws an error. Otherwise, it replaces all occurances of the unknown parameter with the special funject "unknown". It then passes the inverse of the funject a list of two elements: the first is the value the interpreter was attempting to match the application to; the second is this modified argument. Consider an example:

{ ['fum', plus[@x, 27]]: @x + 10 }['fum', 42]

The interpreter first attempts to match the pattern to the argument by matching 'fum' against 'fum'. Since this succeeds, it proceeds to match the application plus[@a, 27] against the 42. It checks that the argument contains only one parameter, converts the only instance of it to unknown, and passes [42, [unknown, 27]] to the inverse:

{
    [@result, [unknown, @b]]: [@result - @b]
    [@result, [@a, unknown]]: [@result - @a]
} [42, [unknown, 27]]

The interpreter matches the first rule and evaluates this application of the inverse to [42 - 27], [15]. The interpreter then sets the possible values of the unmatched parameter @x to the result of evaluating the inverse, in this case [15]. Each element in the list indicates a possible value for the parameter @x. You may return multiple possibilities in an list (or none with an empty list). If the interpreter were to evaluate another invocation and receiver other possible values for @x it would filter out those possibilities not present in both lists. If the interpreter ever finds that a parameter as no possible values, it moves on to the next rule (or throws an error if there are none). If the interpreter ever finishes matching a pattern and has multiple possibilities for a parameter, it chooses one randomly.

In this case, the interpreter assigns @x to the only possibility, 15. It then evaluates the consequent of the first funject—@x + 10—as 25.

Just as you can have multiple instances of the same parameter in a normal pattern, you can the same in an argument in a pattern. If we augment our inverse to this:

plus <- {
    [@result, [unknown, @b]]: [@result - @b]
    [@result, [@a, unknown]]: [@result - @a]
    [@result, [unknown, unknown]: [@result / 2]
}

The following expression,

{ [plus[@x, @x]]: @x } [8]

will now evaluate to 4.

Congratulations! You know core Yak. Now go eat a cookie.

Classes & The Standard Library

Yak features a full class system. But because Yak is under rapid development, we still have several varients for the syntax of defining a class. Once we have chosen a model, we may write about it in this tutorial. In the meantime, you should find Yak's prototypal inheritance (through the << operator) sufficient. If you still want to learn about classes, you may read about them in the tutorial for beginners. Otherwise, you need only know how to use already-defined classes.

The interface for using a class is simple. Since a class it a funject, it may have a number of properties inherent to its purpose. As an example, consider the Number class:

Number.pi # 3.141592653589793

Number.e # 2.718281828459045

A class also has the special .instance rule. When you invoke a class with the symbol .instance, it returns a funject that has all the "attributes" and "methods" that instances of the class have. If you would normally invoke instances of the Number class this way:

4.sin

4.+ 3

Then you can also invoke attributes and methods using the Number class:

Number.instance.sin[4]

Number.instance.+[4, 3]

You can also write .instance. with the :: syntactic sugar:

Number::sin[4]

Number::+[4, 3]

Docs

Yak has eight core classes: Number, String, Boolean, Symbol, Nil, Unknown, List, and the "plain" Funject. If you haven't yet, you should read about them in the tutorial above. In addition, Yak has the Class Class; each class is of the type Class with methods such as .superclass. You can learn about the attributes and methods of a class by clicking on its name.

Source

You can find the source on my Github. The repo contains the source for the Racket interpreter, for the JavaScript interpreter, and for this website (which was written in Yak itself).

Colophon

Who are you?

I'm Will Murphy. I invented the Funject language in the winter of 2011 upon noticing the similarity between an object and a switch statement. I wrote a simple interpreter then, but recently rewrote it into the current one.

Who else are you?

Joining the project in July of 2013, Nathan Dinsmore helped me port the interpreter to JavaScript. Sam Lazarus wrote many internal definitions for the standard library as well as a spiffy tutorial.