Programming Language
Pragmatically Functional for BEAM and More!
Type like Haskell, code like Ruby, scale like Erlang.
Just without the nulls and nils.
v0.1-prealpha · MIT licensed · C++20
record Vector2D do
x : Float
y : Float
end
make Vector2D do
let +(other: This) -> This do
return Vector2D { x: @x + other.x, y: @y + other.y }
end
let *(factor: Float) -> This do
return Vector2D { x: @x * factor, y: @y * factor }
end
let to(String) -> String do
return "(${@x}, ${@y})"
end
end
main do
let position = Vector2D { x: 3.0, y: 4.0 }
let velocity = Vector2D { x: 1.0, y: -0.5 }
let next = position + velocity * 2.0
IO.printLine("next position: ${next.to(String)}")
end next position: (5.0, 3.0) Why Kex?
Balance is the key. The best baked together. OOP style tidiness, Haskell-style typing, Ruby-like syntax and DSL-ability.
Universal Dots
Compose plain functions into fluent chains.
Uniform Function Call Syntax means v.f(arg) is always f(v, arg). Plain functions chain like methods, no monkey-patching required.
Purity
Side effects stay visible in the type everywhere.
Functions are pure unless marked foul, and pure code cannot call foul code. No more funny business (unless fun is mandatory).
Types
Express intent through the types, let it rule
Model data directly with product records and type unions. Get the expressivenes of Haskell typing without the hassle. Make the compiler work for you, not against you.
Traits
Contracts with defaults, behavior without classes.
Declare required methods and ship default implementations. make X implement: Trait pulls them in and you override individual methods where you need.
Pattern matching
Multi-clause functions, match
expressions, destructuring, and guards. Branching reads top to bottom, and
the compiler checks for exhaustiveness.
let fizzBuzz(n: Integer) -> String do
match (n.modulo(3), n.modulo(5)) do
(0, 0) -> "FizzBuzz"
(0, _) -> "Fizz"
(_, 0) -> "Buzz"
(_, _) -> n.to(String)
end
end
main do
(1..100).map(&.fizzBuzz).each { |s| IO.printLine(s) }
end 1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
... Purity, and effects you can see
Functions are pure unless marked foul, and pure code can’t call foul code. main
is the effect boundary; ?
propagates failures without try/catch.
# Pure function, no side effects, can be called from anywhere
let wordCountFrom(lines: String[]) -> Integer do
let words = lines.map do |line|
line.split(" ").count { |w| !w.empty? } # words per line
end
words.sum
end
# A foul, impure function with side-effect.
# Must be called from other foul functions.
foul wordCount(path: String) -> [Integer] do
return if !File.exists?(path)
let file_lines = File.lines(path).or([])
let words = wordCountFrom(lines: file_lines)
let lines = file_lines.count
let bytes = File.size(path).or(0)
[lines, words, bytes]
end
# ... Traits and make blocks
Collect logically similar functions into one place. Use them just like
an object, but better! Define common behaviour with traits, implement
them under
make blocks. Mimic inheritance just like in OOP without the mess.
trait Shape do
area :> Float
perimeter :> Float
let describe = "area=${this.area}" # default implementation
end
record Circle do
radius: Float
end
make Circle, implement: Shape do
let area = Math.PI * @radius * @radius
let perimeter = 2.0 * Math.PI * @radius
end
main do
let c = Circle { radius: 5.0 }
IO.printLine(c.describe)
end area=78.53981633974483 Build it from source
The compiler is written in C++20 with minimal external runtime
dependencies. Requires CMake 3.20+ and
a C++20 compiler.
git clone https://github.com/kexhq/kex.git && cd kex
make build
make run F=examples/fizzbuzz.kex
make repl