Functions
Functions are the backbone of kex. They can be single expressions or
do … end blocks, and they compose through Uniform Function Call Syntax.
Signatures
fns.kex
let double(n: Integer) = n * 2
let factorial(n: Integer) -> Integer do
return 1 if n == 0
return n * factorial(n - 1)
end
# Haskell-style type declarations:
factorial : Integer -> Integer
# multiple clauses pattern-match arguments
let factorial(0) = 1
let factorial(n: Int) = n * factorial(n - 1) Uniform Function Call Syntax
value.f(arg) is equivalent to f(value, arg). The
receiver type decides which overload resolves. This is what makes fluent
pipelines possible without methods living on the data.
ufcs.kex
requests
.filter { |req| req.path.startsWith?("/admin") }
.reject(&.authenticated?)
.map { |req| SecurityEvent.from(req) }
.take(20)
# exactly equivalent:
map(requests, &normalize) Lambdas and block arguments
lambdas.kex
let inc = { |x| x + 1 }
let add = { |a, b| a + b }
[1, 2, 3].map(&inc) # [2, 3, 4]
[1, 2, 3].map(&.to(String)) # method reference via &. Currying and partial application
~func(args) builds a partial application. Bound arguments fill left
to right; _ marks an explicit open slot. Once enough arguments arrive
to fully saturate, the function runs immediately.
curry.kex
let add(a, b) = a + b
let multiply(a, b) = a * b
let inc = ~add(1) # {|b| add(1, b)}
let double = ~multiply(2) # {|b| multiply(2, b)}
[1, 2, 3].map(~multiply(10)) # [10, 20, 30]
(1..100).reduce(0, ~(+)) # 5050
let sub5 = ~(-)(_, 5) # {|a| a - 5}
sub5(20) # 15 Type-directed make
Behavior attaches to a type through make. Inside,
@field is shorthand for this.field, and operators
overload per receiver type.
make.kex
make Vector2D do
let +(other: Vector2D) -> Vector2D do
return Vector2D { x: @x + other.x, y: @y + other.y }
end
let to(String) -> String do
return "(${@x}, ${@y})"
end
end