Sake-lang

Sake (/seɪk/, as in “for the sake of”) is an experimental programming language. It is designed for the sake of finding a new place for types. It keeps Ruby’s syntax, but you write the type on each operation, never on a variable, a parameter, or a field:

String.upcase(name)        # Sake
name.upcase                # Ruby: an error in Sake, reported with the fix above

Because every operation names its type:

The motivation was to make a language that is easy for AI to write. The evaluation did not support it (2026-10-08); development continues at a small scale, from what using the language turns up. See Status.

Writing Sake with a language model? Give it docs/cheatsheet.md: the rules on four pages plus the compact list of operations, about 10k tokens, and all a model needs (raw: https://raw.githubusercontent.com/ko1/sake-lang/main/docs/cheatsheet.md). The full reference manual and the playground are at https://ko1.github.io/sake-lang/.

A taste

class Point
  attr_reader x, y
  def norm2(p) = @x * @x + @y * @y        # @x is Point.x(p): p is the first argument
end

words = String.split("the cat and the hat", " ")
counts = Hash.new(0)
Array.each(words) { |w| counts[w] += 1 }
p(counts)
puts(Point.norm2(Point.new(3, 4)))
$ bin/sake readme.sake
{"the" => 2, "cat" => 1, "and" => 1, "hat" => 1}
25

Mistakes are reported before anything runs, each with a fix:

name = "sake"
puts(name.upcase)
puts(String.upcse(name))
$ bin/sake mistakes.sake
mistakes.sake:2:11: error: method call on a value `name.upcase` is not allowed
  hint: String.upcase(name)
  hint: Symbol.upcase(name)
mistakes.sake:3:13: error: undefined function `String.upcse`
  hint: did you mean `String.upcase`?

Samples

Getting started

The interpreter is written in Ruby, parses with Prism, and walks the AST. It needs Ruby 4.0 (tested with 4.0.2 and Prism 1.9.0) and has no other dependencies.

bin/sake FILE.sake              # check, then run
bin/sake -c FILE.sake           # check only
bin/sake --strict FILE.sake     # check more strictly before running (level 2)
bin/sake --strict=3 FILE.sake   # levels 0-4, or items: --strict=type,nil
bin/sake --types FILE.sake      # experimental: print the inferred types
bin/sake --dump=ast FILE.sake   # print the SakeAST (the resolved program that runs)

The exit status is 0 on success, 1 for an error while running, and 2 for a problem found before running.

Native code through Rust (experimental)

bin/sabic compiles a program to a native executable by way of Rust, for the subset whose types the generator can fix: Integer (as 64-bit, an overflow is an error), Float, true/false, nil as Option, String, Array, Tuple, classes, blocks, unions of classes as a Rust enum with mixin calls as a match, and case/in on values and types. Anything outside the subset is reported with its line, and the program still runs on the interpreter. A program that the checks of bin/sake stop at its default level is not compiled either.

bin/sabic FILE.sake            # writes FILE.rs and builds FILE with rustc -O
bin/sabic FILE.sake --emit     # only the Rust source

On three micro benchmarks (loops, fib, levenshtein) the compiled program ran 3 to 21 times faster than Ruby 4.0 with YJIT, 8 to 53 times faster than Ruby without it, and within 0.9 to 1.6 times of Rust written by hand (experiments/2026-10-10-rust-backend/). A mixin call dispatched on the argument’s type costs 0.12 ns, about the same as a hand-written enum and an eighth of Rust’s dyn Trait. Against Spinel, the Ruby AOT compiler, the same programs ran at the same speed on loops and levenshtein and 8 times faster on the dispatch benchmark, with an analyzer of 1,100 lines of Ruby where Spinel’s is 107,000 lines of C, because Sake’s rules do that work in the language. bin/ceec does the same through C, on the same type inference, and runs at the same speed as the Rust output; against hand-written C, the remaining gap is the overflow check on every Integer operation (fib: 0.186 s with it, 0.065 s without, hand C 0.064). The interpreter is 150 to 760 times slower than Ruby with YJIT.

Documentation

What is in the language

Not yet: case/when, and first-class blocks. Built-in constants such as Math::PI are read as operations (Math.PI); a program has no value constants (def pi = 3.14159 instead).

Repository layout

bin/sake                 the command
bin/sabic                the compiler to native code through Rust (sabi, 錆, is rust)
bin/ceec                 the compiler to native code through C
lib/sake/                resolver (checks before running), lower (Prism AST to SakeAST), interpreter
                         and typer (type inference), both on SakeAST, the Rust and C backends (rust.rb, c.rb)
                         with their range analysis (ranges.rb), standard library (stdlib*.rb)
sakelib/                 the library written in Sake: the prelude (Enum) and ports of Ruby's libraries and gems
test/                    golden tests (test/samples/*.sake with *.expected), CLI tests, test/native/ (programs
                         both native backends must print like the interpreter)
examples/                example programs by category, each with its expected output (examples/README.md)
docs/                    tutorial, specification, built-in list, one-page guide, examples
tools/                   generators for the docs (they run the examples)
experiments/             experiments, each with its method, results, and limits in a README

Run the tests with:

ruby test/test_samples.rb       # UPDATE=1 rewrites the expected outputs
ruby test/test_cli.rb           # the command line, and whether the generated docs are up to date
ruby test/test_examples.rb      # examples/**/*.sake print their *.expected (UPDATE=1 rewrites them)
ruby test/test_sakelib.rb       # the library ports: each test/sakelib/X.sake prints what X.rb prints
ruby test/test_sake_suite.rb    # Sake's test suite written in Sake (test/sake/*_test.sake, on sakelib/minitest.sake)
ruby test/test_ide.rb           # the playground's Ruby side (lib/sake/ide.rb)
ruby test/test_rust.rb          # the Rust backend: test/native/*.sake compiled and compared with the interpreter (needs rustc)
ruby test/test_c.rb             # the C backend: the same programs through bin/ceec (needs cc)
ruby tools/check_reference.rb   # the built-in reference (docs/manual/*/ref): signatures, coverage, and every example

Status

The evaluation concluded on 2026-10-08. Development goes on at a small scale: fixes and features that come from using the language (on 2026-10-10, about 40 findings of the built-in reference, and nested namespaces).

This was a research prototype. The hypothesis was that writing types on operations is better than writing them on variables, for AI to write and to understand code. The evaluation (experiments/2026-10-05-ai-writability/, in Japanese; summary page: https://claude.ai/artifact/NaxNc5iropLucuvhawKETD) could not separate that from a larger effect: Sake is a language no model has seen before.

What the evaluation showed:

What would justify reopening it: a task where Sonnet-class models fall clearly below the ceiling, and static type checks (in any language) are seen to stop errors that the dynamically typed solutions ship.

Not measured: whether people read Sake more easily, for example when reviewing code that AI wrote. “One line tells you which function runs” is also a claim about a human reader, and the evaluation only had AI read. Testing it needs people: review the same AI-written changes with planted bugs in Ruby and in Sake, and compare how many bugs are found and how long it takes. The reviewers already know Ruby, so the comparison is biased against Sake.

The design notes and their reasons are in DESIGN.md.

Known issues (not fixed)

License

MIT