gleam new my_project cd my_project gleam run //// This comment with four slashes is a module-level. //// This kind of comments are used to describe the whole module. import gleam/bool import gleam/io import gleam/int import gleam/float import gleam/list import gleam/option.{type Option, None, Some} import gleam/result import gleam/string import gleam/dict // A type's name always starts with a capital letter, contrasting to variables // and functions, which start with a lowercase letter. // When the pub keyword is used the type alias is public and can be referred to // by other modules. pub type UserId = Int pub fn main() { io.println("Hello from learnxinmyminutes.com!") // io.println("This statement got commented out by a two slashes comment.!") // Modules are the units in which all Gleam code gets organized. // In a module you will find a bunch of definitions of types, functions, etc. // that seem to belong together. // For example, the gleam/io module contains a variety of functions for // printing, like println. // All gleam code is in some module or other, whose name comes from the name // of the file it's in. // For example, gleam/io is in a file called io.gleam in a directory called // gleam. // Gleam has a robust static type system that helps you as you write and edit // code, catching mistakes and showing you where to make changes. // io.println(10) // If you uncomment the previous line you'll get a compile time error reported // as the io.println function only works with strings, not ints. // The compile will output an error that looks like this: // error: Type mismatch // ┌─ /home/contributor/learnxinmyminutes/src/learnxinmyminutes.gleam:21:14 // │ // 21 │ io.println(10) // │ ^^ // // Expected type: // // String // // Found type: // // Int // Working with numbers // When running on the Erlang virtual machine ints have no maximum and minimum // size. // When running on JavaScript runtimes ints are represented using JavaScript's // 64 bit floating point numbers. // We can use `echo` to debug print a value. // Int arithmetic echo 1 + 1 echo 5 - 1 echo 5 / 2 echo 3 * 3 echo 5 % 2 // Int comparisons using variables to prevent compiler static analysis warnings let two = 2 let one = 1 let same_one = 1 let same_two = 2 let _ = echo two > one let _ = echo two < one let _ = echo two >= one let _ = echo two <= one // Equality works for any type and is checked structurally, meaning that two // values are equal if they have the same structure rather than if they are at // the same memory location. let _ = echo one == same_one // True let _ = echo two != same_two // False // Standard library int functions echo int.min(142, 137) // 137 echo int.clamp(-80, min: 0, max: 100) // clamp(value, min, max) specifies value to fall in given range // 0 let _ = echo int.base_parse("10", 2) // parses binary string as base-2 integer. // Ok(2) : 10 in binary equals to 2 in integer // Binary, octal, and hex Int literals echo 0b00001111 echo 0o17 echo 0xF // Use underscores to enhance integer readability echo 1_000_000 // Gleam's numerical operators are not overloaded, so there are dedicated // operators for working with floats. // Float arithmetic echo 1.0 +. 1.5 echo 5.0 -. 1.5 echo 5.0 /. 2.5 echo 3.0 *. 3.5 // Float comparisons using variables to prevent compiler static analysis warnings let two_point_two = 2.2 let one_point_three = 1.3 let _ = echo two_point_two >. one_point_three let _ = echo two_point_two <. one_point_three let _ = echo two_point_two >=. one_point_three let _ = echo two_point_two <=. one_point_three // Floats are represented as 64-bit floating point numbers on both the Erlang // and JavaScript runtimes. // The floating point behaviour is native to their respective runtimes, so // their exact behaviour will be slightly different on the two runtimes. // Under the JavaScript runtime, exceeding the maximum (or minimum) // representable value for a floating point value will result in Infinity // (or -Infinity). Should you try to divide two infinities you will get NaN // as a result. // When running on the BEAM any overflow will raise an error. So there is no // NaN or Infinity float value in the Erlang runtime. // Division by zero is not an error and is defined to be zero echo 3.14 /. 0.0 // 0.0 // Standard library float functions echo float.max(2.0, 9.5) // 9.5 echo float.ceiling(5.4) // 6.0 // Underscores for floats are also supported echo 10_000.01 // Working with strings echo "⭐ Gleam ⭐ - 별" echo "this is a multi line string" echo "\u{1F600}" // Outputs a smiley 😀 // Double quote can be escaped io.println("\"X\" marks the spot") // String concatenation echo "One " <> "Two" // String functions echo string.reverse("1 2 3 4 5") echo "abc" <> "def" io.println(string.reverse("!desrever tog gnirts sihT")) // Outputs "This string got reversed!" // Several escape sequences are supported: // \" - double quote // \\ - backslash // \f - form feed // \n - newline // \r - carriage return // \t - tab // Bool operators // The || and && operators work by short-circuiting echo True && False // False echo True && True // True echo False || False // False echo False || True // True // Bool functions echo bool.to_string(True) // "True" // Assignments let x = "Original value" echo x // Assign `y` to the value of `x` let y = x echo y // Assign `x` to a new value let x = "New value" echo x // The `y` still refers to the original value echo y // In Gleam variable and function names are written in snake_case. let answer_to_the_universe = 42 echo answer_to_the_universe let and_everything = answer_to_the_universe // Now using a variable produces a warning // warning: Unused variable // ┌─ /home/contributor/learnxinmyminutes/src/learnxinmyminutes.gleam:199:7 // │ // 199 │ let and_everything = answer_to_the_universe // │ ^^^^^^^^^^^^^^ This variable is never used // Hint: You can ignore it with an underscore: `_and_everything`. // Type annotations let _name: String = "Gleam" let _is_cool: Bool = True let _version: Int = 1 // Useful for documentation purposes but they do not change how the compiler // type checks the code beyond making sure the annotation matches the type, // otherwise you get an error. // let _has_wrong_type_annotation: Int = True // error: Type mismatch // ┌─ /home/contributor/learnxinmyminutes/src/learnxinmyminutes.gleam:219:41 // │ // 219 │ let _has_wrong_type_annotation: Int = True // │ ^^^^ // // Expected type: // // Int // // Found type: // // Bool // Type aliases let one: UserId = 1 // Refer to the beginning of the file for the definition of the UserId type let two: Int = 2 // Aliases are just for creating more readable code and more precise // documentation. // Under the hood they are still values of the same type so operations // still work echo one + two // 3 // Blocks: scoping and value let radius = { let value = 100.0 value } // echo value // <- This will not compile because "value" is out of scope let area = 3.14159 *. radius *. radius echo area // Use blocks to group operations instead of parenthesis let n1 = { 3 + 2 } * 5 let n2 = 3 + { 2 * 5 } echo n1 != n2 // True // Lists // Nephews of Scrooge McDuck let nephews = ["Huey", "Dewey", "Louie"] echo nephews // ["Huey", "Dewey", "Louie"] // Immutably prepend so the original list is not changed echo ["Donald", ..nephews] // ["Donald", "Huey", "Dewey", "Louie"] // Some standard library functions for lists list.each(nephews, io.println) // Huey // Dewey // Louie echo list.drop(nephews, 2) // ["Louie"] more_examples() more_function_examples() generic_typing_examples() beloved_pipelines_demo() labels_in_function_calls() showcase_flow_control() more_on_recursion() more_on_pattern_matching() showcase_types() more_on_types() more_on_callbacks() showcase_externals() showcase_constants() showcase_dicts() showcase_bit_arrays() showcase_opaque_types() showcase_panic() } // The fn keyword is used to define new functions. fn multiply(a: Int, b: Int) -> Int { // No explicit return // The last expression gets returned a * b } // The double and multiply functions are defined without the pub keyword. // This makes them private functions, they can only be used within this module. // If another module attempted to use them it would result in a compiler error. fn double(a: Int) -> Int { multiply(a, 2) } // Only public functions are exported and can be called from outside the module. // Type annotations are optional for function arguments and return values // but are considered good practice for clarity and in order to encourage // intentional and thoughtful design. pub fn is_leap_year(year: Int) -> Bool { { year % 4 == 0 } && { { year % 100 != 0 } || { year % 400 == 0 } } } fn more_examples() { // Debug also returns a value so its output is the return value of // this function echo double(10) // 20 echo is_leap_year(2000) // True } // Gleam supports higher-order functions: // They can be assigned to variables, passed as arguments to other functions // or even be returned as values from blocks or other functions fn call_func_on_int(func: fn(Int) -> Int, value: Int) -> Int { func(value) } fn more_function_examples() -> Int { echo call_func_on_int(double, 2) // 4 let square = fn(x: Int) -> Int { x * x } echo square(3) // 9 // Calling an anonymous function immediately after defining it echo fn(x: Int) { x + 1 }(1) // Closure example let make_adder = fn(n: Int) -> fn(Int) -> Int { fn(argument: Int) -> Int { argument + n } } let adder_of_fives = make_adder(5) echo adder_of_fives(10) // 15 // Anonymous functions can be used interchangeably with named functions. echo call_func_on_int(fn(x: Int) -> Int { x + 100 }, 900) // 1000 // Let's create a function decorator let twice = fn(wrapped_func: fn(Int) -> Int) -> fn(Int) -> Int { fn(argument: Int) -> Int { wrapped_func(wrapped_func(argument)) } } let quadruple = twice(double) echo quadruple(1) let quadruple_2 = fn(a: Int) -> Int { multiply(4, a) } echo quadruple_2(2) // 8 // A function capture is a shorthand syntax for creating anonymous functions // that takes one argument and immediately calls another function with that // argument let quadruple_3 = multiply(4, _) echo quadruple_3(4) // 16 } // Generic functions are supported using type variables. fn generic_twice(func: fn(value) -> value, argument: value) -> value { func(func(argument)) } // In generic_twice value was the type variable. // In generic_twice_decorator the_type is the type variable. // As in any other variable you get to choose the name. fn generic_twice_decorator( func: fn(the_type) -> the_type, ) -> fn(the_type) -> the_type { fn(argument: the_type) -> the_type { func(func(argument)) } } fn generic_typing_examples() { let double_integers = fn(a: Int) -> Int { a * 2 } let double_floats = fn(a: Float) -> Float { a *. 2.0 } echo generic_twice(double_integers, 3) echo generic_twice(double_floats, 3.0) let quadruple_integers = generic_twice_decorator(double_integers) let quadruple_floats = generic_twice_decorator(double_floats) echo quadruple_integers(1) // 4 echo quadruple_floats(1.0) // 4.0 } // Gleam's pipe operator |> takes the result of the expression on its left // and passes it as an argument to the function on its right. fn beloved_pipelines_demo() { // Let's be honest: you want to use Gleam just for this cool operator, right? ["hello", "world"] |> list.intersperse(" ") |> list.append(["!"]) |> string.concat |> string.capitalise |> echo // Much cleaner than this right? echo string.capitalise( string.concat( list.append(list.intersperse(["hello", "world"], " "), ["!"]), ), ) // Solution to the first problem of Project Euler: // URL: https://projecteuler.net/problem=1 // Description: Find the sum of all the multiples of 3 and 5 below 1000. // Using int.range from gleam/int to iterate tail-recursively. int.range(from: 1, to: 999, with: 0, run: fn(acc, n) { case n % 3 == 0 || n % 5 == 0 { True -> acc + n False -> acc } }) |> int.to_string |> fn(sum_as_text: String) { "Solution to Project Euler's problem #1: " <> sum_as_text } |> echo // Solution to Project Euler's problem #1: 233168 } // Labels can be added before each argument fn call_func_on_int_with_labels( func passed_func: fn(Int) -> Int, value n: Int, ) -> Int { passed_func(n) } // The label and the argument can have the same name fn add_one(number number: Int) -> Int { number + 1 } fn add_two_integers(first n: Int, second m: Int) -> Int { n + m } fn labels_in_function_calls() -> Int { // Since we are labelling the arguments we can switch the order // if we want to echo call_func_on_int_with_labels(value: 8, func: double) echo add_one(number: 1) // 2 echo string.contains(does: "theme", contain: "the") // True // Unlabeled arguments must go first echo add_two_integers(2, second: 2) // 4 } fn showcase_flow_control() { // Use case if you want to use pattern-matching in order to // select which code to execute. // Gleam will make sure all possible values are covered // by performing exhaustiveness checks. // Otherwise you get compilation errors. let puppies = ["Bear", "Frisco", "Ranger"] let count = list.length(of: puppies) { "We have " <> int.to_string(count) <> " " <> // The underscore matches with any other value case count { 1 -> "puppy" _ -> "puppies" } } |> echo // Gleam allows patterns in case expressions to also assign variables. { "Puppy count: " <> case list.length(puppies) { 0 -> "None." 1 -> "Just one." other -> "As many as " <> int.to_string(other) <> " puppies." } } |> echo // Consider BEAM languages are functional in design and Gleam is no exception // so there are no if, for or while constructs available. // Use pattern-matching for conditionals let answer = 42 case answer == 42 { True -> { echo "This is the answer to the universe." } False -> { echo "This is the answer to something else." } } // Use recursion instead of looping from_one_to_ten(1) } // Recursive function fn from_one_to_ten(n: Int) { echo n case n { 10 -> Nil _ -> from_one_to_ten(n + 1) } } // In order to avoid memory exhaustion due to creating excessive // stack frames when calling functions recursively, Gleam supports // "tail call optimisation" which means that the compiler can reuse // the stack frame for the current function if a function call is // the last thing the function does. pub fn fib(x: Int) -> Int { // The public function calls the private tail recursive function fib_loop(x, 1) } fn fib_loop(x: Int, accumulator: Int) -> Int { case x { 1 -> accumulator // The last thing this function does is call itself // In the previous lesson the last thing it did was multiply two ints _ -> fib_loop(x - 1, accumulator + x) } } // Gleam supports pattern-matching the first element and the remainder // of a list with the [x, ..y] pattern inside a case expression. fn reverse_list(the_list: List(value)) -> List(value) { case the_list { [head, ..tail] -> list.flatten([reverse_list(tail), [head]]) [] -> [] } } fn more_on_recursion() { echo fib(10) // 55 echo reverse_list([1, 2, 3]) } fn more_on_pattern_matching() { // When pattern-matching on strings the <> operator match on strings // with a specific prefix and assigns the reminder to a variable let lucy = "Hello, Lucy" let _ = echo case lucy { "Hello, " <> name -> "Greetings for " <> name _ -> "Potentially no greetings" } // Alternative patterns are supported so the same clause is used // for multiple values let month = 2 let year = 2024 let number_of_days = case month { 2 -> case is_leap_year(year) { False -> 28 True -> 29 } 4 | 6 | 9 | 11 -> 30 1 | 3 | 5 | 7 | 8 | 10 | 12 -> 31 _ -> 0 } echo "Number of days: " <> int.to_string(number_of_days) // 29 // Guards in pattern-matching: // When using the if keyword an expression must evaluate to True // for the pattern to match. let list_starts_with = fn(the_list: List(value), the_value: value) -> Bool { case the_list { [head, ..] if head == the_value -> True _ -> False } } echo list_starts_with([10, 20, 30], 10) // True } pub type Gender { Male Female Other } // Records: // - Support variants // - Each variant is similar to a struct with fields pub type Shape { Rectangle(base: Float, height: Float) Triangle(base: Float, height: Float) } // Records with one variant resemble structs pub type Point { Point(x: Float, y: Float) } fn showcase_types() { // Tuples: // - Can mix together elements of different types // - Their type is implicit e.g. #{1, "Hello"} is of type #{Int, String} // - Their elements can be accessed by numeric indexes let tuple_01 = #(1, "Ferris", "rustacean", True) let tuple_02 = #(1, "Lucy", "starfish", True) echo tuple_01 echo tuple_01.0 // 1 echo tuple_02.1 // Lucy let #(_, name, species, _) = tuple_01 echo name <> " the " <> species // Pattern-matching with tuples including variable assignment let _ = print_mascot_status(tuple_02) // Using a custom type with pattern-matching let gender = Other let _ = echo gender_to_string(gender) // Using records let rectangle_1 = Rectangle(base: 10.0, height: 20.0) echo rectangle_1.height // 10.3 let point_1 = Point(x: 3.2, y: 4.3) echo point_1 // Updating a record let point_2 = Point(..point_1, y: 5.7) echo point_2 // In Gleam, values are not nullable. // Nil is the only value of its type. let some_var = Nil let result = io.println("Hello!") echo some_var == result // True } fn print_mascot_status(mascot: #(Int, String, String, Bool)) { case mascot { #(_, name, _, True) -> echo name <> " is a mascot." #(_, name, _, False) -> echo name <> " is not a mascot." } } fn gender_to_string(gender: Gender) -> String { case gender { Male -> "Boy" Female -> "Girl" Other -> "Undetermined" } } pub type Mineral { Gold Silver Copper } // Generic custom types with contained types as parameters pub type Purity(inner_type) { Pure(inner_type) Impure(inner_type) } pub type Beverage { Water Juice } // Existing custom types from the gleam/option and gleam/result modules // facilitate working with nullable values and handling potential errors pub type Person { Person(name: String, nickname: Option(String)) } pub type DiceError { DiceValueOutOfRange } fn checked_dice_value(value: Int) -> Result(Int, DiceError) { case value { 1 | 2 | 3 | 4 | 5 | 6 -> Ok(value) _ -> Error(DiceValueOutOfRange) } } fn double_dice_value(value: Int) -> Result(Int, DiceError) { case value { 1 | 2 | 3 -> Ok(value * 2) _ -> Error(DiceValueOutOfRange) } } fn more_on_types() { let mineral_sample_01: Purity(Mineral) = Pure(Gold) let mineral_sample_02 = Impure(Silver) echo mineral_sample_01 echo mineral_sample_02 // A glass can be empty or not let glass_01: Option(Beverage) = Some(Water) let glass_02 = None echo glass_01 echo glass_02 // A person can have a nickname or not let person_01 = Person(name: "John", nickname: Some("The Ripper")) let person_02 = Person(name: "Martin", nickname: None) echo person_01 echo person_02 // Working with functions that return values of type Result let dice_01 = 5 case checked_dice_value(dice_01) { Ok(checked_value) -> echo "The value of " <> int.to_string(checked_value) <> " is OK." Error(DiceValueOutOfRange) -> echo "The value of the dice is out of range" } // Let's attempt to double the value if the resulting value is still // a number in any of the sides of the dice. // Otherwise, let's put the max value. 2 |> checked_dice_value |> result.try(double_dice_value) |> result.unwrap(or: 6) |> echo } pub fn throw_dice_as_result() { Ok(int.random(6) + 1) } pub fn sum_dice_values(a: Int, b: Int) { Ok(a + b) } // Betting on first-class functions and pattern-matching // can easily lead to tons of indentation fn roll_two_dices_without_use() { result.try(throw_dice_as_result(), fn(first_dice) { result.try(throw_dice_as_result(), fn(second_dice) { sum_dice_values(first_dice, second_dice) }) }) } // The use expression still lets us write code that uses callbacks // but cleans up excessive indentation: // - A call to higher order function go the right side of the <- operator // - The argument names for the callback function go on the left hand side of // the <- operator // - All the remaining code in the enclosing {} block becomes the body of the // callback function. fn roll_two_dices_with_use() { use first_dice <- result.try(throw_dice_as_result()) use second_dice <- result.try(throw_dice_as_result()) sum_dice_values(first_dice, second_dice) } fn more_on_callbacks() { let _ = echo roll_two_dices_without_use() let _ = echo roll_two_dices_with_use() Nil } // Since v1.14.0, the @external annotation can also be used on external types to // point them to Erlang or TypeScript types. pub type DateTime // External functions must annotate a return type. // This function is only defined on the Erlang target; compiling to JavaScript // would require a fallback body or a @external(javascript, ...) annotation. @external(erlang, "calendar", "local_time") pub fn now() -> DateTime { todo } fn showcase_externals() { let _ = echo now() // #(#(2024, 4, 6), #(14, 4, 16)) Nil } // Module-level constants (supported since v1.0.0, with list prepending since v1.16.0) pub const prime_numbers = [2, 3, 5] pub const more_primes = [7, 11, ..prime_numbers] fn showcase_constants() { let _ = echo prime_numbers let _ = echo more_primes // Since v1.17.0, you can also use `todo` inside constant declarations: // pub const upcoming_constant = todo Nil } fn showcase_dicts() { // Dictionaries are key-value collections (stdlib v0.33.0) let scores = dict.new() |> dict.insert("Alice", 10) |> dict.insert("Bob", 15) let _ = echo dict.get(scores, "Alice") // Ok(10) let _ = echo dict.get(scores, "Charlie") // Error(Nil) Nil } fn showcase_bit_arrays() { // Bit arrays represent sequences of binary data (stdlib v0.32.0) let binary_data = <<0x41, 0x42, 0x43>> // "ABC" in ASCII let _ = echo binary_data // In Gleam, pattern matching with `let` must be exhaustive (it must cover all // possible values). If a pattern might fail to match at runtime, we must use // `let assert` instead of `let`. If the match fails, the program panics: let assert <> = binary_data let _ = echo first_byte // 65 let _ = echo rest // <<66, 67>> Nil } // Opaque types are types whose constructors are private to the defining module, // enabling encapsulation and hiding implementation details. pub opaque type EncryptedData { EncryptedData(value: String) } pub fn encrypt(data: String) -> EncryptedData { EncryptedData(data <> "_encrypted") } fn showcase_opaque_types() { let secret = encrypt("my_password") // We can pass the secret around, but we cannot pattern match on EncryptedData // or access its .value field from outside this module. let _ = echo secret Nil } fn showcase_panic() { // We can deliberately abort execution by using the panic keyword // in order to make our program crash immediately let three = 3 let two = 2 case three == two { True -> panic as "The equality operator is broken!" False -> "Equality operator works for integers" } // Calling a function that uses the todo keyword also crashes // homework() } pub fn homework() { todo as "This function is not implemented yet" }