Getting Started, Types, and Values
- "Let" bindings are immutable: once you bind a name to a value, that binding cannot change.
- Custom types describe the shape of your data and let the compiler verify that you have handled every case.
- Records group named fields into a single value, like a Python dataclass.
- Tuples hold a fixed number of values of different types.
Hello, World
- Every Gleam project starts with
gleam new- Creates a project directory with a source file, a test file, and a
gleam.toml
- Creates a project directory with a source file, a test file, and a
name = "basics"
version = "1.0.0"
target = "erlang"
[dependencies]
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
[dev-dependencies]
gleeunit = ">= 1.0.0 and < 2.0.0"
- Use
gleam runto compile and execute the default entry point
import gleam/io
pub fn main() {
let name = "Gleam"
io.println("Hello from " <> name <> "!")
}
Hello from Gleam!
import gleam/iomakes theiomodule availablepub fn main()is the entry pointpub(public) makes it visible outside the modulefnis short for "function"
let name = "Gleam"creates an immutable binding- All bindings are final: reassigning a name is a compile error
<>concatenates strings- Gleam does not overload
+to add strings
- Gleam does not overload
io.printlnprints a line with a newlineio.debugprints any value using its debug representation
Arithmetic
pub fn main() {
let sum = 2 + 2
io.println(string.inspect(sum))
let product = 6 * 7
io.println(string.inspect(product))
let float_div = int.to_float(10) /. int.to_float(3)
io.println(string.inspect(float_div))
}
4
42
3.3333333333333335
2 + 2is integer arithmetic- The result is
Int, notFloat
- The result is
- Gleam separates integer and floating-point operators:
+,-,*,/forInt;+.,-.,*.,/.forFloatint.to_floatconverts before you can mix them
- Yes, this seems pedantic, but it's consistent
Built-in Types
- Gleam's primitive types map directly to Python equivalents
- But the type system prevents you from mixing them
Int: arbitrary-precision integer, like Python'sintFloat: 64-bit floating-point, like Python'sfloatString: UTF-8 text, like Python'sstrBool:TrueorFalse, like Python'sboolNil: the unit value, similar to Python'sNonebut much rarer
Custom Types
- Most important building block in Gleam is the custom type
- packages one or more named variants into a single type
pub type Color {
Red
Green
Blue
}
type Shape {
Circle(Float)
Rectangle(Float, Float)
}
type Color { Red Green Blue }defines a type with three variants- Like a Python
Enum, but integrated into the type system Red,Green,Blueare constructors, not strings
- Like a Python
type Shape { Circle(Float) Rectangle(Float, Float) }defines variants that carry dataCircle(Float)is like a Python dataclass with one float fieldRectangle(Float, Float)has two float fields- The compiler knows exactly which fields each variant carries
- There is no inheritance: custom types are closed and complete
- Now a function that uses the type:
fn area(shape: Shape) -> Float {
case shape {
Circle(r) -> 3.14159 *. r *. r
Rectangle(w, h) -> w *. h
}
}
red is Red
circle is Circle(3.0)
rectangle is Rectangle(4.0, 5.0)
circle area is 28.274309999999996
rectangle area is 20.0
case shape { ... }matches on the value ofshape- Each arm extracts fields, e.g.,
Circle(r)binds the radius tor - The compiler verifies that every variant is handled
- Remove the
Rectanglearm and the compiler reports an error - Python's
if isinstancechains have no such guarantee
- Remove the
- The last expression in each arm is the arm's value: no
returnneeded 3.14159 *. r *. ruses*.becauseris aFloat- Yes, we will get π from a library the next time
Records with Named Fields
- When a variant carries several fields, naming them makes the code clearer
type Flavor {
Chocolate
Vanilla
Strawberry
}
type Container {
Cone
Cup
}
type IceCream {
IceCream(flavor: Flavor, container: Container)
}
IceCream(flavor: Flavor, container: Container)defines a variant with named fieldsflavor: Flavormeans the field namedflavorholds aFlavorvalue- Access fields with dot notation:
item.flavor,item.container
FlavorandContainerare separate custom types defined aboveIceCream- This is like a Python dataclass whose field types are enums
- Creating a value with named fields:
IceCream(flavor: Chocolate, container: Cone)- You can also write
IceCream(Chocolate, Cone)when the order is unambiguous
- You can also write
fn display(item: IceCream) -> String {
flavor_string(item.flavor) <> " in a " <> container_string(item.container)
}
chocolate cone is Chocolate in a cone
vanilla cone is Vanilla in a cup
strawberry cone is Strawberry in a cone
item.flavoranditem.containeraccess named fields using dot notationflavor_stringandcontainer_stringare private helper functions (nopub)- The
<>operator chains string concatenation left to right
Tuples
- A tuple groups a fixed number of values of potentially different types
- Gleam writes tuples with a
#prefix:#(1, "hello", True)
let t = #(1, "hello", True)
io.println(string.inspect(t))
#(1, "hello", True)creates a tuple of type#(Int, String, Bool)- The type is inferred: you do not need to write it explicitly
- As in Python, tuples are useful for returning multiple values from a function
let #(a, b, c) = t
io.println("a from tuple is " <> string.inspect(a))
io.println("b from tuple is " <> string.inspect(b))
io.println("c from tuple is " <> string.inspect(c))
#(1, "hello", True)
a from tuple is 1
b from tuple is "hello"
c from tuple is True
let #(a, b, c) = tdestructs the tuple into three bindingsais1,bis"hello",cisTrue- Like Python's
a, b, c = tbut the#makes clear a tuple is expected
- Gleam does not have indexed access like
t[0]- Destructuring is the idiomatic way to extract tuple fields
Type Inference
- Gleam almost never requires you to write type annotations
- The compiler infers the type of every expression from context
let x = 42infersx: Intlet s = "hello"inferss: Stringfn area(shape: Shape) -> Floatcarries explicit annotations, but they are optional for local bindings
- You can always add annotations for documentation:
- The compiler checks annotations against the inferred type and reports a mismatch as an error
let x: Int = 42
let name: String = "Gleam"
Check Understanding
What is the difference between + in Python and Gleam?
Python uses a single + for both integers and floats and silently coerces between them,
and uses + to concatenate strings as well.
Gleam requires you to be explicit:
+ for integers, +. for floats, and <> for strings.
This makes mixed-type arithmetic a compile error rather than a runtime surprise.
The practical consequence is that you must call int.to_float before combining an integer with a float.
What happens if you forget to handle a variant in a case expression?
The compiler produces an error before the program runs.
Every case expression in Gleam must be exhaustive,
i.e., it must handle every variant of the type being matched.
This is one of the most practical benefits of the type system:
you cannot accidentally omit a case and discover the gap in production.
Python has no equivalent guarantee:
a missing elif or isinstance branch fails silently until the code runs.
Exercises
Hello with arithmetic (5 minutes)
Create a new Gleam project.
In main,
bind your name to a variable and print a greeting that includes it.
Also print the result of 10 * 10 - 1 and the result of dividing 22.0 by 7.0.
Echo
Look up what echo does in Gleam,
then convert one of the examples in this lesson to use it instead of io.println.
How much do you miss Python's print function?
Ice cream order (10 minutes)
Using the IceCream type from icecream_demo.gleam, write
describe(item: IceCream) -> String that returns a sentence like
"Chocolate ice cream in a cone".
Create three orders and print their descriptions.
Shape functions (10 minutes)
Write perimeter(shape: Shape) -> Float that returns 2 * pi * r for a circle
and 2 * (w + h) for a rectangle.
Also write describe(shape: Shape) -> String
that returns a human-readable description like "circle with radius 3.0".
Tuple swap (5 minutes)
Write swap(pair: #(a, b)) -> #(b, a) that returns a new tuple with the two elements reversed.
Gleam will infer the generic types a and b automatically.
Test it with #(1, "one") and #(True, 42).
Public vs. private types (5 minutes)
In src/types.gleam, the Color type is declared pub type Color.
Remove the pub keyword so it reads type Color,
then run gleam run --module types.
What warning does the compiler produce, and why?