Values and Expressions
This page covers the building blocks: numbers, strings and booleans, how to combine them, and how to name the results.
module Values
functions
main() {
let count = 1_000_000
let price = 19.99
let name = "Ann"
let ready = true
Log.info("#{name} has #{count} points")
Log.info("Two of them cost #{price * 2.0}")
Log.info("Ready? #{ready}")
Log.info("7 / 2 is #{7 / 2}, and 7 % 2 is #{7 % 2}")
Log.info("Half of 7 is #{Int.to_float(7) / 2.0}")
Log.info("Hello World" |> String.lowercase |> String.replace(" ", "-"))
}
exports
main
$ polar run values.px
Ann has 1000000 points
Two of them cost 39.98
Ready? true
7 / 2 is 3, and 7 % 2 is 1
Half of 7 is 3.5
hello-world
The basic types
| Type | Examples |
|---|---|
Int |
0, 42, -7,
1_000_000
|
Float |
3.14, 2.0, 1.5e3
|
String |
"hello" |
Bool |
true, false |
{} |
The empty record: "no useful value", like void
|
Underscores in numbers are only there to make them easier to read.
Int and Float don't mix
7 / 2 is integer division, so it gives 3.
To get 3.5, both sides need to be Float.
Polar never converts between the two for you:
error[POLAR0501]: mismatched types
--> mix.px:5:9
|
5 | n / 2.0
| ^^^ expected `Int`, found `Float`
Convert explicitly with Int.to_float or
Float.to_int. It's a little more typing, but you never
get a surprise rounding.
Strings and interpolation
#{…} inside a string inserts the value of any
expression. It works with numbers and booleans too, not only
strings:
"#{name} has #{count} points"
Interpolation works for any type that knows how to show itself,
which is the Show trait. You'll see how to give your
own types Show in
Traits.
Note: There's no + for
strings. Use interpolation, or String.concat(a, b).
Operators
| Operators | Meaning |
|---|---|
+ - * / % |
Arithmetic on Int or on Float
|
== != |
Equality, for any type with Eq |
< <= > >= |
Comparison on numbers and strings |
&& || ! |
And, or, not |
& | ^ ~ |
Bitwise and, or, xor and not on Int |
|> |
The pipe |
The pipe
x |> f(y) means f(x, y): the left side
becomes the first argument. It turns nested calls inside out, so you
can read them top to bottom:
String.replace(String.lowercase("Hello World"), " ", "-")
"Hello World" |> String.lowercase |> String.replace(" ", "-")
Both lines give "hello-world".
Naming values with let
let gives a value a name for the rest of the block.
Polar works out the type, so you don't have to write it. You can if
you want to: let count: Int = 0.
Names can't be reassigned. Instead of changing a value, make a new one with a new name. For the rare cases where you really need something to change, there's mutable state.
Blocks have values
A block is a sequence of lines in braces, and its value is the value
of its last line. That's why functions have no return:
area(width: Int, height: Int) -> Int {
let w = width + 2
let h = height + 2
w * h
}
if is an expression
if gives back a value, like the ternary operator in
JavaScript:
let mark = if done { "x" } else { " " }
Because it always has to produce a value, every
if needs an else:
error[POLAR0214]: `if` without `else`
--> mix.px:9:5
|
9 | if n > 0 { "positive" }
| ^^^^^^^^^^^^^^^^^^^^^^^
|
= help: `if` is an expression, so it needs a value on both branches: add `else { … }`
Chain conditions with else if:
if points >= 90 { "A" } else if points >= 75 { "B" } else { "C" }
Next, let's package expressions up into functions.