voyd
v0.9.0
Published
Voyd is a high performance WebAssembly language with an emphasis on full stack web development.
Readme
Voyd
Voyd is a high performance WebAssembly language with an emphasis on full stack web development.
fn fib(n: i32) -> i32
if n < 2 then:
n
else:
fib(n - 1) + fib(n - 2)
pub fn main()
fib(10)Disclaimer
Voyd is in it's very early stages of development. Voyd is not ready for production use. Some syntax and semantics are still subject to change. Expect frequent breaking changes.
All features documented in README Overview are fully implemented unless otherwise stated. Features documented in the reference are not yet marked with status and may not be implemented.
Features:
- Functional
- Hybrid Nominal & Structural type system
- Algebraic effects
- First class wasm support
- Macros and language extensions
- Uniform function call syntax
Guiding Principles:
- Fun to write and read.
- Predictability
- Hackability
- Balance a great developer experience with performance
- Play nice with others
Getting Started
Install
npm i -g voydUsage Examples
# Run the exported main function
voyd --run script.voyd
# Compile a directory (containing an index.voyd) to webassembly
voyd --emit-wasm src > output.wasm
# Compile to optimized WebAssembly
voyd --emit-wasm --opt src > output.wasmRequirements
Currently requires node v22
# Or nvm
fnm install v22Overview
Quick overview of the language. More detailed reference available here
Comments
// Comments are single line and are marked with a c style slash slashPrimitive Types
true // Boolean
false // Boolean
1 // i32 by default
1.0 // f64 by default
"Hello!" // String literal, can be multiline, supports interpolation via ${} (NOTE: Interpolation not yet implemented)
[1, 2, 3] // Array literal. (NOTE: Not yet implemented. Arrays can be initialized with new_array<T>({ from: FixedArray(val1, val2, etc) })
(1, 2, 3) // Tuple literal (NOTE: Not yet implemented)
{x: 2, y: 4} // Structural object literalVariables
// Immutable variable
let my_immutable_var = 7
// Mutable variable
var my_var = 7Functions
A Basic function:
fn add(a: i32, b: i32) -> i32
a + bIn most cases the return type can be inferred
fn add(a: i32, b: i32)
a + bTo call a function, use the function name followed by the arguments in parenthesis
add(1, 2)Voyd also supports uniform function call syntax (UFCS), allowing functions to be called on a type as if they were methods of that type.
1.add(2)Labeled arguments
Status: Not yet implemented
Labeled arguments can be defined by wrapping parameters you wish to be labeled on call in curly braces.
fn add(a: i32, {to: i32}) = a + to
add(1, to: 2)By default, the argument label is the same as the parameter name. You can override this by specifying the label before the argument name.
fn add(a: i32, {to:b: i32}) = a + b
add(1, to: 2)Labeled arguments can be thought of as syntactic sugar for defining a object type parameter and destructuring it in the function body[1]:
fn move({ x: i32 y: i32 z: i32 }) -> void
// ...
// Semantically equivalent to:
fn move(vec: { x: i32 y: i32 z: i32 }) -> void
let { x, y, z } = vec
// ...
move(x: 1, y: 2, z: 3)
// Equivalent to:
move({ x: 1, y: 2, z: 3 })This allows you to still use object literal syntax for labeled arguments when it might be cleaner to do so. For example, when the variable names match the argument labels:
let (x, y, z) = (1, 2, 3)
// Object field shorthand allows for this:
move({ x, y, z })
// Which is better than
move(x: x, y: y, z: z)[1] The compiler will typically optimize this away, so there is no performance penalty for using labeled arguments.
If Expressions
if 3 < val then:
"hello" // true case
else:
"bye" // false case (optional)Ifs are expressions that return a value
let x = if 3 < val then: "hello" else: "bye"
# VSX DOM Client (Browser)
Voyd can encode VSX element trees as MsgPack from Wasm. A tiny client renderer mounts these trees to the DOM.
- Import: `import { render } from 'voyd/vsx-dom/client'`
- Call your component export (e.g. `run`) to write MsgPack into Wasm memory and return its byte length
- Pass either the `WebAssembly.Instance` or `WebAssembly.Memory` so the renderer can decode the buffer
Example (bundler-friendly):
```ts
import { render } from 'voyd/vsx-dom/client'
// Assume you already have Wasm bytes from a compiled Voyd module
const module = new WebAssembly.Module(wasmBytes)
const instance = new WebAssembly.Instance(module)
// Component function exported by Voyd module, which returns MsgPack length
const component = instance.exports.run as () => number
// Mount into the DOM
const container = document.getElementById('app')!
render(component, container, { instance })
## Loops
> Status: Partially implemented.
> - Tail call optimization fully implemented.
> - While loops and break partially implemented. Do not yet support returning a value.
> - For loops not yet implemented.
While loops are the most basic looping construct
```rust
while condition do:
do_work()For loops can iterate through items of an iterable (such as an array)
for item in iterable do:
print itemVoyd is also tail call optimized:
// This function is super speedy and uses very little memory
pub fn fib(n: i32, a: i32, b: i32) -> i32
if n < 1 then: a
else: fib(n - 1, b, a + b)
pub fn main() -> i32
fib(10, 0, 1)Structural Objects
Structural objects are types compatible with any other type containing at least the same fields as the structure.
fn get_x(obj: { x: i32 })
obj.x
pub fn main()
let vec = {
x: 1,
y: 2,
z: 3
}
vec.get_x() // 1Nominal Objects
Nominal objects attach a name (or brand) to a structure, and are only compatible with extensions of themselves.
obj Animal {
age: i32
}
obj Cat extends Animal {
age: i32,
lives: i32
}
obj Dog extends Animal {
age: i32,
borks: i32
}
fn get_age(animal: Animal)
animal.age
pub fn main()
let dog = Dog { age: 3, borks: 0 }
dog.get_age() // 3
let person = { age: 32 }
person.get_age() // Error { age: 32 } is not a type of animalMethods
obj Animal {
age: i32
}
impl Animal
pub fn get_age(animal: Animal)
animal.ageIntersections
Intersections combine a nominal type and a structural type to define a new type compatible with any subtype of the nominal type that also has the fields of the structural type.
obj Animal { age: i32 }
obj Snake extends Animal {}
obj Mammal extends Animal { legs: i32 }
type Walker = Animal & { legs: i32 }
fn get_legs(walker: Walker)
walker.legs
pub fn main()
let dog = Mammal { age: 2, legs: 4 }
dog.get_legsUnions
Unions define a type that can be one of a group of types
obj Apple {}
obj Lime {}
obj Orange {}
type Produce = Apple | Lime | OrangeMatch Statements
Match statements are used for type narrowing
obj Animal
obj Cat extends Animal
obj Dog extends Animal
let dog = Dog {}
match(dog)
Dog: print "Woof"
Cat: print "Meow"
else:
print "Blurb"Match statements must be exhaustive. When matching against a nominal object, they must have an else (default) condition. When matching against a union, they must have a case for each object in the union
Traits
Status: Partially implemented Supported for nominal types. Trait scoping is not yet enforced.
Traits define a set of behavior that can be implemented on any nominal object or intersection.
trait Walk
fn walk() -> i32
// Implement walk for any animal that contains the field legs: i32
impl Walk for Animal & { legs: i32 }
fn walk(self)
self.walk
// Traits are first class types
fn call_walk(walker: Walk)
walker.walk
fn do_work(o: Object)
// Traits also have runtime types
if (o has_trait Walk) then:
o.call_walk()Closures
Status: Not yet implemented
let double = n => n * 2
array.map n => n * 2Voyd also supports a concise syntax for passing closures to labeled arguments:
try do():
call_fn_that_has_exception_effect()
catch:
print "Error!"Dot Notation
The dot is a simple form of syntactic sugar
let x = 4
x.squared
// Translates to
squared(x)Generics
Status: Basic implementation complete for objects, functions, impls, and type aliases. Inference is not yet supported.
fn add<T>(a: T, b: T) -> T
a + bWith trait constraints
fn add<T: Numeric>(a: T, b: T) -> T
a + bEffects
Status: Not yet implemented
Effects (will be) a powerful construct of the voyd type system. Effects are useful for a large class of problems including type safe exceptions, dependency injection, test mocking and much more.
Think of libraries like TypeScript's Effect library, built directly into the language.
effect Exception
// An effect that may be resumed by the handler
ctl throw(msg: String) -> void
// Effects with one control can be defined concisely as
effect ctl throw(msg: String) -> void
effect State
// Tail resumptive effect, guaranteed to resume exactly once.
// Are defined like normal functions
fn get() -> Int
fn set(x: Int) -> void
// Tail resumptive effects with one function can be defined more concisely as
effect fn get() -> IntJSX
Status: Implementation in progress
Voyd has built in support for JSX. Useful for rendering websites or creating interactive web apps
fn app() -> JSX::Element
let todo_items = ["wake up", "eat", "code", "sleep"]
<div>
<h1>TODO</h1>
<ul>
{todo_items.map i => <li>{i}</li>}
</ul>
</div>