Rye

What’s Rye? A high-level programming language designed for fluid, expressive coding.

How it’s different? Code is data, no-keywords, all functions, 41 datatypes, multi-lang, flexible language - strict about state

About Rye

Rye is a homoiconic, dynamic language inspired by Rebol, with influences from Factor, Linux shell, and Go. Still in active development, our goal is to make it practical and powerful ASAP.

Written in Go, Rye doubles as a scripting companion for Go programs. Its libraries integrate seamlessly, and Rye can be embedded as a scripting or config layer.

As a high-level language, Rye explores bridging coding and user interfaces. Its interactive console (REPL) features syntax highlighting, history, tab completion, and tools to explore the language and environment you’re crafting.

Short Rye examples

Change Hello World to Hello Mars and print it.
"Hello World" .replace "World" "Mars" |print
; prints "Hello Mars"
Find the Sum of unique numbers in a string.
"12 8 12 16 8 6" .load .unique .sum
; returns 42
Use switch function to get the sound of the animal.
switch 'cow { dog { "woof" } cow { "mooo" } cat { "meow" } }
; returns mooo
Extract combination of one number and one letter from string.
regexp "([0-9][a-z])" |submatch? "-7--x3--0k-r--"
; returns 0k
Print out names starting with A.
{ "Anne" "Joan" "Adam" } |filter { .first = "A" }
|for { .print } 

IO, Spreadsheet, HTTP client

