← Code Compare

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.

Show: ErlangElixirGleamLFELuerl
Erlang
-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.

Elixir
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.

Gleam
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.

LFE
(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.

Luerl
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.