Status: experimental. Public API may change. Typing and some static checks are still evolving.
Feedback: have a use case or a paper cut? Open an issue or reach out.
Kumi is a declarative calculation DSL for Ruby. You write business rules once; Kumi:
- Builds a typed dependency graph with vector semantics.
- Performs static validation at definition time.
- Lowers to a portable Low-level IR (LIR).
- Code-generates standalone Ruby and JavaScript.
Targets: finance, tax, pricing, insurance, payroll, analytics—domains where correctness, transparency, and reproducibility matter.
Note: this is not available on the last published gem version (0.0.18), but if you clone this repository you can just copy a ./golden/$schema_name and modify schema.kumi as you and run bin/kumi golden test $schema_name
Kumi emits the kernel for your schema with no runtime in the target language. You can use it within your ruby application backend and/or export to the client.
Schema
module GameOfLife
extend Kumi::Schema
schema do
input do
array :rows do
array :col do
integer :alive # 0 or 1
end
end
end
let :a, input.rows.col.alive
# axis_offset: 0 = x, 1 = y
let :n, shift(a, -1, axis_offset: 1)
let :s, shift(a, 1, axis_offset: 1)
let :w, shift(a, -1)
let :e, shift(a, 1)
let :nw, shift(n, -1)
let :ne, shift(n, 1)
let :sw, shift(s, -1)
let :se, shift(s, 1)
let :neighbors, fn(:sum, [n, s, w, e, nw, ne, sw, se])
# Conway rules
let :alive, a > 0
let :n3_alive, neighbors == 3
let :n2_alive, neighbors == 2
let :keep_alive, n2_alive & alive
let :next_alive, n3_alive | keep_alive
value :next_state, select(next_alive, 1, 0)
end
end
Optimized LIR (lowered IR)
# ...
(Declaration next_state
%t285 = load_input "rows" :: array
%t1539 = Length %t285 :: integer
%t1540 = const -1 :: integer
%t1542 = const 0 :: integer
%t1546 = const 1 :: integer
%t1334 = const 3 :: integer
%t1339 = const 2 :: integer
%t1547 = call core.sub(%t1539, %t1546) :: integer
loop rows id=L31 in %t285 as el=%rows_el_286, idx=%rows_i_287
%t1541 = call core.sub(%rows_i_287, %t1540) :: integer
%t1561 = call core.sub(%rows_i_287, %t1546) :: integer
%t1580 = call core.mod(%rows_i_287, %t1539) :: integer
# ...
Generated Ruby (excerpt)
# Autogenerated by Kumi Codegen
module Kumi::Compiled::KUMI_bd17a3ebee1bec4e58b72118d43e8c1c93bf773f257fc93d9c32a783d212ea4f
def self.from(input_data = nil)
instance = Object.new
instance.extend(self)
instance.instance_variable_set(:@input, input_data)
instance
end
def self.__kumi_executable__
instance = Object.new
instance.extend(self)
instance
end
def update(input_data)
@input = @input.merge(input_data)
self
end
def [](name)
case name
when :next_state then _next_state
else raise KeyError, "Unknown declaration"
end
end
def _next_state(input = @input)
out = []
t285 = input["rows"] || input[:rows]
t1539 = t285.length
t1540 = -1
t1542 = 0
t1546 = 1
t1334 = 3
t1339 = 2
t1547 = t1539 - t1546
t285.each_with_index do |rows_el_286, rows_i_287|
out_1 = []
t1541 = rows_i_287 - t1540
t1561 = rows_i_287 - t1546
t1580 = rows_i_287 % t1539
t1543 = t1541 >= t1542
t1544 = t1541 < t1539
t1549 = [[ t1541, t1542 ].max, t1547 ].min
t1563 = t1561 >= t1542
# ...
Generated JavaScript (excerpt)
export class KumiCompiledModule {
_next_state(input) {
let out = [];
let t285 = input["rows"];
let t1539 = t285.length
const t1540 = -1;
const t1542 = 0;
const t1546 = 1;
const t1334 = 3;
const t1339 = 2;
let t1547 = t1539 - t1546;
t285.forEach((rows_el_286, rows_i_287) => {
let out_1 = [];
let t1541 = rows_i_287 - t1540;
let t1561 = rows_i_287 - t1546;
let t1580 = ((rows_i_287 % t1539) + t1539) % t1539;
let t1543 = t1541 >= t1542;
let t1544 = t1541 < t1539;
let t1549 = Math.min(Math.max(t1541, t1542), t1547);
let t1563 = t1561 >= t1542;
let t1564 = t1561 < t1539;
let t1569 = Math.min(Math.max(t1561, t1542), t1547);
let t1581 = t1580 + t1539;
let t1545 = t1543 && t1544;
let t1550 = t285[t1549]
// ...
gem install kumi
Requires Ruby 3.1+. No external dependencies.
MIT License. See LICENSE.