Variables & Types
Bind an integer, a float, a string, and a boolean, then print one line that uses them all. Notice how each language denotes a binding - = in Erlang/Elixir/LFE-ish forms, let in Gleam, local in Lua - and which of them actually enforce immutability. On the BEAM, Erlang, Elixir, Gleam, and LFE all treat values as immutable (a name binds once, or rebinding just makes a new value); Lua via Luerl is the odd one out, where local variables are genuinely mutable. Watch too how each language spells its types: Erlang/LFE use lowercase atoms like true, Elixir has the same atoms with true/false, Gleam has a real Bool type, and Lua has actual true/false booleans plus a single number type.
-module(variables).
-export([demo/0]).
demo() ->
Count = 42, % integer
Ratio = 3.14, % float
Name = <<"Ada">>, % binary (the idiomatic string)
Active = true, % boolean atom
io:format("~s: count=~p ratio=~p active=~p~n",
[Name, Count, Ratio, Active]).
A capitalized name is a variable and = is single-assignment pattern matching: once Count is bound you cannot rebind it. Lowercase true is an atom, and <<"Ada">> is a binary, the modern, memory-efficient string representation.
count = 42 # integer
ratio = 3.14 # float
name = "Ada" # UTF-8 binary string
active = true # boolean (an atom)
IO.puts("#{name}: count=#{count} ratio=#{ratio} active=#{active}")
# => Ada: count=42 ratio=3.14 active=true
= is the match operator, not assignment; the values are immutable, but a name may be rebound to a fresh value. "Ada" is a UTF-8 binary, true is the atom :true, and #{...} does string interpolation.
import gleam/io
import gleam/int
import gleam/float
pub fn main() {
let count: Int = 42
let ratio: Float = 3.14
let name: String = "Ada"
let active: Bool = True
let active_str = case active {
True -> "true"
False -> "false"
}
io.println(
name
<> ": count=" <> int.to_string(count)
<> " ratio=" <> float.to_string(ratio)
<> " active=" <> active_str,
)
}
let binds an immutable name and the compiler infers or checks the static type (Int, Float, String, Bool). There are no implicit conversions, so each value is turned into a String explicitly - numbers via int.to_string/float.to_string, and the Bool via a case - before being joined with the <> concatenation operator.
(defmodule variables
(export (demo 0)))
(defun demo ()
(let ((count 42) ; integer
(ratio 3.14) ; float
(name #"Ada") ; binary string
(active 'true)) ; boolean atom
(io:format "~s: count=~p ratio=~p active=~p~n"
(list name count ratio active))))
(let ((name value) ...) body) introduces immutable bindings, scoped to the body - the same single-assignment semantics as Erlang, written as S-expressions. 'true is a quoted atom and #"Ada" is a binary.
local count = 42 -- number (integer subtype in 5.3+)
local ratio = 3.14 -- number (float)
local name = "Ada" -- string
local active = true -- boolean
print(string.format("%s: count=%d ratio=%.2f active=%s",
name, count, ratio, tostring(active)))
-- => Ada: count=42 ratio=3.14 active=true
local declares a lexically scoped variable that - unlike the other BEAM languages - is genuinely mutable: you can reassign count later. Lua has a single number type (with integer/float subtypes in 5.3+), real true/false booleans, and string.format for the summary.