main do
IO.printLine("Hello, world!")
end Hello, world!
A tour of Kex through short, self-contained snippets — from
main to
type-directed make blocks.
Each one is drawn from the language repo.
main do
IO.printLine("Hello, world!")
end Hello, world! Might be your first introduction to functional pattern matching...
examples/fact.kexfactorial : Integer -> Integer
let factorial(1) = 1
let factorial(n) = n * factorial(n - 1)
main do
IO.printLine(factorial(4))
end 24 make [X] do
head :> X?
let head(@[]) = None
let head(@[x | _]) = Just(x)
rest :> This
let rest(@[]) = []
let rest(@[_ | xs]) = xs
end
main do
let list = [1, 2, 3]
IO.printLine(list.head) # same as: head(list)
IO.printLine(head([])) # same as: [].head
IO.printLine(list.rest)
end Just(1)
None
[2, 3]
Use the tilde `~` for currying a function. `_` marks an open slot (only required if it's not the next one).
README.mdlet add(a, b) = a + b
let multiply(a, b) = a * b
let inc = ~add(1)
let double = ~multiply(2)
main do
let multipled = [1, 2, 3].map(~multiply(10))
IO.printLine(multipled)
let summed = (1..100).reduce(0, ~(+))
IO.printLine(summed)
end [10, 20, 30]
5050 `!` rebinds a `var` to the method’s updated value. Frozen `let` bindings refuse it.
examples/mutating.kexmain do
var list = [1, 2, 3, 4, 5]
list.push!(6) # list = [1, 2, 3, 4, 5, 6]
list.filter!(&.even?) # list = [2, 4, 6]
list.map! { |x| x * 10 } # list = [20, 40, 60]
IO.printLine(list.to(String))
end [20, 40, 60] Define a record, attach behavior with `make`, overload `+` and `*`, and chain calls.
examples/vectors_advanced.kexrecord 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) Declare a contract, give it a default, implement it for each type, override where you want to.
examples/traits.kextrait 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 Branch on a tuple of remainders with guards and wildcards — readable from top to bottom.
examples/fizzbuzz_pattern_matching.kexlet 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
... Model fallible flows with `Result`, propagate failures with `?`, and pattern-match on what comes back.
examples/error_handling.kextype ParseError = InvalidFormat(String) | Overflow | EmptyInput
let parsePort(s: String) -> Result<Int, ParseError> do
return Error(EmptyInput) if s.empty?
match Integer.parse(s) do
Ok(n) -> do
return Error(Overflow) if n > 65535
return Ok(n)
end
Error(_) -> Error(InvalidFormat(s))
end
end
main do
match parsePort("8080") do
Ok(port) -> IO.printLine("listening on ${port}")
Error(why) -> IO.printLine("bad port: ${why}")
end
end listening on 8080 Pure code can’t call foul code. The compiler rejects it before the program ever runs.
README.md# 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
# ... Block args make library code read like language keywords — routing included.
README.mdlet app = Http.routes do
get "/" do |req|
Response.ok("Welcome")
end
get "/users/:id" do |req|
match UserService.find(req.params.id) do
Just(user) -> Response.json(user)
None -> Response.notFound("user not found")
end
end
post "/users" do |req|
let user = UserService.create(req.body)
return Response.created(user) if user.ok?
return Response.error("error while creating user")
end
end