133 lines
3.9 KiB
Markdown
133 lines
3.9 KiB
Markdown
# 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):
|
|
|
|
```ts
|
|
//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
|
|
}
|
|
|
|
//
|
|
|
|
```
|