127 lines
4.1 KiB
Text
127 lines
4.1 KiB
Text
/*
|
|
* Solace looks like Zig, but without the low-level complexities and added
|
|
* pattern matching. Typical expressions are very much like in TypeScript and
|
|
* it supports the same primitive types undefined, string, number, boolean,
|
|
* and their array versions, as well as Map and Set. It uses none instead of
|
|
* null and objects are called structs. There is no new keyword and there are
|
|
* no classes. Instead, Solace uses scopes that work like JavaScript's scopes.
|
|
*/
|
|
|
|
// Optional parameters
|
|
fn sum(a: number, b?: number): number {
|
|
if b is undefined {
|
|
return a
|
|
}
|
|
return a + b
|
|
}
|
|
|
|
// Optional values
|
|
fn sum(a: number, b: ?number): number {
|
|
if b is none { // b has to be defined, but can be set to none
|
|
return a
|
|
}
|
|
return a + b
|
|
}
|
|
|
|
// Error Unions
|
|
fn sum(a: number, b?: number): Error!number {
|
|
if b is undefined {
|
|
return failure("Second number is missing", Error)
|
|
}
|
|
return a + b
|
|
}
|
|
|
|
// Error unions default to Error, so the last example could also be written like
|
|
fn sum(a: number, b?: number): !number {
|
|
if b is undefined {
|
|
return failure("Second number is missing")
|
|
}
|
|
return a + b
|
|
}
|
|
|
|
// Solace also supports initialized default return values, similar to Swift's inout parameters.
|
|
// They are really useful for short functions:
|
|
|
|
fn fizzbuzz(until: number) => (output: string[] = []) {
|
|
// var output = [] // no need, because it is already defined
|
|
for i in 0..=until {
|
|
// pattern matching
|
|
match i {
|
|
_ % 3 == 0 && _ % 5 == 0 -> output.push("fizzbuzz")
|
|
_ % 3 == 0 -> output.push("fizz")
|
|
_ % 5 == 0 -> output.push("buzz")
|
|
_ -> output.push(i.toString())
|
|
}
|
|
}
|
|
|
|
// no need, because it's the default return value, but can added for clarity
|
|
// return output
|
|
}
|
|
|
|
// Matches are expressions, which means, they return a value.
|
|
// Also, just for completeness, match doesn't need a value. So here is the last example changed to take both into account:
|
|
|
|
fn numberToFizzBuzz(n: number): string {
|
|
return match {
|
|
i % 3 == 0 && i % 5 == 0 -> "fizzbuzz"
|
|
i % 3 == 0 -> "fizz"
|
|
i % 5 == 0 -> "buzz"
|
|
i -> i.toString()
|
|
}
|
|
}
|
|
|
|
fn fizzbuzz(until: number) => (output: string[] = []) {
|
|
for i in 0..=until: output.push(numberToFizzBuzz(i))
|
|
}
|
|
|
|
// Now to the more interesting parts of Solace: Reactivity and unified handling of values.
|
|
|
|
import fs from "node:fs/promises"
|
|
|
|
fn openFile(path: string) {
|
|
// fs.open returns a Promise, and while Solace supports them, it doesn't need
|
|
// Promises or async/await, because it has live values.
|
|
// Promises (and with them return values of async functions) are translated
|
|
// to reactive values if used like this:
|
|
live fd = fs.open(path, "rw")
|
|
|
|
// A defer statement runs after everything else finished, successful or not.
|
|
// You can see it like try-finally, for functions.
|
|
defer when ok(fd) {
|
|
// Because it is a nameless assignment, we use _ to
|
|
// access the return value. If you need a name, use:
|
|
// defer when ok(fd) |file| { /*...*/ }
|
|
_.close() // close file descriptor
|
|
}
|
|
|
|
// live variables would be defined like this in TypeScript:
|
|
// { state: "pending", value: Promise<T>, error: null } |
|
|
// { state: "err", value: null, error: Error } |
|
|
// { state: "ok", value: T, error: null }
|
|
watch fd {
|
|
match fd {
|
|
// ok(x) returns the value of Result types like live, which is essentially an Error Union,
|
|
// while err(x) returns the error or null
|
|
ok(fd) -> handleFile(_) // here _ refers to the return value of ok(fd)
|
|
err(fd) -> handleError(_) // here _ refers to the error value returned by err(fd)
|
|
_ -> continue // matches need to handle all possible cases, and continue just ignores
|
|
}
|
|
}
|
|
}
|
|
|
|
// here all the different ways to write loops:
|
|
const myArray: number[] = [23, 42]
|
|
for x in myArray: console.log(x)
|
|
for y,i in myArray: console.log(`Index ${i} has value ${y}!`)
|
|
|
|
for i in 0..<23 {
|
|
console.log("This will output as long as i is smaller than 23")
|
|
}
|
|
for i in 0..=23 {
|
|
console.log("This will output as long as i is smaller or equal than 23")
|
|
}
|
|
while i <= 23: console.log("This will output forever, because I forgot to change the value of i")
|
|
|
|
while i <= 23 {
|
|
console.log("This will output until i reaches 23. It's value is now", i++)
|
|
}
|