polar

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.