Docs / Functions

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