Functional Programming in hica - hica
Functional Programming in hica
Functional programming (FP) is a style where you build programs by composing functions rather than by writing sequences of instructions that change state. hica is designed around this style: immutable data, expressions everywhere, and functions as first-class values.
All examples are runnable. You don’t need a background in FP; if you can write a function and read a match expression, you have enough.
Expressions, not statements
In most languages, statements do things and expressions produce values. In hica, almost everything is an expression, including if, match, and blocks. That means you can use them anywhere a value is expected:
fun sign(x) => if x 0 { "negative" } else { "non-negative" }
The body of a {} block is the value of its last expression. No return keyword:
fun clamp(x, lo, hi) {<br>if x lo { lo }<br>else if x > hi { hi }<br>else { x }
This single rule carries you through most of FP: when everything has a value, everything can be composed.
Immutability by default
FP avoids shared mutable state. When you can’t change a value after creating it, your functions are easier to reason about and test.
hica uses let for immutable bindings:
let name = "Alicia"<br>let scores = [85, 92, 78]
You never mutate scores in place. Instead, you create new lists:
let updated = scores + [95] // new list: [85, 92, 78, 95]<br>let doubled = map(scores, (x) => x * 2)
When you need mutation (a counter, a loop variable), use var. It’s locally scoped and can’t leak out of the function:
var total = 0<br>for x in scores {<br>total = total + x
var is opt-in. Everything else stays immutable.
Pure functions
A pure function always returns the same output for the same input and has no side effects (no printing, no file I/O, no mutable state). Pure functions are easy to test and compose.
fun add(a, b) => a + b<br>fun square(x) => x * x<br>fun to_celsius(f: float) => (f - 32.0) * 5.0 / 9.0
In hica, functions are pure by default. The type system (inherited from Koka) tracks effects like I/O, so when a function does have a side effect, that’s visible in its type.
Pure functions are what you build with. Everything else is composition.
Functions as first-class values
In FP, functions are values like integers or strings. You can store them in variables, pass them to other functions, and return them from functions.
fun apply(f, x: int) => f(x)
fun main() {<br>let double = (x) => x * 2<br>let greet = (name) => "Hello, " + name
println(apply(double, 5)) // 10<br>println(greet("Olle")) // Hello, Olle
A function that takes or returns another function is called a higher-order function. apply above is one.
Closures
A closure is a function that captures variables from its surrounding scope:
fun make_adder(n) => (x) => x + n
fun main() {<br>let add5 = make_adder(5)<br>let add10 = make_adder(10)
println(add5(3)) // 8<br>println(add10(3)) // 13
make_adder returns a new function each time. That function closes over n, meaning it remembers the value of n from when it was created, even after make_adder has returned.
This pattern lets you stamp out specialised functions on demand:
fun make_multiplier(factor) => (x) => x * factor
fun main() {<br>let triple = make_multiplier(3)<br>let nums = [1..5]<br>println(map(nums, triple)) // [3, 6, 9, 12, 15]
map, filter, fold
These three functions cover most of what you need for working with lists.
map: transform every element
fun main() {<br>let nums = [1..5]<br>println(map(nums, (x) => x * x)) // [1, 4, 9, 16, 25]<br>println(map(nums, show)) // ["1", "2", "3", "4", "5"]
map takes a list and a function. It applies the function to each element and returns a new list of the same length.
filter: keep matching elements
fun main() {<br>let nums = [1..8]<br>let evens = filter(nums, (x) => x % 2 == 0)<br>println(evens) // [2, 4, 6, 8]
filter keeps only the elements for which the predicate returns true.
fold: reduce to a single value
fun main() {<br>let nums = [1..5]<br>let total = fold(nums, 0, (acc, x) => acc + x)<br>println(total) // 15
let product = fold(nums, 1, (acc, x) => acc * x)<br>println(product) // 120
fold accumulates a result. It starts with an initial value and applies the function to each element in turn: acc holds the running result, x is the current element.
You can implement many list operations with fold:
fun my_length(xs) => fold(xs, 0, (acc, _) => acc + 1)<br>fun my_max(xs) => fold(xs, 0, (acc, x) => if x > acc { x } else { acc })<br>fun my_reverse(xs) => fold(xs, [], (acc, x) => [x] + acc)
Composition with |>
The pipe operator |> feeds the result of one expression into the next function. It reads left to right, matching the order of operations:
fun main() {<br>let result = [1..10]<br>|> filter((x) => x % 2 == 0) // [2, 4, 6, 8, 10]<br>|> map((x) => x * x) // [4, 16, 36, 64, 100]<br>|> fold(0, (acc, x) => acc + x) // 220
println(result)
Without |> you’d write:
let result = fold(map(filter([1..10], (x) => x % 2 == 0), (x) => x * x), 0, (acc, x) => acc + x)
With |>, each...