diff options
| author | Antonio Ognio <30294002+aognio@users.noreply.github.com> | 2024-05-13 01:11:06 -0500 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-05-13 00:11:06 -0600 | 
| commit | 2c2e8650d9b7b3caba65fa9e3ca6bc55ea08c317 (patch) | |
| tree | e46e55c742861cfa4e340d0637fae4dd3981d6a0 | |
| parent | 519c9566fb4a14836ac7cd26820e10dc97925f10 (diff) | |
[gleam/en-us] Add initial version of Gleam (#4886)
| -rw-r--r-- | gleam.html.markdown | 888 | 
1 files changed, 888 insertions, 0 deletions
| diff --git a/gleam.html.markdown b/gleam.html.markdown new file mode 100644 index 00000000..df6cddfd --- /dev/null +++ b/gleam.html.markdown @@ -0,0 +1,888 @@ +--- +name: Gleam +category: language +language: Gleam +contributors: +    - ["Antonio Ognio", "https://github.com/aognio/"] +filename: learngleam.gleam +--- + +Gleam is a new language for Erlang's BEAM virtual machine that relies on the +power of a robust type system, the expressiveness of functional programming, +and the highly concurrent fault-tolerant Erlang runtime using familiar and +modern syntax inspired by languages like OCaml, Rust and Elixir. + +Being a pretty modern development, Gleam comes with a compiler, a build tool, +a code formatter, several editor integrations, and package manager. + +Being part of the larger BEAM ecosystem, the programs created with Gleam can +also make use of thousands of published packages written in Erlang or Elixir. + +The design of the language is very concise so it feature no null values, +no exceptions, clear error messages, and a practical type system. + +JavaScript is additionally supported as a compile target, so you can run Gleam +code in browser or any other JS-enabled runtime. When using this feature, +TypeScript definitions get created, so you can interact with your Gleam code +confidently, even from the outside. + +```gleam +//// 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/iterator +import gleam/option.{type Option, None, Some} +import gleam/result +import gleam/string +import gleam/string as text + +// 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 full 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. + +  // Int arithmetic +  io.debug(1 + 1) +  io.debug(5 - 1) +  io.debug(5 / 2) +  io.debug(3 * 3) +  io.debug(5 % 2) + +  // Int comparisons +  io.debug(2 > 1) +  io.debug(2 < 1) +  io.debug(2 >= 1) +  io.debug(2 <= 1) + +  // 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. +  io.debug(1 == 1) +  // True +  io.debug(2 != 2) +  // False + +  // Standard library int functions +  io.debug(int.min(142, 137)) +  // 137 +  io.debug(int.clamp(-80, min: 0, max: 100)) +  // 0 +  io.debug(int.base_parse("10", 2)) +  // Ok(2) + +  // Binary, octal, and hex Int literals +  io.debug(0b00001111) +  io.debug(0o17) +  io.debug(0xF) + +  // Use underscores to enhance integer readibility +  io.debug(1_000_000) + +  // Gleam's numerical operators are not overloaded, so there are dedicated +  // operators for working with floats. + +  // Float arithmetic +  io.debug(1.0 +. 1.5) +  io.debug(5.0 -. 1.5) +  io.debug(5.0 /. 2.5) +  io.debug(3.0 *. 3.5) + +  // Float comparisons +  io.debug(2.2 >. 1.3) +  io.debug(2.2 <. 1.3) +  io.debug(2.2 >=. 1.3) +  io.debug(2.2 <=. 1.3) + +  // 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 +  io.debug(3.14 /. 0.0) +  // 0.0 + +  // Standard library float functions +  io.debug(float.max(2.0, 9.5)) +  // 9.5 +  io.debug(float.ceiling(5.4)) +  // 6.0 + +  // Underscores for floats are also supported +  io.debug(10_000.01) + +  // Division by zero will not overflow, but is instead defined to be zero. + +  // Working with strings +  io.debug("⭐ Gleam ⭐ - 별") +  io.debug( +    "this +    is +    a +    multi +    line +    string", +  ) +  io.debug("\u{1F600}") +  // Outputs a smiley 😀 + +  // Double quote can be escaped +  io.println("\"X\" marks the spot") + +  // String concatenation +  io.debug("One " <> "Two") + +  // String functions +  io.debug(text.reverse("1 2 3 4 5")) +  io.debug(text.append("abc", "def")) + +  io.println(text.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 + +  io.debug(True && False) +  // False + +  io.debug(True && True) +  // True + +  io.debug(False || False) +  // False + +  io.debug(False || True) +  // True + +  // Bool functions +  io.debug(bool.to_string(True)) +  // "True" + +  io.debug(bool.to_int(False)) +  // 0 + +  // Assignments +  let x = "Original value" +  io.debug(x) + +  // Assign `y` to the value of `x` +  let y = x +  io.debug(y) + +  // Assign `x` to a new value +  let x = "New value" +  io.debug(x) + +  // The `y` still refers to the original value +  io.debug(y) + +  // In Gleam variable and function names are written in snake_case. +  let answer_to_the_universe = 42 +  io.debug(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 +  io.debug(one + two) +  // 3 + +  // Blocks: scoping and value +  let radius = { +    let value = 100.0 +    value +  } +  // io.debug(value) // <- This will not compile because "value" is out of scope + +  let area = 3.14159 *. radius *. radius +  io.debug(area) + +  // Use blocks to group operations instead of parenthesis +  let n1 = { 3 + 2 } * 5 +  let n2 = 3 + { 2 * 5 } +  io.debug(n1 != n2) +  // True + +  // Lists + +  // Nephews of Scrooge McDuck +  let nephews = ["Huey", "Dewey", "Louie"] +  io.debug(nephews) +  // ["Huey", "Dewey", "Louie"] + +  // Immutably prepend so the original list is not changed +  io.debug(["Donald", ..nephews]) +  // ["Donald", "Huey", "Dewey", "Louie"] + +  // Some standard library functions for lists + +  list.each(nephews, io.println) +  // Huey +  // Dewey +  // Louie + +  io.debug(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_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 +  io.debug(double(10)) +  // 20 +  io.debug(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 { +  io.debug(call_func_on_int(double, 2)) +  // 4 + +  let square = fn(x: Int) -> Int { x * x } +  io.debug(square(3)) +  // 9 + +  // Calling an anonymous function inmediately after defining it +  io.debug(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) +  io.debug(adder_of_fives(10)) +  // 15 + +  // Anonymous functions can be used interchangeably with named functions. +  io.debug(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) +  io.debug(quadruple(1)) + +  let quadruple_2 = fn(a: Int) -> Int { multiply(4, a) } +  io.debug(quadruple_2(2)) +  // 8 + +  // A function capture is a shorthand syntax for creating anonymous functions +  // that take one argument and immediately call another function with that +  // argument +  let quadruple_3 = multiply(4, _) +  io.debug(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 } +  io.debug(generic_twice(double_integers, 3)) +  io.debug(generic_twice(double_floats, 3.0)) + +  let quadruple_integers = generic_twice_decorator(double_integers) +  let quadruple_floats = generic_twice_decorator(double_floats) +  io.debug(quadruple_integers(1)) +  // 4 +  io.debug(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 +  |> io.debug + +  // Match cleaner than this right? +  io.debug( +    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. +  iterator.iterate(1, fn(n) { n + 1 }) +  |> iterator.take(1000 - 1) +  |> iterator.filter(fn(n) { { n % 3 == 0 } || { n % 5 == 0 } }) +  |> iterator.fold(from: 0, with: fn(acc, element) { element + acc }) +  |> int.to_string +  |> fn(sum_as_text: String) { +    "Solution to Project Euler's problem #1: " <> sum_as_text +  } +  |> io.debug +  // 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 +  io.debug(call_func_on_int_with_labels(value: 8, func: double)) +  io.debug(add_one(number: 1)) +  // 2 +  io.debug(string.contains(does: "theme", contain: "the")) +  // True +  // Unlabeled arguments must go first +  io.debug(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" +    } +  } +  |> io.debug + +  // 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." +    } +  } +  |> io.debug + +  // 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 -> { +      io.debug("This is the answer to the universe.") +    } +    False -> { +      io.debug("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) { +  io.debug(n) +  case n { +    10 -> Nil +    _ -> from_one_to_ten(n + 1) +  } +} + +// In order to avoid memory exhaustion due to creating excesive +// 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.concat([reverse_list(tail), [head]]) +    [] -> [] +  } +} + +fn more_on_recursion() { +  io.debug(fib(10)) +  // 55 +  io.debug(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 +  io.debug(case "Hello, Lucy" { +    "Hello, " <> name -> "Grettings for " <> name +    _ -> "Potentially no greetings" +  }) + +  // Alternative patters 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 +  } +  io.debug("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 +    } +  } +  io.debug(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) +  io.debug(tuple_01) +  io.debug(tuple_01.0) +  // 1 +  io.debug(tuple_02.1) +  // Lucy +  let #(_, name, species, _) = tuple_01 +  io.debug(name <> " the " <> species) + +  // Pattern-matching with tuples including variable assignment +  case tuple_02 { +    #(_, name, _, True) -> io.debug(name <> " is a mascot.") +    #(_, name, _, False) -> io.debug(name <> " is not a mascot.") +  } + +  // Using a custom type with pattern-matching +  let gender = Other +  io.debug(case gender { +    Male -> "Boy" +    Female -> "Girl" +    _ -> "Undetermined" +  }) + +  // Using records +  let rectangle_1 = Rectangle(base: 10.0, height: 20.0) +  io.debug(rectangle_1.height) +  // 10.3 + +  let point_1 = Point(x: 3.2, y: 4.3) +  io.debug(point_1) + +  // Updating a record +  let point_2 = Point(..point_1, y: 5.7) +  io.debug(point_2) + +  // In Gleam, values ar not nullable. +  // Nil is the only value of its type. +  let some_var = Nil +  let result = io.println("Hello!") +  io.debug(some_var == result) +  // True +} + +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) +  io.debug(mineral_sample_01) +  io.debug(mineral_sample_02) + +  // A glass can be empty or not +  let glass_01: Option(Beverage) = Some(Water) +  let glass_02 = None +  io.debug(glass_01) +  io.debug(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) +  io.debug(person_01) +  io.debug(person_02) + +  // Working with functions that return values of type Result +  let dice_01 = 5 +  case checked_dice_value(dice_01) { +    Ok(checked_value) -> +      io.debug("The value of " <> int.to_string(checked_value) <> " is OK.") +    Error(DiceValueOutOfRange) -> +      io.debug("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) +  |> io.debug +} + +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) { +      result.map(sum_dice_values(first_dice, second_dice), fn(sum) { sum }) +    }) +  }) +} + +// The use expression still let 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()) +  use sum <- result.map(sum_dice_values(first_dice, second_dice)) +  // this is the remaing code in innermost callback function +  sum +} + +fn more_on_callbacks() { +  io.debug(roll_two_dices_without_use()) +  io.debug(roll_two_dices_with_use()) +} + +pub type DateTime + +// External functions must annotate a return type +@external(erlang, "calendar", "local_time") +pub fn now() -> DateTime + +fn showcase_externals() { +  io.debug(now()) +  // #(#(2024, 4, 6), #(14, 4, 16)) +} + +fn showcase_panic() { +  // We can deliberately abort execution by using the panic keyword +  // in order to make our program crash immediately +  case 3 == 2 { +    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 +} +``` + +## Further reading + +* [Gleam's official website](https://gleam.run/) +* [Language tour](https://tour.gleam.run/) - Includes live code editor +* [Official documentation](https://gleam.run/documentation/) +* [Gleam's awesome list](https://github.com/gleam-lang/awesome-gleam) +* [Exercism track for Gleam](https://exercism.org/tracks/gleam) + +There official docs have cheatsheets for people familiar with: + +* [Elixir](https://gleam.run/cheatsheets/gleam-for-elixir-users) +* [Elm](https://gleam.run/cheatsheets/gleam-for-elm-users) +* [Erlang](https://gleam.run/cheatsheets/gleam-for-erlang-users) +* [PHP](https://gleam.run/cheatsheets/gleam-for-php-users) +* [Python](https://gleam.run/cheatsheets/gleam-for-python-users) +* [Rust](https://gleam.run/cheatsheets/gleam-for-python-users) | 
