One language for everything
# the answer, computed the long way
double := fn (x := i32 ?) -> i32 (
x + x
),
mut sum := i32 0,
for i in 0..7 (
sum = sum + i
),
print «answer {double(sum)}» # answer 42# a literal is an exact fraction until a typed place
# takes it; only then does it become a machine number
print «{0.1 + 0.2 == 0.3}», # true
x := f64 0.1,
print «{x + 0.2 == 0.3}» # false: now an f64# a function can return a type; the call runs at
# parse time and becomes the type it yields
pick := fn (i := i32 ?) -> type (
if (i == 0) (i32) else (f64)
),
mut a := pick(1) ?, # a is an f64 place
a = 9.9,
print «{pick(0) == i32}» # true# interpreted by default; the source asks for machine
# code, and the next call jumps to it
sum_to := fn (n := i64 ?) -> i64 (
mut i := i64 0,
mut s := i64 0,
while (i < n) (
s = s + i,
i = i + 1
),
s
),
compile sum_to,
print «{sum_to(1000000)}»# alloc returns an owning pointer and writes the
# teardown into this scope itself: `defer free a`
a := alloc 1 of i32 40,
b := own a, # ownership and its free move to b
print «b {b@}» # b 40# own moves the value and ends the name on that line;
# the parse refuses a later use, nothing waits for run
a := alloc 1 of i32 40,
b := own a,
print «{b@}» # 40
print «{a@}» # error: `a` is dead here# a value's type is read with :type, since it is not
# one of its own fields; types compare by identity
x := i32 5,
same := x:type == i32, # true
cross := x:type == f64, # false
meta := i32:type == type, # true: the root type
print «{same and meta and not (cross)}» # true# a conjecture states a fact; its proof is the chain
# of relations that gets there, each citing a rule
twice :=
conjecture ( a + a == 2 * a )
where ( a:type is number ),
cancel :=
conjecture ( a / a == 1 )
where ( a:type is number and a != 0 ),
half :=
conjecture ( (a + a) / a == 2 )
where ( a:type is number and a != 0 )
proof (
(a + a) / a
== twice(a + a) / a
== mul_div_assoc((2 * a) / a)
== 2 * cancel(a / a)
== mul_one(2 * 1)
== 2
),
print «{half((3 + 3) / 3)}» # 2# the language defines itself: a power operator `^`,
# written in ordinary Logos and used in the same file,
# over integers and floats, with any real exponent
# (exp and ln are ordinary Logos functions too)
^ := type (
lhs := ?,
rhs := ?,
output_type := type ?,
share parse_rank = *.parse_rank + 1,
share associativity = right,
share parse = (
tape[0]:type = ^,
tape[0].lhs = tape[-1],
tape[0].rhs = tape[1],
if tape[-1]:type == f64 or tape[1]:type == f64 (
tape[0].output_type = f64
) else if tape[-1]:type == f32 or tape[1]:type == f32 (
tape[0].output_type = f32
) else if tape[1]:type == rational_number and not (tape[1]:type ⊆ i64) (
tape[0].output_type = f64
) else (
tape[0].output_type = tape[-1]:type
),
tape.is_constructed[0] = true,
tape.remove(1),
tape.remove(-1)
),
# The run is built after the program's own names exist, so its locals avoid common ones.
share run = (
if (f64(rhs) == f64(i64(rhs))) (
mut power := output_type 1,
mut times := i64(rhs),
mut factor := output_type(lhs),
if times < 0 (
if not (output_type == (f64 or f32 or rational_number))
error «a negative exponent of a whole number is not whole: write the base as a float»,
factor = output_type 1 / factor,
times = 0 - times
),
for 0..times
( power = power * factor ),
power
) else if (output_type == (f64 or f32)) (
real_base := f64(lhs),
if real_base < 0.0
error «a negative base has no real power for a fractional exponent»,
if real_base == 0.0
output_type 0
else
output_type (exp(f64(rhs) * ln(real_base)))
) else
error «a fractional exponent needs a float result»
)
),
f := fn (x := i32 ?) -> i32 ( x ^ 3 + 1 ),
compile f,
print «{f(2)}», # 9
x := f64 2.0,
print «{x ^ 0.5}», # 1.4142135623730954
print «{x ^ -2}» # 0.25# with ^ from the tab before: every part of it is a
# node the program can read, and reading runs nothing
e := 2 ^ 3,
print «{e}» # 8
print «{(2 ^ 3).lhs} {(2 ^ 3).rhs}» # 2 3, not run
print «{^.parse_rank > *.parse_rank}» # tighter
print «{^.associativity == right}» # true
print «{^.fields}» # lhs rhs output_type
print «{^.run}» # its run body
print «{^.parse}» # its parse body
# finding the arity:
mut n := ^.fields.length,
for i in ^.fields (
if i:name == «output_type» (n -= 1)
if share is i:gate (n -= 1)
),
print «arity {n}» # arity 2# a scope is a node: it knows the one it stands in and
# holds one field, dyads, its nodes in order
x := i32 1,
g := (
a := i32 1,
b := a + 1,
print «{here.scope.back == x:scope}», # up: true
b
),
s := g:start.rhs, # g's := node, then its right side
print «{s.back == here.scope}», # true: one up
print «{s.dyads.size}», # 5: comments are nodes
print «{s.dyads[0].lhs:name}», # a
print «{s.dyads[0].rhs:type == i32}», # true
print «{s.dyads[1].rhs.lhs:name}», # a, read in b
# caller: inside a constructor, where its word stood
mut seen := here.scope,
w := type (
share parse = (
seen = caller.scope, tape.remove(0)
)
),
h := ( z := i32 3, w, z ), # w used inside h
print «{seen.back == here.scope}» # true: one up# a macro is a constructor: lex turns text into tape
# cells, and the constructor splices them in
twice := type (
share parse_rank = *.parse_rank + 1,
share parse = (
tape.insert(1, lex «* 2»),
tape.remove(0)
)
),
print «{3 + 4 twice}» # 11: 4 twice is 4 * 2# one word for both: an unmarked member is a place per
# instance, a share one is one place, read both ways
point := type (
x := i32 ?,
y := i32 ?,
share dims := i32 2
),
p := point (1, 2),
print «{p.x} {point.dims} {p.dims}» # 1 2 2// the answer, computed the long way
fn double(x: i32) -> i32 {
x + x
}
fn main() {
let mut sum = 0;
for i in 0..7 {
sum += i;
}
println!("{}", double(sum)); // 42
}// the answer, computed the long way
const std = @import("std");
fn double(x: i32) i32 {
return x + x;
}
pub fn main() void {
var sum: i32 = 0;
var i: i32 = 0;
while (i < 7) : (i += 1) sum += i;
std.debug.print("{d}\n", .{double(sum)}); // 42
}// the answer, computed the long way
#include <stdio.h>
/* double is a keyword, so: */
int twice(int x) { return x + x; }
int main(void) {
int sum = 0;
for (int i = 0; i < 7; i++) sum += i;
printf("%d\n", twice(sum)); /* 42 */
return 0;
}# the answer, computed the long way
def double(x: int) -> int:
return x + x
total = 0
for i in range(7):
total += i
print(double(total)) # 42// the answer, computed the long way
const double = (x: number): number => x + x;
let sum = 0;
for (let i = 0; i < 7; i++) sum += i;
console.log(double(sum)); // 42;; the answer, computed the long way
#lang racket
(define (double x) (+ x x))
(define sum
(for/sum ([i (in-range 7)]) i))
(displayln (double sum)) ; 42-- the answer, computed the long way
def double (x : Int) : Int := x + x
def main : IO Unit := do
let mut sum : Int := 0
for i in [0:7] do
sum := sum + i
IO.println (double sum) -- 42lacking: 0.1 is a float; no exact rational in std
// a literal is a float from the start; no exact
// rational in the language or its standard library
fn main() {
println!("{}", 0.1 + 0.2 == 0.3); // false
}lacking: no exact literals, not even at compile time
// a literal is a float from the start, even at
// compile time, where it is an f128
const std = @import("std");
pub fn main() void {
// false
std.debug.print("{}\n", .{0.1 + 0.2 == 0.3});
}lacking: no exact literals; 0.1 is a double
// a literal is a double from the start
#include <stdio.h>
int main(void) {
printf("%d\n", 0.1 + 0.2 == 0.3); /* 0: false */
return 0;
}lacking: 0.1 is a float; exact needs Fraction and a string
# a literal is a float; exactness is a library type
from fractions import Fraction as F
print(0.1 + 0.2 == 0.3) # False
print(F("0.1") + F("0.2") == F("0.3")) # Truelacking: no exact literals; 0.1 is a double
// a literal is a double from the start
console.log(0.1 + 0.2 === 0.3); // false;; a decimal literal is inexact unless marked #e,
;; which makes it an exact rational
#lang racket
(displayln (= (+ 0.1 0.2) 0.3)) ; #f
(displayln (= (+ #e0.1 #e0.2) #e0.3)) ; #t-- a Float literal rounds; as a ℚ it is exact
import Mathlib
#eval (0.1 : Float) + 0.2 == 0.3 -- false
example : (0.1 : ℚ) + 0.2 = 0.3 := by norm_numnot supported
// a function can return a type, run at compile time
const std = @import("std");
fn pick(i: u8) type {
return if (i == 0) i32 else f64;
}
pub fn main() void {
const a: pick(1) = 9.9; // a is an f64
const same = pick(0) == i32;
std.debug.print("{d} {}\n", .{ a, same });
}not supported
# types are values, so a function can return one,
# though nothing checks what is then done with it
def pick(i: int) -> type:
return int if i == 0 else float
a = pick(1)(9.9) # an ordinary float
print(pick(0) is int) # Truenot supported
not supported
-- a definition can return a type; types are terms
def pick (i : Nat) : Type :=
if i = 0 then Int else Float
def a : pick 1 := (9.9 : Float) -- a is a Float
example : pick 0 = Int := rflnot supported
not supported
not supported
not supported
not supported
not supported
not supported
// a Box owns its heap value and frees it when the
// owner goes out of scope; a move hands that duty on
fn main() {
let a = Box::new(40);
let b = a; // ownership moves to b; a is gone
println!("{}", *b); // 40
}not supported
not supported
not supported
not supported
not supported
not supported
// a move ends the name; a later use is a compile
// error, nothing waits for run time
fn main() {
let a = Box::new(40);
let b = a;
println!("{}", *b); // 40
println!("{}", *a); // error[E0382]: moved value
}not supported
not supported
not supported
not supported
not supported
not supported
// a value's type has an identity at run time,
// though nothing more of it can be read back
use std::any::{Any, TypeId};
fn main() {
let x: i32 = 5;
let same = x.type_id() == TypeId::of::<i32>();
let cross = x.type_id() == TypeId::of::<f64>();
println!("{}", same && !cross); // true
}// @TypeOf reads a type at compile time; types compare
// with ==
const std = @import("std");
pub fn main() void {
const x: i32 = 5;
const same = @TypeOf(x) == i32; // true
const cross = @TypeOf(x) == f64; // false
const meta = @TypeOf(i32) == type; // true
const all = same and meta and !cross;
std.debug.print("{}\n", .{all});
}not supported
# types are objects, and `type` is its own type
x = 5
same = type(x) is int # True
cross = type(x) is float # False
meta = type(int) is type # True
print(same and meta and not cross)// typeof names a handful of run-time kinds; the
// static types themselves are gone by run time
const x = 5;
const same = typeof x === "number"; // true
const cross = typeof x === "string"; // false
console.log(same && !cross);not supported
-- types are terms; a metaprogram reads a value's type
import Lean
open Lean Meta
def x : Int := 5
#eval show MetaM Bool from do
let t ← inferType (mkConst ``x)
return t == mkConst ``Int -- truenot supported
not supported
not supported
not supported
not supported
not supported
-- a theorem is a type and its proof a term the kernel
-- checks; the rewrites are the same steps
import Mathlib
theorem half (a : ℚ) (h : a ≠ 0) :
(a + a) / a = 2 := by
rw [← two_mul, mul_div_assoc, div_self h, mul_one]
#print axioms half -- the world it rests onnot supported
not supported
not supported
not supported
not supported
lacking: no precedence, one operator per parenthesis; the math is the built-in expt
;; a module can redefine application itself, so
;; (x ^ 3) reads the operator between its operands
#lang racket
(module infix racket
(require (for-syntax syntax/parse))
(provide (rename-out [app #%app])
(except-out (all-from-out racket) #%app))
(define-syntax (app stx)
(syntax-parse stx
[(_ lhs (~literal ^) rhs) #'(expt lhs rhs)]
[(_ f x ...) #'(#%app f x ...)])))
(module main (submod ".." infix)
(define (f x) (+ (x ^ 3) 1))
(displayln (f 2))) ; 9, one operator per parenthesislacking: Int base and Nat exponent only: no floats, no negative or fractional exponent
-- a new operator is a notation, written in Lean and
-- used in the same file
def power (b : Int) : Nat → Int
| 0 => 1
| n + 1 => b * power b n
infixr:75 " ** " => power
def f (x : Int) : Int := x ** 3 + 1
#eval f 2 -- 9not supported
lacking: parameters and return type only; the body cannot be read
// @typeInfo reads a function's parameters and return
// type at compile time; its body cannot be read
const std = @import("std");
fn power(b: i32, n: u32) i32 {
var r: i32 = 1;
for (0..n) |_| r *= b;
return r;
}
pub fn main() void {
const info = @typeInfo(@TypeOf(power)).@"fn";
std.debug.print("{d} {s}\n", .{
info.params.len,
@typeName(info.return_type.?),
});
}not supported
lacking: source comes back as text and bytecode, not nodes; an expression runs before it can be read
# a function is an object: its signature, its source
# and its bytecode can all be read back
import inspect, dis
def power(b: int, n: int) -> int:
return b ** n
print(inspect.signature(power)) # the signature
print(inspect.getsource(power)) # the source text
dis.dis(power) # the bytecodelacking: name, arity and source text only; the types are erased
// a function is an object: its name, arity and source
// text can be read back; its types are erased
function power(b: number, n: number): number {
return b ** n;
}
console.log(power.name, power.length); // power 2
console.log(power.toString()); // the source textlacking: name and arity only; the body is gone once compiled
;; a procedure's name and arity can be read back;
;; its body cannot, once compiled
#lang racket
(define (power b n) (expt b n))
(displayln (object-name power)) ; power
(displayln (procedure-arity power)) ; 2lacking: read only from a metaprogram, not by the running program
-- a definition is a term the environment holds, and a
-- metaprogram reads its type and its body back
import Lean
open Lean Meta
def power (b : Int) : Nat → Int
| 0 => 1
| n + 1 => b * power b n
#print power -- the definition: its type and body
#eval show MetaM Expr from do
return (← getConstInfo ``power).typelacking: file and line only; no scope to read or walk up
// the spot a line is written at is text, not a scope:
// its file and line; nothing walks up from it
fn main() {
println!("{}:{}", file!(), line!()); // file, line
}lacking: file, line and the type around; the scopes above are not values
// @src() is the spot a line is written at and @This()
// the type around it; the scopes above are not values
const std = @import("std");
pub fn main() void {
const here = @src();
std.debug.print("{s}:{d}\n", .{
here.file, here.line,
});
}lacking: file and line only; no scope to read or walk up
// the spot a line is written at is text, not a scope:
// its file and line; nothing walks up from it
#include <stdio.h>
int main(void) {
printf("%s:%d\n", __FILE__, __LINE__);
return 0;
}lacking: call frames at run time; the lexical scopes are not values
# the call stack is reachable as frames at run time;
# the lexical scopes themselves are not values
import inspect
x = 1
def g():
y = 2
f = inspect.currentframe()
print("y" in f.f_locals) # True: here
print("x" in f.f_back.f_locals) # True: one up
g()not supported
lacking: a namespace to look names up in; the scopes are not values
;; a namespace lists what a spot can name; the scopes
;; a line stands in are not values, hygiene keeps them
#lang racket
(define x 1)
(define-namespace-anchor here)
(define ns (namespace-anchor->namespace here))
(displayln (namespace-variable-value 'x #t #f ns)) ; 1lacking: names in scope, only in a metaprogram; the scopes above are not values
-- inside a metaprogram, the local context is a value:
-- the names in scope, in order; nothing above them
import Lean
open Lean Elab Term
elab "names_here" : term => do
let ns := (← getLCtx).decls.toList.filterMap
(fun d => d.map (·.userName))
logInfo m!"{ns}"
return mkNatLit 0
def f (x y : Nat) : Nat := names_here -- [x, y]lacking: prefix only; `4 twice` cannot be written
// a macro rewrites tokens, written in Rust and used
// in the same file; prefix only, never `4 twice`
macro_rules! twice {
($e:expr) => { $e * 2 };
}
fn main() {
println!("{}", 3 + twice!(4)); // 11
}not supported
lacking: prefix only; `4 TWICE` cannot be written
// a macro rewrites text before the compiler reads it;
// prefix only, never `4 TWICE`
#include <stdio.h>
#define TWICE(e) ((e) * 2)
int main(void) {
printf("%d\n", 3 + TWICE(4)); /* 11 */
return 0;
}not supported
not supported
lacking: prefix only; `4 twice` cannot be written
;; a macro rewrites syntax, written in Racket and used
;; in the same file; prefix, as every form is a list
#lang racket
(define-syntax-rule (twice e) (* e 2))
(displayln (+ 3 (twice 4))) ; 11-- a notation is a syntax rewrite, written in Lean and
-- used in the same file, postfix included
notation:70 x:70 " twice" => x * 2
#eval 3 + 4 twice -- 11lacking: `Point::DIMS` cannot be read through a value
// a struct holds the fields; an associated const is
// read through the type only, never through a value
struct Point { x: i32, y: i32 }
impl Point { const DIMS: i32 = 2; }
fn main() {
let p = Point { x: 1, y: 2 };
println!("{} {}", p.x + p.y, Point::DIMS); // 3 2
}lacking: `Point.dims` cannot be read through a value
// a struct is also a namespace: a decl inside it is
// read through the type, never through a value
const std = @import("std");
const Point = struct {
x: i32,
y: i32,
const dims: i32 = 2;
};
pub fn main() void {
const p = Point{ .x = 1, .y = 2 };
std.debug.print("{d} {d}\n", .{p.x, Point.dims});
}not supported
# a class is both: instance attributes and class
# attributes, the latter read through either
class Point:
dims = 2
def __init__(self, x, y):
self.x, self.y = x, y
p = Point(1, 2)
print(p.x, Point.dims, p.dims) # 1 2 2lacking: `Point.dims` cannot be read through an instance
// a class holds fields; a static member is read
// through the class, not through an instance
class Point {
static dims = 2;
constructor(public x: number, public y: number) {}
}
const p = new Point(1, 2);
console.log(p.x, Point.dims); // 1 2not supported
lacking: `Point.dims` cannot be read through a value
-- a structure holds the fields; a definition in its
-- namespace is read through the name, not a value
structure Point where
x : Int
y : Int
def Point.dims : Int := 2
def p : Point := ⟨1, 2⟩
#eval (p.x, Point.dims) -- (1, 2)Comparison Matrix
- has it
- partial
- no
| Capability | Logos | C/C++ | Rust | Zig | Lean 4 | Unison | Racket | Smalltalk | Julia | Python | TS/JS | Mojo |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Memory safety without a GCownership and borrow checking, zero runtime cost | yes | no | yes | no | no | no | no | no | no | no | no | partial |
| Compiles to native machine codeAOT or JIT, systems-grade performance | yes | yes | yes | yes | yes | partial | partial | partial | yes | partial | partial | yes |
| The speed ceiling of C and Rustno GC or boxing tax, zero-cost abstractions | yes | yes | yes | yes | no | no | no | no | partial | no | no | yes |
| Targets GPUs and custom hardwarekernels written in the language itself, not shader strings | yes | yes | partial | partial | no | no | no | no | yes | partial | no | yes |
| Runs in the browsercompiles to WebAssembly or runs in a web page | partial | partial | partial | partial | no | no | no | partial | no | partial | yes | no |
| Multithreaded parallelismuse every core with shared memory | yes | yes | yes | yes | partial | partial | partial | no | yes | partial | partial | yes |
| Async concurrencyasync/await or lightweight tasks for IO-bound work | yes | partial | yes | partial | partial | yes | yes | partial | yes | yes | yes | partial |
| Formal proofs in the languagedependent types / theorem proving built in | yes | no | no | no | yes | no | no | no | no | no | no | no |
| Gradual verificationprove one part, leave the rest ordinary code | yes | no | no | no | yes | no | no | no | no | no | no | no |
| Effects tracked in typespurity, IO, async as capabilities the compiler checks | yes | no | partial | no | yes | yes | no | no | no | no | no | partial |
| Code as dataprograms are a structure the language can read | yes | no | partial | no | yes | partial | yes | yes | yes | yes | partial | no |
| Semantic reflectionthe readable structure carries types and checked facts | yes | partial | no | partial | yes | no | partial | partial | partial | partial | partial | no |
| Compile-time code executionrun ordinary code at compile time, results baked in | yes | partial | partial | yes | yes | no | yes | partial | yes | no | no | yes |
| Compiler extensible as a librarynew syntax and optimizations as ordinary libraries | yes | no | partial | no | yes | no | yes | yes | partial | no | partial | no |
| Hosts other languages as librariesembed an HDL or shader language without a new compiler | yes | no | partial | partial | yes | no | yes | no | partial | no | partial | no |
| Hygienic syntax extensionsyntax extensions can't capture names by accident | yes | no | partial | no | yes | no | yes | no | yes | no | no | no |
| First-class rewrite engineequality saturation shared by compiler and user code | yes | no | no | no | partial | no | partial | partial | no | no | no | no |
| Live systemredefine parts of a running program | yes | no | no | no | no | partial | partial | yes | yes | partial | partial | no |
| Image persistencesave the whole running system, resume it later | partial | no | no | no | no | no | no | yes | partial | no | no | no |
| Content-addressed codedefinitions identified by hash of their content | partial | no | no | no | no | yes | no | no | no | no | no | no |
| Usable todaya stable compiler you can build real software on now | no | yes | yes | partial | yes | yes | yes | yes | yes | yes | yes | partial |
| Backward-compatibility promisecode from years ago still builds and runs today | partial | yes | yes | no | partial | partial | yes | partial | yes | partial | yes | no |
| Package ecosystempackages, users, production track record | partial | yes | yes | partial | partial | partial | partial | partial | yes | yes | yes | no |