Lii
| Paradigm(s) | declarative, functional, object-oriented |
|---|---|
| Designed by | User:Fergusq |
| Appeared in | 2014 |
| Type system | static |
| Computational class | Turing complete |
| Reference implementation | Unimplemented |
| Influenced by | F# |
| File extension(s) | .lii |
Lii is a declarative object-oriented mildly esoteric language created by User:Fergusq in 2014. It is named after the fictional 31st century tea company Lii Tea (from a Sci-Fi book, I forgot the name). The language is a mix of functional and object-oriented programming.
Overview
In Lii, all objects are immutable and do not contain any fields. This means that all instances of a class are equal. New "values" are created by declaring new anonymous classes. All methods are side effectless.
A traditional Hello World:
Main { main -> BS { "Hello World!"; } }
Here, the class Main with the method main is declared. The method returns a BasicString (BS for short) object. The interpreter should output that string. This is the only way to output things in Lii.
Classes and methods
Classes always have a super class (default Object). Methods always have a return type.
For example, a class that represents a function (for implementation of the Natural class, see examples):
Function {
call(Natural val) -> Natural;
}
Anonymous classes
The ^ operator is used to create a new anonymous class instance.
^Function {
call(Natural val) -> Natural {
val.increment;
}
}
Built-in classes
Lii has a small standard library of important classes.
Object (O)
The base class of all classes. toString is an abstract method, calling it will raise an error.
Object : Object {
class -> Class;
toString -> BS;
}
Class (C)
Represents a class. The base class of all metaclasses. Can be used with the ^ operator to create a new anonymous class.
Class : Object {}
BasicString (BS)
Used for output. toString returns the string.
BasicString : Object {
append(BasicString str) -> BasicString;
toString -> BasicString;
}
Computational class
Lii is Turing complete, for example here are the implementations of SKI combinators:
Function {
call(Function f) -> Function;
}
I : Function {
call(Function x) -> Function {
x;
}
}
K : Function {
call(Function x) -> Function {
^ Function {
call(Function y) -> Function {
x;
}
};
}
}
S : Function {
call(Function f) -> Function {
^ Function {
call(Function g) -> Function {
^ Function {
call(Function x) -> Function {
f.call(x).call(g.call(x));
}
};
}
};
}
}
Related languages
CLii
Influenced by F#, CLii is a less esoteric variant of Lii. Although syntax has been completely redesigned, the core runtime system is same as Lii's.
Main {
main ->
"Hello, world!"
}
Another example:
Main {
.factorial(n) ->
if n = ^Natural{} then ^Natural{}.inc else .factorial(n.dec) * n
main ->
let test1 = .list()
let test2 = .fib(10).toString()
let test3 = .factorial(6).toString()
""+test1+"\n"+test2+"\n"+test3
list ->
let list = ^List{}.cons("world").cons("Hello, ") .. "!"
list.toString()
fib(i) ->
if i <= 1 then i else .fib(i - 1) + .fib(i - 2)
}
Examples
Hello world
Main {
main -> BS { "Hello world"; }
}
Fibonacci sequence
Calculates fib(6).
Main {
main -> BS {
self.fib(^Natural{}.increment.increment.increment.increment.increment.increment).toString();
}
fib(self m, Natural n) -> Natural {
// Please note that 0.decrement returns 0.
n.decrement.if(^Factory{
create->O {
m.fib(n.decrement).add(m.fib(n.decrement.decrement));
}
}, ^Factory{
create->O {
n;
}
});
}
}
Useful classes
Natural numbers via linked lists
Factory {
create -> O;
}
Natural {
link -> Natural;
if(Factory f, Factory o) -> O {
o.create();
}
toString -> BS {
"";
}
increment(self i) -> Natural {
^Natural {
link -> Natural {
i;
}
if(Factory f, Factory o) -> O {
f.create();
}
toString -> BS {
"*".append(i.toString());
}
decrement -> Natural {
self.link;
}
};
}
decrement -> Natural {
self;
}
// Addition and subtraction using if function. There are possible better ways to implement using linked lists
add(self a, Natural b) -> Natural {
b.if(
^ Factory {
create -> O {
a.increment.add(b.decrement);
}
},
^ Factory {
create -> O {
a;
}
}
);
}
subtract(self a, Natural b) -> Natural {
b.if(
^ Factory {
create -> O {
a.decrement.add(b.decrement);
}
},
^ Factory {
create -> O {
a;
}
}
);
}
}
Tape via stacks
Main {
main -> BasicString {
^ Tape {
// Initialize tape, push some zeros to the stacks
stack1 -> Stack {
^Stack{}.push(^ Natural)
.push(^ Natural)
.push(^ Natural);
}
stack2 -> Stack {
^Stack{}.push(^ Natural)
.push(^ Natural)
.push(^ Natural)
.push(^ Natural);
}
// Example usage
}.right().increment().increment().left().right().current().toString();
}
}
Entry {
prev -> Entry;
next -> Entry;
obj -> Natural;
}
Stack {
first -> Entry;
push(self stack, Natural o) -> Stack {
^ stack.class() {
first -> Entry {
^ Entry {
next -> Entry { stack.first(); }
obj -> Natural { o; }
};
}
};
}
pop(self stack) -> Stack {
^ stack.class() {
first -> Entry {
stack.first.next;
}
};
}
}
Tape {
stack1 -> Stack { ^ Stack; }
stack2 -> Stack { ^ Stack; }
current(self tape) -> Natural {
tape.stack2.first.obj;
}
left(self tape) -> Tape {
^ tape.class() {
stack1 -> Stack { tape.stack1.pop(); }
stack2 -> Stack { tape.stack2.push(tape.stack1.first.obj); }
};
}
right(self tape) -> Tape {
^ tape.class()
stack1 -> Stack { tape.stack1.push(tape.stack2.first.obj); }
stack2 -> Stack { tape.stack2.pop(); }
};
}
increment(self tape) -> Tape {
^ tape.class() {
stack2 -> Stack {
^ Stack {
first -> Entry {
^ tape.stack2.first.class {
obj -> Natural {
tape.stack2.first.obj.increment();
}
};
}
};
}
};
}
decrement(self tape) -> Tape {
^ tape.class() {
stack2 -> Stack {
^ Stack {
first -> Entry {
^ tape.stack2.first.class {
obj -> Natural {
tape.stack2.first.obj.decrement();
}
};
}
};
}
};
}
}
Linked list
Main {
main -> BS {
^List{}.cons("world").cons("Hello, ").append("!").toString();
}
}
// An empty pair
List {
car -> Object;
cdr -> List;
// = [o, l]
cons(self l, Object o) -> List {
^Pair {
car -> Object { o; }
cdr -> List { l; }
};
}
// = [o, []]
append(self l, Object o) -> List {
// This is same as cons, because the list is empty. See Pair for append for non-empty lists.
^Pair {
car -> Object { o; }
cdr -> List { l; }
// append = [o, [b, []]]
};
}
toString -> BS {
"";
}
}
// A non-empty pair
Pair : List {
toString -> BS {
self.car.toString().append(self.cdr.toString());
}
// = [o, l.a(n)]
append(self l, Object n) -> List {
^ self.class {
cdr -> List { l.cdr.append(n); }
};
}
}