mirror of
https://github.com/adambard/learnxinyminutes-docs.git
synced 2024-12-23 17:41:41 +00:00
[gleam/en-us] Add initial version of Gleam (#4886)
This commit is contained in:
parent
519c9566fb
commit
2c2e8650d9
888
gleam.html.markdown
Normal file
888
gleam.html.markdown
Normal file
@ -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)
|
Loading…
Reference in New Issue
Block a user