Note: This overview is very incomplete, and leaves out a lot of features whose structure is still being finalized. Additionally, everything listed here is subject to changes and revisions.
func main() do
println("Hello World!")
endfunc main() do
let foo = 10
println(foo)
endLite infers types of variables, so you don't have to provide explicit type annotations everywhere. By default, variables are immutable, but you can declare them as mutable with the mut keyword.
func main() do
let mut foo = 10
foo = 20
println(foo)
endLite also has arrays, which are hold a bunch of values, each of which have to be of the same type.
func main() do
let numbers = [1, 2, 3, 4, 5]
println(numbers)
endLite also has tuples, which are collections of values which can be of different types.
func main() do
let points = (10, -4)
endEach tuple has its own type signature. The signature of points in the example above is (int, int).
Lite has all the expected logical operators, alongside the negation ! and negative - infix operators.
if 5 == 5 do
println("All is right with the world")
else do
println("what the heck?")
endConditionals in Lite are much like what you'd see in other languages. One difference with languages like Python or Java is that conditionals in Lite, alongside many other language constructs, are expressions, and can evaluate to a value. Hence why they'd be called if expressions instead of if statements. if will evaluate to the value of the last expression in whatever branch of the conditional is accepted. Here's an example.
func main() do
let foo = 10;
let bar = if foo == 10 do
"is ten"
else do
"is not ten"
end
println(bar) // "is ten"
endAll branches in an if expression must evaluate to the same data type.
for loops in Lite are more like for loops in Python or Rust as opposed to C or Java.
func main() do
for i in 1..10 do
println(i)
end
endLite loops are pretty much what you'd see in any other imperative language.
while 1 == 1 do
println("all is right with the world")
endYou can create functions in Lite using the func keyword.
func add(a: Int, b: Int) -> Int do
a + b
endWhen defining a function, any parameters you list need to be annotated with a type. If the function returns anything, it must also be annotated with the -> Type syntax. The main() function you'll see defined in binaries is the starting point of a Lite program.
The last expression in any function is what is returned. Alternatively, you can use an explicit return in cases where the previous option may not be applicable.
types are the cornerstone of Lite's type system, they allow you to define things like sum types (which we'll call enums) and record types, as well as aliases to other types.
type has three forms. Enums, records, and aliases.
You can define an enum with the following syntax
type Color = do
Red or
Green or
Blue
end
func main() do
let color = Color.Green
println(color)
endYou can shorten the above declaration of Color to
type Color do
Red or
Green or
Blue
endpretty much removing the =.
Enums let you define something which can be any variant of a set of values. Another powerful feature of enums is that you can associate some data with a variant. Here's an example.
type Message do
Success or
Failure(Str)
end
func main() do
let failed_message = Message.Failure("Oh no, a problem occurred")
endThe data you can associate with an enum variants can also be much more structured, like a record.
You can define a record with the following syntax
type Person do
name: Str and
age: Int and
job: Str
end
func main() do
let joe = Person("joe", 36, "accountant")
println(joe.name)
endRecords are a lot like structs in other language.
You can also use type to alias pre-existing types.
type MyInt = IntTwo useful enums defined in the standard library are Option and Result.
Option is Lite's replacement for a None, Nil, or Null value. It lets you express that a value can either be something or nothing. It's defined as the following.
type Option[T] do
Some(T) or
None
endYou can use it in the following manner.
func main() do
let an_int = Some(10);
println(an_int);
let no_value = None;
println(no_value);
endThe reason you can directly access the Some and None variants of Option is because they are already imported in the prelude.
Result is Lite's take on error handling. You can use Result to represent a successful operation with some value attached, or an error. It's defined as the following.
type Result[T, E] do
Ok(T) or
Err(E)
endOne important feature Lite has is pattern matching, through its match syntax. You can use pattern matching to compare some value against patterns, and run some code based on what pattern matches. Patterns can be something like a constant value, or something like a range or tuple. You can also destructure things like records and enums.
type Status do
Finished or
Pending or
Preparing or
end
func main() do
let status = Status.Ready
match status do
Status.Finished -> println("Operation has finished")
Status.Pending -> println("Operation is pending")
Status.Preparing -> println("Operation is preparing")
end
endYou can also match against enums with variants that have values attached. We can display this with the build in Option enum.
type User do
name: Str
end
func get_user(user_id: Int) -> Option[User] do
let user = fetch_user_from_db(user_id)
user
end
func main() do
let user = get_user(1)
match user do
Ok(u) -> println("Hello {u.name}")
None -> println("User does not exist")
end
endLike if, for, and while, Lite's match is also an expression. It evaluates to the value of whichever branch is chosen.
Lite has support for algebraic effects. Algebraic effects are a way for you to define some "effect" that a function may exhibit, and in turn handle them. You can draw a comparison with languages with try/except, like Python. The difference is that instead of catching exceptions, you can catch any kind of effect. Additionally, instead of stopping execution of the function, you can resume operation while also being able to give back some value to the function. Here's an example which defines the functionality of generators through effects.
effect Yield[T] do
func yield(a: T)
end
func counter() -> Yield[Int] do
let mut n = 0
while true do
yield(n)
n += 1
end
end
handle counter() do
yield(a) -> do
println(a)
resume
end
endEffects in Lite are defined with the effect keyword, followed by its name and a list of operations under the effect. You can denote that a function exhibits some effects through its return type signature, as seen in the -> Yield[Int]. In the body, yield is called as any other function. You can then use the handle construct to handle effects. handle looks similar to match, and you can use syntax similar to pattern matching to handle multiple effects. The keyword resume is used whenever you want to resume execution of a function. You can also invoke resume with a value, which is sent back to the function.