Since every programmer usually knows at least one C-style language (C/C++/C#/Java/JavaScript/PHP etc.), a quick way to get to know Lobster is seeing how it differs from what you're familiar with. Where C-style languages differ I will by default pick C# (as being the most "average" of the range), though sometimes may show multiple language alternatives (as noted in the comments). This document mostly focusses on things that are different, i.e. you will not see how a + b in Lobster is the same as a + b in C#. |
Lobster version | C-style version | Comments |
a = 1 | a = 1; | Assign existing variable. |
var a = 1 | int a = 1; | Create new variable, type thru type inference. |
let a = 1 | const int a = 1; | Create constant variable. |
let a, b = 1, 2 | int a = 1; int b = 2; | Define multiple at once. |
let a, b = f() | int a = f(&b); | Define multiple from a function returning multiple values. |
let v = [ 1, 2, 3 ] | int[] v = new int[] { 1, 2, 3 }; | The type of vectors is inferred. They are also resizable, so they are probably more akin a C# List than an array. |
let w = v + v | vec3 w = new Vec3; w.x = v.x + v.x; w.y = v.y + v.y; w.z = v.z + v.z; |
Vector operation exist for most operators and functions. |
nil | null | Nil is more strict in lobster, in that a reference type which can be nil is different from those that are never nil, and you explicitly convert between them. |
a or b and c | (a || b) && c | These are really the only operators that are different. |
let a = f() or g() | var a = f(); if (a == null) a = g(); |
The or operator works slightly differently in that rather than returning true, it returns whatever value was true (and true is everything that is not nil, false, 0 or 0.0) |
Lobster version | C-style version | Comments |
if a: b else: c | if (a) b; else c | |
if a: b c else: d e |
if (a) { b; c; } else { d; e; } |
|
for(m) i: print i | for (int i = 0; i < m; i++) print(i); | |
for m: print _ | for (int i = 0; i < m; i++) print(i); | |
for(m) i: print i |
for (int i = 0; i < m; i++) { print(i); } |
|
while a: b | while (a) b; | |
for(list) a, i: print a print i |
for(int i = 0; i < list.size(); i++) { auto a = list[i]; print(a); print(i); } |
Where `a` is the item at index `i` of `list. |
for(list): print _ print _i |
for(int _i = 0; _i < list.size(); _i++) { auto _ = list[_i]; print(_); print(_i); } |
Where `_` is the item at index `_i` of `list`. Here `_` and `_i` are anonymous parameters that Lobster auto populates. They must be prefixed with `_`, and are assigned by their declaration order in the body. |
for(list) a: print a | foreach (var a in list) print(a); | |
let a = switch i: case 1: "no" case 2, 4..6: "yes" default: "maybe" | switch (i) { case 1: a = "no"; break; case 2: case 4: case 5: case 6: a = "yes"; break; default: a = "maybe"; } | |
let r = map(list) x: x * x | var r = new List(); foreach(var x in list) r.Add(x * x); |
(map needs: import std) |
let r = map(list) x: x * x | var r = list.ConvertAll(x => x * x); | C# and pretty much all programming languages except for Java (< 8) can nowadays use some form of lambdas that are similar but not quite as powerful as the ones in Lobster (for example, you can't use "return" to break out of a loop). |
let r = filter list: _ > 0 | var r = new List(); foreach (var x in list) if (x > 0) r.Add(x); |
|
let r = exists list: _ > 0 | var r = false; foreach (var x in list) if (x > 0) { r = true; break; } |
Lobster version | C-style version | Comments |
def name(a:int, b:int) -> int: return a + b | int name(int a, int b) { return a + b; } | In Lobster it is unusual to specify types, especially the return type. |
def name(a, b): return a + b |
T name<T>(T a, T b) { return a + b; } | By default function are declared without types, and automatically inferred on use. |
def name(a, b): return a + b |
T name<T>(T a, T b) { return a + b; } |
For multiple lines, use indentation. |
def magnitude(v::xy): return sqrt(x * x + y * y) |
// inside class xy float magnitude() { return sqrt(x * x + y * y); } |
The :: way to indicate a type (v is of type xy) allows you to access object elements directly rather than having to write v.x etc. This is similar to writing a method, though Lobster makes no such distinction. |
// inside class/struct xy def magnitude(): return sqrt(x * x + y * y) | // inside class xy float magnitude() { return sqrt(x * x + y * y); } | This is an equivalent way of writing the above when inside the scope of a class/struct (see below). |
v.magnitude() | v.magnitude() | On a function call, you can move the first argument to before the call, for any function, not just "methods". |
def name(x:X): return 0 def name(y:Y): return 1 |
/* inside class X */ int name() { return 0; } /* inside class Y */ int name() { return 1; } |
You write multiple function implementations for multiple types, which can then be called and resolved either statically (overloading) or dynamically (dynamic dispatch) depending on the situation. These functions can work on user defined types or builtin ones. They can be written by whoever wrote the type (even in-line in the class definition), or completely separately. |
let f = def(a, b): a + b | var f = (int a, int b) => a + b | Function value in C# |
let f = def(a, b): a + b | auto f = [](int a, int a) { return a + b; } | Function value in C++ |
def fold(xs, init, fun): for(xs): init = fun(init, _) return init |
T fold<T>(List<T> xs, T init, Func<T, T, T> fun) { foreach (var x in xs) init = fun(init, x); return init; } |
How to write a Higher Order Function: a function that takes a function argument. In this case "fold": take a list, and apply the function to each element and the previous result, effectively "folding" the list into a single value. |
let r = fold(list, 0) a, b: a + b | var r = fold(list, 0, (a, b) => a + b); | How to call that function with a function value. This sums all values in a list. |
let r = fold(list, 0): _a + _b | var r = fold(list, 0, (a, b) => a + b); | Anonymous arguments in lexical order, see language reference. |
let r = fold(list, 0) a, b: print a a + b |
var r = fold(list, 0, (a, b) => { print(a); return a + b; }); |
Note how in most languages lambda syntax becomes messy when you want to use them with a larger body, and looking very different from builtin control structures. |
let r = fold(list, 0) a, b: if a < 0: return a a + b |
/* not possible */ | Another way in which lambdas in most languages differ from Lobster: you cannot return/break from the loop, making them often useless as control structures. In Lobster, they function just like real constrol structures would. By default, return returns from the lexically enclosing named function. |
def f(): g() return 0 def g(): if something: return 1 from f return 0 |
int f() { try { g(); return 0; } catch (int x) { return x; } } int g() { if (something) throw 1; return 0; } |
Return even allows you to specify which function to return from, which in other languages you can do with exception handling. In fact, exception handling in Lobster is not a language feature, it is simply some utility functions (try/catch/...) implemented on top of return/from (implemented in exception.lobster). |
def mret(): return 1, 2 | int mret(out int o) { o = 2; return 1; } | |
let a, b = mret() | var b; var a = mret(&b); |
Lobster version | C-style version | Comments |
struct xy: x:float y:float |
public struct xy { float x; float y; } | |
struct xy<T> x:T y:T |
public struct xy<T> { T x; T y; } | Generics: unlike functions, these are explicitly specified in Lobster. |
struct xyz : xy: z:float |
public struct xyz : xy { float z; } | Inheritance. |
let v = xyz { 1, 0, 0 } | var v = new xyz(1, 0, 0) | You'd actually have to define the constructor in C# to be able to do this. |
struct xy: x:float y:float def magnitude(): return sqrt(x * x + y * y) | public class xy { float x; float y; float magnitude() { return x * x + y * y; } } | Intendation based syntax, with inline function definition (automatic this argument). |
enum color: red = 1 green blue yellow = 10 | enum color { red = 1, green, blue, yellow = 10 } |