Read a file, print the number of lines and the last 5 lines.
read\lines %data.txt |with
{ .length? .print , .tail 5 |print }
Load a CSV, get average score of all players of level 7.
load\csv %ryebots.csv |where-equal 'level 7
|column? 'score |avg
Load a webpage, save it to a file, print out all the links.
get https://ryelang.org |write* %page.html
|reader |parse-html { <a> [ .attr? 'href |print ] }
Load JSON over HTTP nad extract information, in a Goroutine
go does { get https://httpbin.org/get |parse-json
  -> "headers" -> "User-Agent" |print } print "Loading ..."

HTTP, SMTP server

Serve folder over HTTP
http-server ":8082"
|handle "/" new-static-handler %public_html
|serve
Web server that responds with current time.
http-server ":8081"
|handle "/time" fn { w r } { .write to-string now }
|serve
Start smtp server and print notification on new email.
handler: fn { mail from to origin } {
printv from "new mail from {}" }
smtp-server ":25" |serve ?handler "demo"
      

Dialects

Validation dialect
dict { name: "anakin" } |validate { 
   name: required calc { .capitalize } 
   score: optional 0 integer }
; returns { name: "Anakin" score: 0 }	  
Math dialect.
; Rye like Rebol or Lisp has no operator precedence.
; But it has Math dialect which has it and more.
math { 2 + 2 * sqrt ( 12 + ( 24 / 3 ) ) }
; returns 42
SQL dialect.
rye .args .first :id
open sqlite://data.db
|query { select * from operator where id = ?id }

GUI

See the Cookbook for many more GUI Examples.
do\in fyne {
	  
	lab: label "I'm Waiting ..."
	btn: button "Click here" does { lab .set-text "Finally ..." }
	box: v-box [ lab layout-spacer btn ]
	
	with app .window "Button" {
		.resize size 200.0 100.0 ,
		.set-content box ,
		.show-and-run
	}
}
See the Cookbook for many more GUI Examples.
 
do\in fyne {
  app .window "Percentage Clock" :win
  
  cont: v-box [
    label "This Year [days]" :ly
    progress-bar :py
    label "This month [days]"
    progress-bar :pM
    label "Today [hours]"
    progress-bar :ph
    label "This hour [minutes]"
    progress-bar :pm
    label "This minute [seconds]"
    progress-bar :ps
  ]
  
  m-of: ?multiple-of
  is-leap-year: fn { y } { all { y .m-of 4  not y .m-of 100  not y .m-of 400 } }
  days-in: fn { y } { .is-leap-year .either { 366 } { 365 } }

  go fn\par { } current {
    forever {
      with n:: now {
        .year? ::y |concat* "Year " |set-text* ly ,
        .year-day? / days-in y  |set-value* py ,
        .day? / days-in-month? n |set-value* pM ,
        .hour?     / 24 |set-value* ph ,
        .minute?   / 60 |set-value* pm ,
        .second?   / 60 |set-value* ps
        sleep 500
      }
    }
  }
  win |resize size 300.0 200.0 |set-content cont |show-and-run
}
 

Delve deeper

If examples above made you interested, you can delve deeper into the Rye language and Rye runtime. You have two sources for that, both are still work-in-progress, so check back later for more and better content:

Meet RyeFocuses on Rye language, it's basics, concepts and components
Rye CookbookFocuses on practical uses and specific technologies
Function referenceBasic documentation and simple examples for many core functions

Where

Rye can run on Linux, Mac OS or Windows. Rye also runs in a web-browser (Wasm) and Docker. Rye could be compiled for mobile (Android and iOS).

You can try a browser-based Rye console right now by clicking a button on top-right of this page.

Get Ryelang

x86 64arm 64wasm
Linuxv0.0.80
Windowsv0.0.80
Mac OSv0.0.80 v0.0.80
/v0.0.80
Homebrewbrew install ryelang
Dockerdocker pull ghcr.io/refaktor/rye:latest

Development

Rye lives on GitHub—visit, star, report issues, or contribute! Building from source is easy with Go; see the README for instructions..

Bateries included

Rye's main binary comes with core language functions and also a lot of bindings and technologies already included. Below are some examples of what's already included:

AWS Bcrypt BSON Crypto FT search Goroutines HTML parser HTTP servers JSON MySQL Psql Postmark Regexp SMTP server SXML SQLite

Sub-projects

Rye can be extended internally or externally. Two bigger external extension are developed in their own repositories:

Fyne GUI frameworkRye-fyne repository Download: Linux MacOS Windows
Gio UI frameworkRye-gio repository
Ebitengine game engineRye-ebitengine repository
RyegenRyegen repository

Related sites

Github Blog Reddit Old blog Asciinema Youtube

Rye concepts

Rye is still evolving. The basic ideas are formed, but we are still exploring, so your feedback is welcome!

Very flexible (but uniform) about the language

Everything is an Expression

All constructs (even control flow, print, assignments) return a value. Expression based code composes better and requires less state handling. And it's easier to write in Rye.

b: print 123 + a: print 123
; prints:
; 123
; 234 and assigns 123 to a and 234 to b

direction: 'out
print2 either direction = 'in { "Hello" } { "Bye" } ", mr. Jones"
; prints:
; Bye, mr. Jones

load\csv %bears.csv |column? 'weight |with { .avg .print , .max .print }
; prints:
; 214
; 749    

Code is Data

Rye code consists of Rye values (e.g., blocks, words, literal values, ...). There is no difference between Rye code and Rye data. This brings internal consistency and options for code introspection.

thats-true: { print "That's true" }

do thats-true
; prints That's true    
    
if 1 > 0 thats-true
; prints That's true    

person: context { name: "Joe" }
print-name: fn\in { } person { print name }
; prints Joe

First-Class Everything

Words, functions, blocks of code, scopes (contexts), and literals are all values that can be created, passed, returned, or assigned. Every value of Rye Runtime is also accessible to the language itself.

some-word: 'this-word
some-func: fn { x } { x + 1 }

inspect-fn: fn { f } {
  print2 "Code: " dump ?f
  print2 "Result f(100): " f 100
}

inspect-fn ?some-func
; prints:
; Code: fn { x } { x + 1 }
; Result f(100): 101

some-scope: context { name: "Joe" }
print-name: fn\in { } some-scope { print name }
; prints Joe

Functions are all you need

Rye has no keywords and no special forms. For example if, loop, fn, try, return are all built-in functions and every active component of the language is just a built-in or an ordinary function call.

probe ?if
; Prints:
; [Pure BFunction(2): Executes a block of code if the condition is true, returning the result of the block or false.]

probe ?fn
; Prints:
; [Pure BFunction(2): Creates a function with named parameters specified in the first block and code in the second block.]

; our custom if
if-joe: fn { n code } { if n = "Joe" { do code } }
if-joe "Joe" { print "Hey" }
; prints Hey
    
flip-flop-loop: fn { n a b } { .loop { .is-odd .either { a } { b } |do } }
flip-flop-loop 5 { prns "Tik" } { prns "Tok" }
; Prints:
; Tik Tok Tik Tok Tik

Very strict about state

Constants by default

Words you define with set-words are all constants and can't be redefined in given context. This being the default gives you certainty that by default things won't change under your feet.

name: "Tango"

name: "Cash"
; produces error, you can't use a set-word (one colon) to set an already set word

name:: "Cash"
; also produces error, you can't use a mod-word (two colons) to modify a constant

; you need to use var, or mod-word directly to define or modify a variable word
var 'current-month "January"
current-month:: "February"

current-year:: 2024  ; mod-word also creates a variable word if not yet created
current-year:: 2025

cant-redefine: fn { } { "me" }

Explicit about modification of a word

As we've demonstrated above, set-words can only set words once. Modification of a word is visually explicit, and somewhat visually painfull, so you only use it when you have concrete reason.

{ "jim" "jane" "oto" } .for { :name , print capitalize name }
; produces error, set-word can't set an already set word

    
; sometimes you must modify a value of the word
{ "jim" "jane" "oto" } .for { ::name , print capitalize name }
; prints:
; Jim
; Jane
; Oto

; but also a lot of those cases can be solved better
{ "jim" "jane" "oto" } .for { .capitalize .print }
; prints:
; Jim
; Jane
; Oto

No invisible modification in-place

Rye code usually always returns a new value rather than modify a value and it has good support for this way of solving problems. For rare and specific cases where you do need to chanve a value in-place, the functions it has must end with exclamation mark (!).

; many cases where you would use append have better solutions
names: { "jane" "anne" "john" }

; you don't want to do this
jay-s: { }
for names {
    ::n
    if first n = "j" { append! jay-s n }
}

; if you can do 
filter names { .first = "j" }
    

Failures, Not Nulls

Functions return the result or Fail, not nulls.
Unhandled failures become runtime errors (e.g., try or match to recover).

State and Data Flow

Information-Oriented Flow

Minimize state; emphasize data transformation pipelines.

Separation of State and Logic

Data is immutable by default; mutation is explicit and localized.
"Practically functional": pure functions where possible, controlled side effects where needed.

Explicit Validation

Declarative validation DSL.

Data Structures

Table Datatype

Built-in spreadsheet-like tables for tabular data (e.g., SQL-like ops, columnar transforms).

Philosophy

Words-in-Context

Avoid paradigm-specific terms (e.g., "methods," "monads").
Focus on "words" (symbols) bound to functions/values in named contexts (scopes).

Errors vs. Failures

Failures: Expected, recoverable (e.g., "file not found").
Errors: Bugs; halt execution and debug.

Thank you

Thanks for exploring Rye! If it sparks your interest, join us—star us on GitHub, follow on Reddit or YouTube, or drop an email. GitHub updates frequently, and we’d love your help with docs, tests, examples, or code! Or just send me an email.