Cheat Sheet

From Swym Wiki
Jump to: navigation, search

Swym is a web-native language, currently implemented in Javascript. No installation is necessary - you can just click Run on any of the programs in this wiki. To write your own programs, I recommend the interpreter at:

(There's no repl yet, but you can type any program into the input box there and run it with the Run button; the URL will then contain an encoding of your program, which you can share with others, like this.).

Some other interesting addresses - - Swym's source code repository on GitHub - the Swym standard library - a Swym interpreter hooked up to an HTML5 canvas - the Swym automated test suite


[edit] Core Data Types

Number, and Int (a subtype of Number that doesn't match fractional numbers).

 1, -77, 60.2


 true, false


 "This is a string with a\nnewline and \"quotes\""


 [ 1, 10.5, true, false, "an array of miscellaneous values" ]


 { "type"=>"a table", "style"=>"like ruby!" }

Block (lambda functions)

 'arg'->{ arg+1 }

Void: return type of a function that doesn't return anything. The only value of this type is void.

Anything: any value except void. (This is the default when no type is specified.)

DontCare: any value, including void. DontCare typically indicates "this code will never run" - for example, an empty array is considered to contain values of type DontCare.


(Notice that by convention, type names begin with capital letters.)

[edit] Common Tasks

 // this is a comment
 /* so is this */
 "string".at(3)             // get an element from a string or array (first index is 0)
                          // NB: square brackets can NOT be used to index an array.
 { "x"=>"a table" }.at("x") // the same function also works on tables
 [1,2,3].2nd                // .1st, .2nd and so on also work for accessing elements.
 [1..10].length             // the length of an array
 "Hello" + "World"          // concatenate arrays or strings
 random[1..100]             // Swym's main support for random numbers: selecting a random element from an array
 print("Hello World!")      // prints the string.
 println("Hello World!")    // prints the string, followed by a newline.
 trace("Hello World!")      // for debugging. Prints "Hello World" - including quotes.
 alert("Nag!")                       // javascript alert
 'name' = input("Enter your name")   // javascript input popup
 'localConstant' = 99          // single quotes indicate declarations
 Int:'localVariable' = 0       // colon is a type declaration - on a local declaration, it also indicates that it's mutable.
                               // initializing the variable is not optional.
 localVariable = 2             // and of course, a variable can be assigned. Other assignment operators don't currently work.(No +=, ++, etc.)

Line breaks are the only syntactically meaningful whitespace in Swym. Here are the rules:

Two newlines in a row (i.e. a blank line) is a hard separator - equivalent to a semicolon. (As in C, the ; operator is a statement separator. It's rarely used in Swym.)

One newline is a soft separator - no separator is generated if the operator just before it requires a right-hand operand, or the operator just after it requires a left-hand operand. (NB: . and - are valid prefix operators where the left hand side is optional. A newline before a . or - does act as a separator.)

Additionally, an open (parenthesis) or open {brace} operator after a newline does NOT generate a separator. This enables function calls to be split across multiple lines. (NB: The open [bracket] operator does not behave this way. Inconsistent? Kinda. It's a heuristic: [ is often the beginning of a new statement, whereas { and ( are often used to pass additional arguments.)

[edit] Nothings

Swym has no concept of a null value. (void simply denotes that no value can ever be at this position; novalues is equivalent to iterating over an empty array - a function that takes novalues as its argument will not actually run at all. See multivalues for more information.)

Instead of null you can use a Maybe object:

'x' = random[, Maybe.none ]

println( x.value else{ "nothing!" } )

Similarly, when a table might have an undefined member, you can use a conditional accessor:"x") else{ "missing x!" }

[edit] Strings

The terse toString operator $ converts any value to a minimalist string representation.


In particular, note that the $ operator does not annotate arrays with commas or any symbol. This makes it easy to collapse an array of strings into a single string:

$["Hello", " ", "World", "!"]

The debug toString operator $$ converts any value to a string containing (as far as possible) the Swym expression that would generate that value.


For convenience, the trace function prints a value using debug toString.

trace["This ", "is ", "an ", "array"]

Writing $ or $$ within a string results in string interpolation:

'foo' = "Hello"
print("The word $foo, as a string literal, is $$foo.")

There's no separate "char" type - a string just behaves like an array of one-element strings.

"Hello".2nd.trace // the string "e".
"H".1st.1st.1st.trace // the string "H".

Slightly weird consequence: an empty string is an array of length 0... and as such, it's equal to the empty array, even if they don't look the same.

"" == []

[edit] Function Calls

Fundamentally, Swym is a functional language. Almost every expression is a function. Although there are many types of syntactic sugar affecting how function calls look, they all boil down to the following base form, with named arguments:

 functionName( arg1=value1, arg2=value2, arg3=value3 )

You may omit some or all argument names; the anonymous arguments will be matched up with the corresponding positioned arguments in the function declaration.

 functionName( value1, arg3=value3, value2 )

Instead of separating arguments with commas, you can equivalently separate them by putting them in multiple parenthesised blocks: (NB: that's ONE function call. No currying here!)

 functionName( value1, arg3=value3 )( value2 )

If such a function argument is delimited by brackets or braces - i.e. a literal [array], {block}, or {key=>value} expression - then you can just put it bare on its own; it doesn't need parentheses around it.

 functionName( arg3=[ array3 ] )[ array1 ]{ block2 }

And as we saw above, you can split these expressions across multiple lines.

Int:'x' = 10
while{ x > 4 } // note the braces - to reevaluate the condition, it needs to be a block.
  x = x-1

...yes, that is a function call. Equivalent to while( test={x>4}, body={println(x); x=x-1} ).

The . operator is an alternate way to pass an argument named "this".

 foo.functionName // equivalent to functionName(this=foo)

If the . operator is used without a left-hand operand, it will use the value named "it", or (failing that), "this" from the calling scope.

 .functionName // equivalent to it.functionName or this.functionName, depending on context

The else operator allows you to pass an argument named "else".

 if( x > y )

Again, yes, that's a function call. Equivalent to if( cond=x>y, body={x}, else={y} ). NB: As a result, in Swym, it's not legal to omit the braces around the body of an if statement. You're actually constructing a block.

[edit] Function Declarations

Function declarations look like this - note the single quotes again.

  .length == 0 // remember, when the . operator has no left side, it implicitly uses "this".
"".isEmpty, isEmpty[99] // prefix and postfix style calls are equally valid

And again, this is equivalent to 'isEmpty'(Array:'this') { this.length == 0 }

Naturally, you can also pass more than one argument to a function, like the if function we saw earlier. Declaring such a function looks like this (note again the single quotes for introducing a new name):

Array.'insert'('value', 'at')
  .slice(end=at) + [value] + .slice(start=at)
[0..10].insert(101, at=3)

It's fine to overload a function with multiple versions that take different types, or different numbers of arguments - at compile time, it'll figure out which version you meant. (If it's ambiguous, of course, then that's an error. There's currently no support for run-time polymorphism... it's on the to-do list.)

It's also not a problem for functions to have the same name as local variables; they don't conflict.

As you've probably noticed, a function returns the value of the last expression with the function body. If you're just writing a quick one-line function declaration, it can be clearer to use the returns keyword instead of {braces}:

Array.'insert'('value', 'at') returns .slice(end=at) + [value] + .slice(start=at)

[0..10].insert(101, at=3)

Alternatively, if you want to explicitly return a value after doing some work, you can use the return keyword:

  if(this == 0) { return "Zero" }
  if(this == 1) { return "One" }
  if(this == 2) { return "Two" }
  return "Many"

NB: Recall that an expression such as { return "Zero" } is defining a block. Because if statements in Swym use blocks, it's not so useful for a return to break out of the current block. So instead, it breaks out of the entire enclosing function.

Some other neat features; overloading an operator is as simple as declaring a function of that name:

Number.'+'(String:'s') returns this + s.toInt

5 + "23"

(Work in progress - currently this only works with the + - * / % ^ == != < > <= and >= operators.)

And if you declare a function whose name begins with a #, it implicitly takes a number argument named #, and can (optionally) be called by writing that number instead of the # sign.

'#squared' returns # * #
12squared // equivalent to #squared(#=12)

This is used to implement the .1st, .2nd (and so on) functions we've already seen.

 Array.'#th' { .at(#-1) }

I also plan to use it to implement units, such as 4km or 90deg.

[edit] Blocks

(a.k.a. lambda functions)

In Swym, lambda functions are known as "blocks". We've already seen some of these in the earlier examples. You create a block by simply writing any expression within curly brackets.

'myblock' = { sqrt(144) }

To evaluate a block, call the do function, and pass in the block as one of its arguments. Optionally, you may provide one additional argument, which the block will receive as a value named "it".

'x' = { sqrt(it) }

You may recall that "it" is the default value used if you don't give an explicit input to the dot operator. This allows you to write very terse, "point free" style blocks:

'x' = {.sqrt}

Alternatively, you can name the argument using the -> operator:

'x' = 'number'->{ sqrt(number) }

FAQ: No, regular functions are not first-class values. Only blocks are. If you want to turn a function into a value you can pass around, it's trivial to just wrap it in a block:

'length' = {.length} // look, it's a block that calls the 'length' function.

FAQ: No, a block cannot take more than one argument. (If you need to do something more complex, you should probably be declaring a function instead.)

Having said that, if a block takes an array as an argument, you can trivially destructure the array into named values in the block's argument declaration, like this:

'divide' = ['a','b']->{ a/b }

And similarly with a table:

'divide' = {"numerator"=>'a', "denominator"=>'b'}->{ a/b }
{"numerator"=>1, "denominator"=>10}.(divide)

[edit] Types

Swym is a statically typed language in which types behave, ostensibly, like first-class values. You can pass types to functions, etc. However, the language is statically typed, so type declarations must be determinable at compile time. (In other words, they're approximately like C++ templates.)

Here are some examples of built-in functions that take and/or return a type - // test whether this value (at runtime) matches the given type.
 Int.Array // an array of integers
 0.Literal // a singleton type - only matches one value, the exact number 0.
           // As the name implies, the value must be known at compile time.
 x.Type    // what's the type of value x? This is a compile-time function,
           // so the type will match all _possible_ values of x.

So, for example, you could declare a function that only works on arrays of integers:

Int.Array.'composeDigits' returns .$.toInt


Another important function is Struct, which creates a new type with a list of named elements. Each element must have a name. Types and default values for the elements can optionally be provided.

'Color' = Struct{ Int:'r', Int:'g', Int:'b', Int:'a'=255 }
// naturally, you can declare new functions that work on the types you declare
Color.'darken' {, .g/2, .b/2, .a) }
'red' =,0,0) // new is what C++ programmers would call a 'static' function.
// You can use Literal to declare new 'static' functions of your own.
Color.Literal.'pink' returns,160,160) 
// all values of the type Color automatically support functions named r g b & a.
g( )

The type Type accepts any type. So you can declare a function that takes a type as an argument.

Type.'matches'('value') returns
Int.matches(3) // equivalent to

[edit] Array Processing

Arrays are one of the most important types in Swym. If you're working with any kind of sequential data, you almost certainly want to represent it as an array: they're very flexible, and very well supported. The standard library contains a huge variety of built-in functions, and even a few operators, that manipulate arrays. Here are a few of the most commonly used.

'x' = 5
println( x ==any [1..100] ) // Yes, that's an operator named '==any'. Returns true if the array contains this value.
println( x !=any [1..100] ) // the opposite
// also known to functional programmers as 'map'
'result' = forEach[1..10] 'element'->
  element + 1
[1..100].where{ .$.1st == .$.last }, // all elements with the same number at the start and end
[1..100].firstWhere{ .$.1st == .$.last }, // the first such element
[1..100].firstWhere{ >100 } else { -1 }, // evaluates the else block if nothing passes the test.
["Guten Tag", "Aloha", "Bonjour", "Hello"].min{.length}, // find the shortest element
//(or if several elements are tied for shortest, the first of them.)
["Guten Tag", "Aloha", "Bonjour", "Hello"].whereMin{.length}, // list all the equal-shortest elements
// slice has a wide variety of different modes to select chunks from an array.
"Hello, World!".slice(length=5),
"Hello, World!".slice(end=9),
"Hello, World!".slice[0..3],
"Hello, World!".slice[0..<3],

Cells are also worth mentioning here. These are nothing magical - just a few structures and functions provided by the Swym standard library - but they're a very convenient way to iterate over elements in an array while keeping track of the current index and/or referring to adjacent elements.

forEach([30..10].cells) 'cell'->
  if( cell.key % 2 == 1 )
    cell.previousCell.value // if index is odd, return the previous value
    cell.value // else return the current value

The cells function takes an array, and returns an array of structs of type Cell. There's one cell for each position in the array. The key of a cell is its index; calling value returns the value at that position. And there are a bunch of other functions, such as previousCell and nextCell, which are a convenient way to access neighboring array elements.

[edit] Multivalues

At its core, Swym supports... a rather unusual approach to iterating over arrays. The each function takes an array, and returns something called a multivalue, representing each element of the array. Anything you do to that multivalue gets applied independently to each element of the array; and when you're done, you can box up the result into a new array using the familiar [bracket] operator.

'array' = [1,2,3,4,5]
[array.each + 10] // adds 10 to each element of the array

Multivalues can also be generated by various operators.

 5,10,15 // comma - concatenates two values or multivalues together into one multivalue.
 1..10 // 1,2,3,4,5,6,7,8,9,10: a range of integers
 1..<10 // 1,2,3,4,5,6,7,8,9: a range, excluding the endpoint
 1<..<10 // 2,3,4,5,6,7,8,9: and/or the startpoint, or any other combination you need. 
 2**6 // 2,2,2,2,2,2: a value repeated some number of times

If you pass a multivalue as an argument to a function call or operator, the function or operator runs multiple times - once for each value. The results are collected into a new multivalue.

["Hello", "Goodbye", "Hola"].each.length

...and if you pass two multivalues as arguments to an operator or function call, it runs once for every possible pairing of values from the two multivalues.

'x' = 1..5
[ x*x ] // every possible way of multiplying two numbers from x

It can often be useful to control this behaviour by calling a block:

'x' = 1..5
[ x.{ it*it } ] // squaring each element of x

Note that because of these rules, when writing a block or function you can be sure that the values passed in are not multivalues. If a multivalue argument does get passsed in, the entire block or function gets invoked multiple times; each invocation just receives a single value.

[edit] Quantifiers

Quantifiers are very closely related to multivalues. You can create quantifiers using the functions some, all and none. (I'm afraid there's no quantifier corresponding to logical NAND, because english doesn't have a word for that.)

'array' = [1..10]
if[ array.some == 5 ] { print("Hello 5!") }

Just like a multivalue, if a quantifier is passed to any operator or function, that expression will be evaluated for each element of the quantifier separately. When you're done, you can resolve it into a single boolean value with the [bracket] operator.

One very nice thing about quantifiers is that you can use more than one in a single expression. They're evaluated in left-to-right order:

 if[ oneOrMore(people).likes(all(people)) ]{...} // is there a person who likes everybody?
 if[ all(people).likedBy(oneOrMore(people)) ]{...} // does everybody have at least somebody who likes them?

(Because the meaning of the word some can be unclear, especially on arrays with plural names, the function oneOrMore is predefined as a synonym for it.)

[edit] Etc

The etc keyword is a complex topic, but I'll summarize the important concepts here. As the name implies, etc instructs the compiler to deduce the rest of the expression based on what you've written so far. For example:

1/2 + 2/4 + 3/8 + etc

etc must follow an infix operator (in this case +), and it looks back across the expression, expecting to see multiple uses of that operator, and tries to extrapolate the next term in the sequence from the arguments it sees. It can handle moderately complex sequences.

'x' = [1..10]
[x.1st,  x.1st*x.2nd,  x.1st*x.2nd*x.3rd,  etc]

If it's accessing an array, it will helpfully stop as soon as it sees an out-of-bounds value. Aside from this rule, you can explicitly provide a halting condition by writing etc.. (to tell it to stop when it reaches a term with the given value).

[1, 2, 4, etc..1024]

Other halting conditions include etc** (to generate a specific number of terms), and etc..< (to stop at any term greater than or equal to the halting value).

Etc understands arithmetic, geometric (if it has at least 3 examples), and quadratic (if it has at least 4 examples) number sequences.

[1, 2, 4, 7, etc..<100]
Personal tools