Solace - a language to find solace from JavaScript.
src | ||
.gitignore | ||
index.js | ||
package.json | ||
pnpm-lock.yaml | ||
README.md |
Solace
A language to find solace after using JavaScript
Proposal: A strongly typed, programming language that compiles to JavaScript, with the following features:
- Error unions:
Error!string
- Optional values:
?string
- scoped blocks
- exhaustive pattern matching
- "unreachable" keyword
- built-in reactivity system (using signals?)
Syntax example (very early proposal, might change):
//imports work just as in ES6
import { func1, func2, func3 as funFunc } from './MyCoolModule'
import Cool from './MyCoolModule'
// Declarations can be constant, variable or live (a.k.a. reactive):
// Constants can never change
const c = 299792468 // neither type nor value will ever change
c++ // Error! Constant values cannot change.
const car = struct{
tires: 4,
engine: true
}
car.tires = 5 // Error! Unlike in JS, struct values cannot be changed, either
// Variables can be changed, as long as their type and shape stay the same:
var vehicle = struct{
tires: 2
engine: true
}
vehicle.engine = false // no problem
vehicle = struct{ // also fine
tires: 2
engine: false
}
vehicle = struct{ // Error! Type cannot be changed
color: 'red'
}
/* Types and Shapes?
*
* Solace differentiates between types (as in string, number, boolean)
* and shapes. While most types don't have shapes, structs ("objects") have.
* Solace is strict about shapes of structs and doesn't allow any undefined
* properties. Instead, property names should be known from the beginning
* and can be explicitely optional and set to null, if the value isn't known
*/
// TODO: introduce a dict type, that can be used without shape constraints?
// Live values can be changed and their changes are tracked
live speed = 50
track speed {
console.log(`Driving ${speed} km/h now`)
}
speed++ // logs speed automatically
speed = 'fast' // gives an error, because the type cannot change
// Computed values are basically getters (similar to Vuejs' computed)
computed apparentSpeed {
// pattern matching is another useful language feature that is explained in
// further detail later
return match speed {
10 -> 'walking speed'
_ < 50 -> 'inside town, maybe'
_ -> 'outside town, hopefully'
}
}
// Values can be optional
var userName: ?string = None // we don't know the name, yet
// Errors can be involved as well
var userName: !string = Error('user not found')
if (userFound) {
userName = 'Hans'
}
/* Optional values and Error Unions explained:
*
* ?string would be string | null in Typescript,
* !string would be string | Error,
* !?string would be (string | null) | Error
* Error unions can be specific: NetworkError!string
* Optional values can either be a value or None (there is no null in Solace)
*/
// undefined
var userName: string // undefined, because it is not initialized
userName = undefined // Error! You cannot set anything to undefined
// In Solace, a value can either be explicitely optional and set to None,
// or it can be undefined, because there was no assignment, yet.
// This makes the distinction between undefined and null very clear:
if (userName is undefined) // not initialized at all
if (userName is None) // explicitely set to None
// Guards: certain places can make use of guards to tighten their matches:
match userName when Some(name) {
name => console.log('User is named', name)
} else {
console.log('User has no name?!')
}
// defer allows you to run a code block after a function finished
async fn deleteUser(userId: string) {
const user: ?User = await fetch(`/api/users/${userId}`)
// lets not forget to send the user a mail
defer when user is Some(_) {
sendEmail(userId, `Greetings ${user.name}, you have been deleted!`)
}
await deleteUser(userId)
}
// functions look like this
fn doMath(): ?number {
if (!computer.ready) return None
return 42
}
//