Rye is a dynamic, homoiconic language inspired by Rebol, Factor, Shell, and Go.
Its main claim is that you can have a flexible syntax while being strict about state. Rye has no Null, is constant by default, treats everything as an expression, has no keywords, and every active element is a function. Words are evaluated in a hierarchy of contexts… more about its principles
But all this has already been described in multiple places. Here, we try to teach quickly by showing compact, practical code with minimal commentary. So let’s dive in…
; # 80% of Rye in 20% of the Time [1/3]
; _inspired by Learn X in Y minutes_
; This is a comment
;
; Heads-up: every Rye token must be separated by a space
; ## The Values
; These are some literal values
1 ; integer
3.14 ; decimal
"hello" ; string
{ 1 2 "three" } ; block
; there is no null
; These are constructed values (they use a constructor function)
true ; function that returns a boolean value
dict { "name" "Jim" "age" 32 }
table { "planet" "diameter" } { "Earth" 12756 "Mars" 6779 }
fn { a b } { a + b }
context { name: "Jim" age: 32 }
; There are multiple word value types
a-word ; evaluates to its bound value
set-word: ; creates a constant binding (right to left)
mod-word:: ; creates/modifies a variable (right to left)
?get-word ; returns value without calling if function
'lit-word ; evaluates to the word itself
.op-word ; takes left value as first argument
; The list above is not exhaustive
; ## The Rye Code
; Rye data consists of blocks of Rye values
{ "some" 'data }
{ { some } { 'more } ?data |or .code }
{ print "Hello world" }
{ if hot { open 'main-window } }
; ## The Evaluator
; Rye has multiple evaluators, but the main evaluator does this:
"literal values" ; evaluate to themselves
123.45
303
{ "blocks" don-t .eval } ; and so do blocks
'lit-words ; and lit-words (evaluate to a word)
; Word behavior is determined by its type
word ; evaluates to the value it's bound to
; or error
print "Yello" ; if bound to a function it calls the function
set-word: "value" ; creates a constant word and binds a value to it
mod-word:: 1 ; modifies or creates a variable of type integer
"on the left" :lset-word ; like set-word, but takes value from the left
2 ::lmod-word ; same for lmod-word
?print ; get-word returns the value, even when it's a function
; The evaluator has more behavior, but we must first meet more value types
; ## The built-in functions
; The evaluator has clear rules for evaluating blocks of Rye values, and that’s it.
; It has no keywords or special forms beyond that. So to do anything we need
; functions, built-in functions for starters.
; We can build a minimal Rye runtime with behavior as described above and
; with, for example just 4 built-in functions.
; true - accepts no arguments and returns a boolean true
; print - accepts any Rye value and prints it
; if - accepts a boolean and a block of code
; loop - accepts an integer and a block of code
; We can now write our first program
if true { print "Hello world" }
; and a second one
loop 3 { print "Wah" }
; prints: Wah
; Wah
; Wah
; Let's register more built-ins to our runtime: prns, range, for\, either
; and is-multiple-of
for\ range 1 3 'i { print either is-multiple-of i 2 { "Tik" } { "Tok" } }
; prints: Tok Tik Tok
; `for\` is just a variant of `for` that also accepts a word
; to set each iteration. Regular `for` doesn't need that.
; Everything above is just application of (built-in) functions. Let's dissect:
for\ ; - takes a block, a word (i) and a block of code to evaluate
range ; - takes two integers and produces a block { 1 2 3 }
print ; - prints the argument
either ; - takes a boolean and two blocks of code, returns result of block evaluation
is-multiple-of ; takes two integers and returns a boolean
; priority of evaluation for block above
( print ( either ( is-multiple-of i 2 ) { "Tik" } { "Tok" } ) )
; ## More built-ins
; Rye has many built-in functions; here are a few to get a taste:
_+ 101 10 ; 111
_< 101 10 ; false
; _+ _< are also just functions, we usually
; use them as op-words [more on that later]
and true false ; false
any { false "Jim" 101 } ; Jim
all { true _> 10 5 "Bob" } ; Bob
_++ upper "ban" "ana" ; BANana - priority: ( _++ ( upper "ban" ) "ana" )
index? "Joey" "e" ; 2 [convention: noun? means get-noun]
join\with { "uncle" "bob" } "," ; uncle,bob
head { 11 22 33 } 2 ; { 11 22 }
max { 48 256 127 } ; 256
length? union { 1 2 } { 2 3 } ; 3
transpose { { 1 2 } { "a" "b" } } ; { { 1 "a" } { 2 "b" } }
; Here we will use op-words, which we will fully introduce in [2/3]
sort\by { "abc" "cd" "e" } { .length? } ; { "e" "cd" "abc" }
map { 1 2 3 } { * 10 } ; { 10 20 30 }
reduce range 10 20 'acc { * acc } ; 6704425728000
for { 11 12 13 } { .prns } ; prints: 11 12 13
var 'i 0 ; function var creates a variable
type? 123.4 ; decimal
to-integer "404" ; 404
now .year? ; 2026
"[ 1, 2, 3 ]" |parse-json ; list { 1 2 3 }
match-block { 1 2 3 } { a b c } , a + c ; 4
match { 7 5 3 } { { a : bb } { a - avg bb } } ; 3.0
; To see many more built-in function visit the Function reference:
;
; https://ryelang.org/info/base.html
;
The function reference mentioned above.
This should be the first third of the document. This post shows the basics. The next two should be more interesting, but probably also a little harder to explain. Stay tuned!
Visit the main page or our GitHub.
Updates:
- The second part is already online: Part 2
- Related discussion on Lobste.rs
- Related discussion on r/ProgramingLanguages