Functions
Functions live in the functions zone. This page shows
how to write them, pass them around and make them work for many
types.
Declaring a function
functions
add(a: Int, b: Int) -> Int {
a + b
}
A function has a name, typed parameters, an optional return type
after ->, and a body. The body's last expression is
the result.
Parameters always need types. The return type is optional: leave it out and Polar works it out from the body.
double(n: Int) {
n * 2
}
Note: Writing the return type does one more thing: it promises the function is pure, unless you list its effects too. We'll come back to this in Effects. For now, a good habit is to write return types on exported functions.
Calling functions
Call a function with parentheses. Functions from other modules are
reached through the module name, like List.map.
Functions can call themselves:
factorial(n: Int) -> Int {
if n <= 1 { 1 } else { n * factorial(n - 1) }
}
Anonymous functions
function(…) { … } makes a function without a name.
You'll mostly use these as arguments:
List.filter(tasks, function(t) { !t.done })
Their parameter types are usually inferred from where they're used,
so you rarely write them. You can also store one in a
let:
let shout = function(s) { String.uppercase(s) }
Functions are values
A named function can be passed anywhere a function is expected, without wrapping it:
[1, 2, 3] |> List.map(double) |> List.map(Int.to_string)
A function type is written function(Int) -> Int.
Here's a function that takes another function:
apply_twice(f: function(Int) -> Int, x: Int) -> Int {
f(f(x))
}
apply_twice(double, 5) is 20.
Generic functions
A lowercase name in a type, like a, is a
type variable. It stands for "any type", so the
function works for all of them. You don't declare type variables;
you just use them.
types
Pair<a> = { first: a, second: a }
functions
swap(p: Pair<a>) -> Pair<a> {
{ first: p.second, second: p.first }
}
swap works on a Pair<Int>, a
Pair<String> and so on. The type checker makes
sure both fields have the same type, and that the result does too.
Destructuring parameters
A parameter can be a pattern that pulls fields out of a record:
types
Entry = { key: String, value: Int }
functions
describe({ key: k, value: v }: Entry) -> String {
"#{k}=#{v}"
}
All together
module Funcs
uses
Std.List
types
Entry = { key: String, value: Int }
Pair<a> = { first: a, second: a }
functions
add(a: Int, b: Int) -> Int {
a + b
}
double(n: Int) {
n * 2
}
apply_twice(f: function(Int) -> Int, x: Int) -> Int {
f(f(x))
}
describe({ key: k, value: v }: Entry) -> String {
"#{k}=#{v}"
}
swap(p: Pair<a>) -> Pair<a> {
{ first: p.second, second: p.first }
}
factorial(n: Int) -> Int {
if n <= 1 { 1 } else { n * factorial(n - 1) }
}
main() {
let shout = function(s) { String.uppercase(s) }
let p: Pair<String> = { first: "left", second: "right" }
Log.info("#{add(2, 3)} #{double(21)} #{apply_twice(double, 5)}")
Log.info(shout("quiet"))
Log.info(List.join(List.map([1, 2, 3], double) |> List.map(Int.to_string), ", "))
Log.info(describe({ key: "a", value: 1 }))
Log.info(swap(p).first)
Log.info("10! = #{factorial(10)}")
}
exports
main
$ polar run funcs.px
5 42 20
QUIET
2, 4, 6
a=1
right
10! = 3628800
Next: records, the main way to group data in Polar.