Lii

From Esolang
Jump to navigation Jump to search
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); }
		};
	}
}