diff options
Diffstat (limited to 'src')
77 files changed, 1316 insertions, 89 deletions
diff --git a/src/content/chapter0_basics/lesson00_hello_world/code.gleam b/src/content/chapter0_basics/lesson00_hello_world/code.gleam new file mode 100644 index 0000000..30530b2 --- /dev/null +++ b/src/content/chapter0_basics/lesson00_hello_world/code.gleam @@ -0,0 +1,5 @@ +import gleam/io + +pub fn main() { + io.println("Hello, Joe!") +} diff --git a/src/content/chapter0_basics/lesson00_hello_world/text.html b/src/content/chapter0_basics/lesson00_hello_world/text.html new file mode 100644 index 0000000..cb41be9 --- /dev/null +++ b/src/content/chapter0_basics/lesson00_hello_world/text.html @@ -0,0 +1,26 @@ +<h2>Hello, friend ๐ซ</h2> +<p> + Welcome to Try Gleam! An interactive tour of the Gleam programming language. +</p> +<p> + It covers all aspects of the Gleam language, and assuming you have some + prior programming experience should teach you everything you need to write + real programs in Gleam. +</p> +<p> + The tour is interactive! The code shown is editable and will be compiled and + evaluated as you type. Anything you print using <code>io.println</code> or + <code>io.debug</code> will be shown in the bottom section, along with any + compile errors and warnings. To evaluate Gleam code the tour compiles Gleam to + JavaScript and runs it, all entirely within your browser window. +</p> +<p> + If at any point you get stuck or have a question do not hesitate to ask in + <a href="https://discord.gg/Fm8Pwmy">the Gleam Discord server</a>. We're here + to help, and if you find something confusing then it's likely others will too, + and we want to know about it so we can improve the tour. +</p> +<p> + OK, let's go. Click "Next" to get started, or click "Index" to jump to a + specific topic. +</p> diff --git a/src/content/chapter0_basics/lesson01_basics/code.gleam b/src/content/chapter0_basics/lesson01_basics/code.gleam new file mode 100644 index 0000000..67cc6b4 --- /dev/null +++ b/src/content/chapter0_basics/lesson01_basics/code.gleam @@ -0,0 +1,7 @@ +// Import a Gleam module from the standard library +import gleam/io + +pub fn main() { + // Print to the console + io.println("Hello, Joe!") +} diff --git a/src/content/chapter0_basics/lesson01_basics/text.html b/src/content/chapter0_basics/lesson01_basics/text.html new file mode 100644 index 0000000..8e2033a --- /dev/null +++ b/src/content/chapter0_basics/lesson01_basics/text.html @@ -0,0 +1,17 @@ +<p> + Here is a program that prints out the text "Hello, Joe!". +</p> +<p> + It does this by using the `println` function which has been imported from the + <a href="https://hexdocs.pm/gleam_stdlib/gleam/io.html"><code>gleam/io</code></a> + module, which is part of the Gleam standard library. +</p> +<p> + In a normal Gleam program this program would be run use the command + <code>gleam run</code> on the command line, but here in this tutorial the + program is automatically compiled and run as the code is edited. +</p> +<p> + Try changing the text being printed to <code>Hello, Mike!</code> and see what + happens. +</p> diff --git a/src/content/chapter0_basics/lesson02_unqualified_imports/code.gleam b/src/content/chapter0_basics/lesson02_unqualified_imports/code.gleam new file mode 100644 index 0000000..2708f25 --- /dev/null +++ b/src/content/chapter0_basics/lesson02_unqualified_imports/code.gleam @@ -0,0 +1,10 @@ +// Import the module and one of its functions +import gleam/io.{println} + +pub fn main() { + // Use the function in a qualified fashion + io.println("This is qualified") + + // Or an unqualified fashion + println("This is unqualified") +} diff --git a/src/content/chapter0_basics/lesson02_unqualified_imports/text.html b/src/content/chapter0_basics/lesson02_unqualified_imports/text.html new file mode 100644 index 0000000..8fda45e --- /dev/null +++ b/src/content/chapter0_basics/lesson02_unqualified_imports/text.html @@ -0,0 +1,15 @@ +<p> + Normally functions from other modules are used in a qualified fashion, with + the module qualifier before function name. For example, + <code>io.println("Hello!")</code>. +</p> +<p> + It is also possible to specify a list of functions to import from a module in + an unqualified fashion, such as the <code>println</code> function in the code + editor. Because it has been imported like this it can be referred to as just + <code>println</code>. +</p> +<p> + Generally it is best to use qualified imports, as this makes it clear where + the function is defined, making the code easier to read. +</p> diff --git a/src/content/chapter0_basics/lesson03_type_checking/code.gleam b/src/content/chapter0_basics/lesson03_type_checking/code.gleam new file mode 100644 index 0000000..e068f31 --- /dev/null +++ b/src/content/chapter0_basics/lesson03_type_checking/code.gleam @@ -0,0 +1,7 @@ +import gleam/io + +pub fn main() { + io.println("My lucky number is:") + // io.println(4) + // ๐๏ธ Uncomment this line +} diff --git a/src/content/chapter0_basics/lesson03_type_checking/text.html b/src/content/chapter0_basics/lesson03_type_checking/text.html new file mode 100644 index 0000000..fadfe64 --- /dev/null +++ b/src/content/chapter0_basics/lesson03_type_checking/text.html @@ -0,0 +1,19 @@ +<p> + Gleam has a robust static type system that is help you as you write and edit + code, catching mistakes and showing you where to make changes. +</p> +<p> + Uncomment the line <code>io.println(4)</code> and see how a compile time error + is reported as the <code>io.println</code> function only works with strings, + not ints. +</p> +<p> + To fix the code change the code to call the <code>io.debug</code> + function instead, as it will print a value of any type. +</p> +<p> + Gleam has no <code>null</code>, no implicit conversions, no exceptions, and + always performs full type checking. If the code compiles you can be reasonably + confident it does not have any inconsistencies that may cause bugs or + crashes. +</p> diff --git a/src/content/chapter0_basics/lesson04_ints/code.gleam b/src/content/chapter0_basics/lesson04_ints/code.gleam new file mode 100644 index 0000000..cb7991b --- /dev/null +++ b/src/content/chapter0_basics/lesson04_ints/code.gleam @@ -0,0 +1,25 @@ +import gleam/io +import gleam/int + +pub fn main() { + // 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 + io.debug(1 == 1) + io.debug(2 == 1) + + // Standard library int functions + io.debug(int.max(42, 77)) + io.debug(int.clamp(5, 10, 20)) +} diff --git a/src/content/chapter0_basics/lesson04_ints/text.html b/src/content/chapter0_basics/lesson04_ints/text.html new file mode 100644 index 0000000..252496a --- /dev/null +++ b/src/content/chapter0_basics/lesson04_ints/text.html @@ -0,0 +1,17 @@ +<p>Gleam's <code>Int</code> type represents whole numbers.</p> +<p> + There are arithmetic and comparison operators for ints, as well as the + equality operator which works on all types. +</p> +<p> + 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, +</p> +<p> + The + <a href="https://hexdocs.pm/gleam_stdlib/gleam/int.html" + ><code>gleam/int</code></a + > + standard library module contains functions for working with ints. +</p> diff --git a/src/content/chapter0_basics/lesson05_floats/code.gleam b/src/content/chapter0_basics/lesson05_floats/code.gleam new file mode 100644 index 0000000..8c4e89a --- /dev/null +++ b/src/content/chapter0_basics/lesson05_floats/code.gleam @@ -0,0 +1,24 @@ +import gleam/io +import gleam/float + +pub fn main() { + // 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) + + // Equality works for any type + io.debug(1.1 == 1.1) + io.debug(2.1 == 1.2) + + // Standard library float functions + io.debug(float.max(2.0, 9.5)) + io.debug(float.ceiling(5.4)) +} diff --git a/src/content/chapter0_basics/lesson05_floats/text.html b/src/content/chapter0_basics/lesson05_floats/text.html new file mode 100644 index 0000000..497bb13 --- /dev/null +++ b/src/content/chapter0_basics/lesson05_floats/text.html @@ -0,0 +1,19 @@ +<p> + Gleam's <code>Float</code> type represents numbers that are not integers. +</p> +<p> + Unlike many languages Gleam does not have a `NaN` or `Infinity` float value. +</p> +<p> + Gleam's numerical operators are not overloaded, so there are dedictated + operators for working with floats. +</p> +<p> + Floats are represented as 64 bit floating point numbers on both Erlang and + JavaScript runtimes. +</p> +<p> + The <a href="https://hexdocs.pm/gleam_stdlib/gleam/float.html"><code>gleam/float</code></a> + standard library module contains functions for working with floats. +</p> + diff --git a/src/content/chapter0_basics/lesson06_number_formats/code.gleam b/src/content/chapter0_basics/lesson06_number_formats/code.gleam new file mode 100644 index 0000000..7307185 --- /dev/null +++ b/src/content/chapter0_basics/lesson06_number_formats/code.gleam @@ -0,0 +1,16 @@ +import gleam/io + +pub fn main() { + // Underscores + io.debug(1_000_000) + io.debug(10_000.01) + + // Binary, octal, and hex Int literals + io.debug(0b00001111) + io.debug(0o17) + io.debug(0xF) + + // Scientific notation Float literals + io.debug(7.0e7) + io.debug(3.0e-4) +} diff --git a/src/content/chapter0_basics/lesson06_number_formats/text.html b/src/content/chapter0_basics/lesson06_number_formats/text.html new file mode 100644 index 0000000..308219a --- /dev/null +++ b/src/content/chapter0_basics/lesson06_number_formats/text.html @@ -0,0 +1,13 @@ +<p> + Underscores can be added to numbers for clarity. For example, + <code>1000000</code> can be tricky to read quickly, while + <code>1_000_000</code> can be easier. +</p> +<p> + Ints can be written in binary, octal, or hexadecimal formats using the + <code>0b</code>, <code>0o</code>, and <code>0x</code> prefixes respectively. +</p> +<p> + Floats can be written in a scientific notation. +</p> + diff --git a/src/content/chapter0_basics/lesson07_strings/code.gleam b/src/content/chapter0_basics/lesson07_strings/code.gleam new file mode 100644 index 0000000..c77163e --- /dev/null +++ b/src/content/chapter0_basics/lesson07_strings/code.gleam @@ -0,0 +1,20 @@ +import gleam/io +import gleam/string + +pub fn main() { + // String literals + io.debug("๐ฉโ๐ป ใใใซใกใฏ Gleam ๐ณ๏ธโ๐") + io.debug( + "multi + line + string", + ) + io.debug("\u{1F600}") + + // String concatenation + io.debug("One " <> "Two") + + // String functions + io.debug(string.reverse("1 2 3 4 5")) + io.debug(string.append("abc", "def")) +} diff --git a/src/content/chapter0_basics/lesson07_strings/text.html b/src/content/chapter0_basics/lesson07_strings/text.html new file mode 100644 index 0000000..820f1b3 --- /dev/null +++ b/src/content/chapter0_basics/lesson07_strings/text.html @@ -0,0 +1,23 @@ +<p> + In Gleam Strings are written as text surrounded by double quotes, and + can span multiple lines and contain unicode characters. +</p> +<p> + The <code><></code> operator can be used to concatenate strings. +</p> +<p> + Several escape sequences are supported: +</p> +<ul> + <li><code>\"</code> - double quote</li> + <li><code>\\</code> - backslash</li> + <li><code>\f</code> - form feed</li> + <li><code>\n</code> - newline</li> + <li><code>\r</code> - carriage return</li> + <li><code>\t</code> - tab</li> + <li><code>\u{xxxxxx}</code> - unicode codepoint</li> +</ul> +<p> + The <a href="https://hexdocs.pm/gleam_stdlib/gleam/string.html"><code>gleam/string</code></a> + standard library module contains functions for working with strings. +</p> diff --git a/src/content/chapter0_basics/lesson08_bools/code.gleam b/src/content/chapter0_basics/lesson08_bools/code.gleam new file mode 100644 index 0000000..e5c1d98 --- /dev/null +++ b/src/content/chapter0_basics/lesson08_bools/code.gleam @@ -0,0 +1,14 @@ +import gleam/io +import gleam/bool + +pub fn main() { + // Bool operators + io.debug(True && False) + io.debug(True && True) + io.debug(False || False) + io.debug(False || True) + + // Bool functions + io.debug(bool.to_string(True)) + io.debug(bool.to_int(False)) +} diff --git a/src/content/chapter0_basics/lesson08_bools/text.html b/src/content/chapter0_basics/lesson08_bools/text.html new file mode 100644 index 0000000..3f60743 --- /dev/null +++ b/src/content/chapter0_basics/lesson08_bools/text.html @@ -0,0 +1,17 @@ +<p> + A <code>Bool</code> is a either <code>True</code> or <code>False</code>. +</p> +<p> + The <code>||</code>, <code>&&</code>, and <code>!</code> operators can be used + to manipulate bools. +</p> +<p> + The <code>||</code> and <code>&&</code> operators are short-circuiting, + meaning that if the left hand side of the operator is <code>True</code> for + <code>||</code> or <code>False</code> for <code>&&</code> then the right hand + side of the operator will not be evaluated. +</p> +<p> + The <a href="https://hexdocs.pm/gleam_stdlib/gleam/bool.html"><code>gleam/bool</code></a> + standard library module contains functions for working with bools. +</p> diff --git a/src/content/chapter0_basics/lesson09_assignments/code.gleam b/src/content/chapter0_basics/lesson09_assignments/code.gleam new file mode 100644 index 0000000..a030e43 --- /dev/null +++ b/src/content/chapter0_basics/lesson09_assignments/code.gleam @@ -0,0 +1,17 @@ +import gleam/io + +pub fn main() { + let x = "Original" + 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" + io.debug(x) + + // The `y` still refers to the original value + io.debug(y) +} diff --git a/src/content/chapter0_basics/lesson09_assignments/text.html b/src/content/chapter0_basics/lesson09_assignments/text.html new file mode 100644 index 0000000..6d535de --- /dev/null +++ b/src/content/chapter0_basics/lesson09_assignments/text.html @@ -0,0 +1,8 @@ +<p> + A value can be assigned to a variable using <code>let</code>. +</p> +<p> + Variable names can be reused by later let bindings, but the values they + reference are immutable, so the values themselves are not changed or mutated + in any way. +</p> diff --git a/src/content/chapter0_basics/lesson10_discard_patterns/code.gleam b/src/content/chapter0_basics/lesson10_discard_patterns/code.gleam new file mode 100644 index 0000000..fa2c0e3 --- /dev/null +++ b/src/content/chapter0_basics/lesson10_discard_patterns/code.gleam @@ -0,0 +1,4 @@ +pub fn main() { + // This variable is never used + let _score = 1000 +} diff --git a/src/content/chapter0_basics/lesson10_discard_patterns/text.html b/src/content/chapter0_basics/lesson10_discard_patterns/text.html new file mode 100644 index 0000000..46dc79b --- /dev/null +++ b/src/content/chapter0_basics/lesson10_discard_patterns/text.html @@ -0,0 +1,10 @@ +<p> + If a variable is assigned but not used then Gleam will emit a warning. +</p> +<p> + If a variable is intended not to be use then the name can be prefixed with an + underscore, silencing the warning. +</p> +<p> + Try changing the variable name to <code>score</code> to see the warning. +</p> diff --git a/src/content/chapter0_basics/lesson11_type_annotations/code.gleam b/src/content/chapter0_basics/lesson11_type_annotations/code.gleam new file mode 100644 index 0000000..1299c2f --- /dev/null +++ b/src/content/chapter0_basics/lesson11_type_annotations/code.gleam @@ -0,0 +1,7 @@ +pub fn main() { + let _name: String = "Gleam" + + let _is_cool: Bool = True + + let _version: Int = 1 +} diff --git a/src/content/chapter0_basics/lesson11_type_annotations/text.html b/src/content/chapter0_basics/lesson11_type_annotations/text.html new file mode 100644 index 0000000..8738a15 --- /dev/null +++ b/src/content/chapter0_basics/lesson11_type_annotations/text.html @@ -0,0 +1,15 @@ +<p> + Let assignments can be written with a type annotation after the name. +</p> +<p> + Type annotations may be useful for documentation purposes, but they do not + change how Gleam type checks the code beyond ensuring that the annotation is + correct. +</p> +<p> + Typically Gleam code will not have type annotations for assignments. +</p> +<p> + Try changing a type annotation to something incorrect to see the compile + error. +</p> diff --git a/src/content/chapter0_basics/lesson12_blocks/code.gleam b/src/content/chapter0_basics/lesson12_blocks/code.gleam new file mode 100644 index 0000000..31e4729 --- /dev/null +++ b/src/content/chapter0_basics/lesson12_blocks/code.gleam @@ -0,0 +1,13 @@ +import gleam/io + +pub fn main() { + let fahrenheit = { + let degrees = 64 + degrees + } + // io.debug(degrees) // <- This will not compile + + // Changing order of evaluation + let celsius = { fahrenheit - 32 } * 5 / 9 + io.debug(celsius) +} diff --git a/src/content/chapter0_basics/lesson12_blocks/text.html b/src/content/chapter0_basics/lesson12_blocks/text.html new file mode 100644 index 0000000..bc82e39 --- /dev/null +++ b/src/content/chapter0_basics/lesson12_blocks/text.html @@ -0,0 +1,23 @@ +<p> + Blocks are one or more expressions grouped together with curly braces. Each + expression is evaluated in order and the value of the last expression is + returned. +</p> +<p> + Any variables assigned within the block can only be used within the block. +</p> +<p> + Try uncommenting <code>io.debug(degrees)</code> to see the compile error from + trying to use a variable that is not in scope. +</p> +<p> + Blocks can also be used to change the order of evaluation of binary operators + expressions. +</p> +<p> + <code>*</code> binds more tightly than <code>+</code> so the expression + <code>1 + 2 * 3</code> evaluates to 7. If the <code>1 + 2</code> should be + evaluated first to make the expression evaluate to 9 then the expression can be + wrapped in a block: <code>{ 1 + 2 } * 3</code>. This is similar to grouping + with parentheses in some other languages. +</p> diff --git a/src/content/chapter0_basics/lesson13_lists/code.gleam b/src/content/chapter0_basics/lesson13_lists/code.gleam new file mode 100644 index 0000000..646ad6e --- /dev/null +++ b/src/content/chapter0_basics/lesson13_lists/code.gleam @@ -0,0 +1,16 @@ +import gleam/io + +pub fn main() { + let ints = [1, 2, 3] + + io.debug(ints) + + // Immutably prepend + io.debug([-1, 0, ..ints]) + + // Uncomment this to see the error + // io.debug(["zero", ..ints]) + + // The original lists are unchanged + io.debug(ints) +} diff --git a/src/content/chapter0_basics/lesson13_lists/text.html b/src/content/chapter0_basics/lesson13_lists/text.html new file mode 100644 index 0000000..dd07cd7 --- /dev/null +++ b/src/content/chapter0_basics/lesson13_lists/text.html @@ -0,0 +1,19 @@ +<p> + Lists are ordered collections of values. +</p> +<p> + <code>List</code> is a generic type, having a type parameter + for the type of values it contains. A list of ints has the type + <code>List(Int)</code>, and a list of strings has the type + <code>List(String)</code>. +</p> +<p> + Lists are immutable single-linked lists, meaning they are very efficient to + add and remove elements from the front of the list. +</p> +<p> + Counting the length of a list or getting elements from other positions in the + list is expensive and rarely done. It is rare to write algorithms that index + into sequences in Gleam, but but when they are written a list is not the right + choice of data structure. +</p> diff --git a/src/content/chapter0_basics/lesson14_list_functions/code.gleam b/src/content/chapter0_basics/lesson14_list_functions/code.gleam new file mode 100644 index 0000000..92d1cc6 --- /dev/null +++ b/src/content/chapter0_basics/lesson14_list_functions/code.gleam @@ -0,0 +1,15 @@ +import gleam/io +import gleam/list + +pub fn main() { + let ints = [0, 1, 2, 3, 4, 5] + + let doubled = list.map(ints, fn(x) { x * 2 }) + io.debug(doubled) + + let even = list.filter(ints, fn(x) { x % 2 == 0 }) + io.debug(even) + + let total = list.fold(ints, 0, fn(count, e) { count + e }) + io.debug(total) +} diff --git a/src/content/chapter0_basics/lesson14_list_functions/text.html b/src/content/chapter0_basics/lesson14_list_functions/text.html new file mode 100644 index 0000000..e143654 --- /dev/null +++ b/src/content/chapter0_basics/lesson14_list_functions/text.html @@ -0,0 +1,25 @@ +<p> + The <a href="https://hexdocs.pm/gleam_stdlib/gleam/list.html"><code>gleam/list</code></a> + standard library module contains functions for working with lists. A Gleam + program will likely make heavy use of this module. +</p> + +<p> + <a href="https://hexdocs.pm/gleam_stdlib/gleam/list.html#map"><code>map</code></a> + makes a new list by running a function on each element in a list. +</p> +<p> + <a href="https://hexdocs.pm/gleam_stdlib/gleam/list.html#filter"><code>filter</code></a> + makes a new list containing only the elements for which a function returns + true. +</p> +<p> + <a href="https://hexdocs.pm/gleam_stdlib/gleam/list.html#fold"><code>fold</code></a> + combines all the elements in a list into a single value by running a function + left-to-right on each element, passing the result of the previous call to the + next call. +</p> +<p> + It's worth getting familiar with all the functions in this module when writing + Gleam code. +</p> diff --git a/src/content/chapter1_functions/lesson00_functions/code.gleam b/src/content/chapter1_functions/lesson00_functions/code.gleam new file mode 100644 index 0000000..220e58d --- /dev/null +++ b/src/content/chapter1_functions/lesson00_functions/code.gleam @@ -0,0 +1,13 @@ +import gleam/io + +pub fn main() { + io.debug(double(10)) +} + +fn double(a: Int) -> Int { + multiply(a, 2) +} + +fn multiply(a: Int, b: Int) -> Int { + a * b +} diff --git a/src/content/chapter1_functions/lesson00_functions/text.html b/src/content/chapter1_functions/lesson00_functions/text.html new file mode 100644 index 0000000..32d5bed --- /dev/null +++ b/src/content/chapter1_functions/lesson00_functions/text.html @@ -0,0 +1,14 @@ +<p> + The <code>fn</code> keyword is used to define new functions. +</p> +<p> + The <code>double</code> and <code>multiply</code> functions are defined + without the <code>pub</code> keyword. This makes them <em>private</em> + functions, they can only be used within this module. If another module + attempted to use them it would result in a compiler error. +</p> +<p> + Like with assignments, type annotations are optional for function arguments + and return values. It is considered good practice to use type annotations for + functions, for clarity and to encourage intentional and thoughtful design. +</p> diff --git a/src/content/chapter1_functions/lesson01_higher_order_functions/code.gleam b/src/content/chapter1_functions/lesson01_higher_order_functions/code.gleam new file mode 100644 index 0000000..e3fb3e7 --- /dev/null +++ b/src/content/chapter1_functions/lesson01_higher_order_functions/code.gleam @@ -0,0 +1,18 @@ +import gleam/io + +pub fn main() { + // Call a function with another function + io.debug(twice(1, add_one)) + + // Functions can be assigned to variables + let function = add_one + io.debug(function(100)) +} + +fn twice(argument: Int, function: fn(Int) -> Int) -> Int { + function(function(argument)) +} + +fn add_one(argument: Int) -> Int { + argument + 1 +} diff --git a/src/content/chapter1_functions/lesson01_higher_order_functions/text.html b/src/content/chapter1_functions/lesson01_higher_order_functions/text.html new file mode 100644 index 0000000..3343e4d --- /dev/null +++ b/src/content/chapter1_functions/lesson01_higher_order_functions/text.html @@ -0,0 +1,12 @@ +<p> + In Gleam functions are values. They can be assigned to variables, passed to + other functions, and anything else you can do with values. +</p> +<p> + Here the function <code>add_one</code> is being passed as an argument to the + <code>twice</code> function. +</p> +<p> + Notice the <code>fn</code> keyword is also used to describe the type of the + function that <code>twice</code> takes as its second argument. +</p> diff --git a/src/content/chapter1_functions/lesson02_anonymous_functions/code.gleam b/src/content/chapter1_functions/lesson02_anonymous_functions/code.gleam new file mode 100644 index 0000000..2b037e0 --- /dev/null +++ b/src/content/chapter1_functions/lesson02_anonymous_functions/code.gleam @@ -0,0 +1,14 @@ +import gleam/io + +pub fn main() { + // Assign an anonymous function to a variable + let add_one = fn(a) { a + 1 } + io.debug(twice(1, add_one)) + + // Pass an anonymous function as an argument + io.debug(twice(1, fn(a) { a * 2 })) +} + +fn twice(argument: Int, function: fn(Int) -> Int) -> Int { + function(function(argument)) +} diff --git a/src/content/chapter1_functions/lesson02_anonymous_functions/text.html b/src/content/chapter1_functions/lesson02_anonymous_functions/text.html new file mode 100644 index 0000000..f7bea3f --- /dev/null +++ b/src/content/chapter1_functions/lesson02_anonymous_functions/text.html @@ -0,0 +1,7 @@ +<p> + As well as module-level named functions, Gleam has anonymous function + literals. +</p> +<p> + Anonymous functions can be used interchangeably with named functions. +</p> diff --git a/src/content/chapter1_functions/lesson03_function_captures/code.gleam b/src/content/chapter1_functions/lesson03_function_captures/code.gleam new file mode 100644 index 0000000..35f3412 --- /dev/null +++ b/src/content/chapter1_functions/lesson03_function_captures/code.gleam @@ -0,0 +1,14 @@ +import gleam/io + +pub fn main() { + // These two statements are equivalent + let add_one_v1 = fn(x) { add(1, x) } + let add_one_v2 = add(1, _) + + io.debug(add_one_v1(10)) + io.debug(add_one_v2(10)) +} + +fn add(a: Int, b: Int) -> Int { + a + b +} diff --git a/src/content/chapter1_functions/lesson03_function_captures/text.html b/src/content/chapter1_functions/lesson03_function_captures/text.html new file mode 100644 index 0000000..afa87a3 --- /dev/null +++ b/src/content/chapter1_functions/lesson03_function_captures/text.html @@ -0,0 +1,11 @@ +<p> + Gleam has a shorthand syntax for creating anonymous functions that take one + argument and immediately call another function with that argument: the + function capture syntax. +</p> +<p> + The anonymous function <code>fn(a) { some_function(..., a, ...) }</code> can + be written as <code>some_function(..., _, ...)</code>, with any number of + other arguments passed to the inner function. The underscore <code>_</code> is + a placeholder for the final argument. +</p> diff --git a/src/content/chapter1_functions/lesson04_generic_functions/code.gleam b/src/content/chapter1_functions/lesson04_generic_functions/code.gleam new file mode 100644 index 0000000..e232bf8 --- /dev/null +++ b/src/content/chapter1_functions/lesson04_generic_functions/code.gleam @@ -0,0 +1,19 @@ +import gleam/io + +pub fn main() { + let add_one = fn(x) { x + 1 } + let exclaim = fn(x) { x <> "!" } + + // Invalid, Int and String are not the same type + // twice(10, exclaim) + + // Here the type variable is replaced by the type Int + io.debug(twice(10, add_one)) + + // Here the type variable is replaced by the type String + io.debug(twice("Hello", exclaim)) +} + +fn twice(argument: value, function: fn(value) -> value) -> value { + function(function(argument)) +} diff --git a/src/content/chapter1_functions/lesson04_generic_functions/text.html b/src/content/chapter1_functions/lesson04_generic_functions/text.html new file mode 100644 index 0000000..1369c93 --- /dev/null +++ b/src/content/chapter1_functions/lesson04_generic_functions/text.html @@ -0,0 +1,25 @@ +<p> + Up until now each function has accepted precisely one type for each of its + arguments. +</p> +<p> + The <code>twice</code> function for example only worked with functions that + would take and return ints. This is overly restrictive, it should be possible + to use this function with any type, so long as the function and the initial + value are compatible. +</p> +<p> + To enable this Gleam support <em>generics</em>, also known as <em>parametric + polymorphism</em>. +</p> +<p> + This works by instead of specifying a concrete type, a type variable is used + which stands in for whatever specific type is being used when the function is + called. These type variable are written with a lowercase name. +</p> +<p> + Type variables are not like an <code>any</code> type, they get replaced with a + specific type each time the function is called. Try uncommenting + <code>twice(10, exclaim)</code> to see the compiler error from trying to use a + type variable as an int and a string at the same time. +</p> diff --git a/src/content/chapter1_functions/lesson05_pipelines/code.gleam b/src/content/chapter1_functions/lesson05_pipelines/code.gleam new file mode 100644 index 0000000..ec9b805 --- /dev/null +++ b/src/content/chapter1_functions/lesson05_pipelines/code.gleam @@ -0,0 +1,19 @@ +import gleam/io +import gleam/string + +pub fn main() { + // Without the pipe operator + io.debug(string.drop_left(string.drop_right("Hello, Joe!", 1), 7)) + + // With the pipe operator + "Hello, Mike!" + |> string.drop_right(1) + |> string.drop_left(7) + |> io.debug + + // Changing order with function capturing + "1" + |> string.append("2") + |> string.append("3", _) + |> io.debug +} diff --git a/src/content/chapter1_functions/lesson05_pipelines/text.html b/src/content/chapter1_functions/lesson05_pipelines/text.html new file mode 100644 index 0000000..783ade9 --- /dev/null +++ b/src/content/chapter1_functions/lesson05_pipelines/text.html @@ -0,0 +1,25 @@ +<p> + It's common to want to call a series of functions, passing the result of one + to the next. With the regular function call syntax this can be a little + difficult to read as you have to read the code from the inside out. +</p> +<p> + Gleam's pipe operator <code>|></code> helps with this problem by allowing you + to write code top-to-bottom. +</p> +<p> + The pipe operator takes the result of the expression on its left and passes it + as an argument to the function on its right. +</p> +<p> + It will first check to see if the left-hand value could be used as the first + argument to the call. For example, <code>a |> b(1, 2)</code> would become + <code>b(a, 1, 2)</code>. If not, it falls back to calling the result of the + right-hand side as a function, e.g., <code>b(1, 2)(a)</code> +</p> +<p> + Gleam code is typically written with the "subject" of the function as the + first argument, to make it easier to pipe. If you wish to pipe to a different + position then a function capture can be used to insert the argument to the + desired position. +</p> diff --git a/src/content/chapter1_functions/lesson06_labelled_arguments/code.gleam b/src/content/chapter1_functions/lesson06_labelled_arguments/code.gleam new file mode 100644 index 0000000..25bb8c1 --- /dev/null +++ b/src/content/chapter1_functions/lesson06_labelled_arguments/code.gleam @@ -0,0 +1,16 @@ +import gleam/io + +pub fn main() { + // Without using labels + io.debug(calculate(1, 2, 3)) + + // Using the labels + io.debug(calculate(1, add: 2, multiply: 3)) + + // Using the labels in a different order + io.debug(calculate(1, multiply: 3, add: 2)) +} + +fn calculate(value: Int, add addend: Int, multiply multiplier: Int) { + value * multiplier + addend +} diff --git a/src/content/chapter1_functions/lesson06_labelled_arguments/text.html b/src/content/chapter1_functions/lesson06_labelled_arguments/text.html new file mode 100644 index 0000000..b1d771c --- /dev/null +++ b/src/content/chapter1_functions/lesson06_labelled_arguments/text.html @@ -0,0 +1,23 @@ +<p> + When functions take several arguments it can be difficult to remember what the + arguments are, and what order they are expected in. +</p> +<p> + To help with this Gleam supports labelled arguments, where function arguments + are given an external label in addition to their internal name. These labels + are written before the argument name in the function definition. +</p> +<p> + When labelled arguments are used the order of the arguments does not matter, + but all unlabelled arguments must come before labelled arguments. +</p> +<p> + There is no performance cost to using labelled arguments, it does not allocate + a dictionary or perform any other runtime work. +</p> +<p> + Labels are optional when calling a function, it is up to the programmer to + decide what is clearest in their code. +</p> + + diff --git a/src/content/chapter1_functions/lesson099_documentation_comments/code.gleam b/src/content/chapter1_functions/lesson099_documentation_comments/code.gleam new file mode 100644 index 0000000..a84dce6 --- /dev/null +++ b/src/content/chapter1_functions/lesson099_documentation_comments/code.gleam @@ -0,0 +1,19 @@ +//// A module containing some unusual functions and types. + +/// A type where the value can never be constructed. +/// Can you work out why? +pub type Never { + Never(Never) +} + +/// Call a function twice with an initial value. +/// +pub fn twice(argument: value, function: fn(value) -> value) -> value { + function(function(argument)) +} + +/// Call a function three times with an initial value. +/// +pub fn thrice(argument: value, function: fn(value) -> value) -> value { + function(function(function(argument))) +} diff --git a/src/content/chapter1_functions/lesson099_documentation_comments/text.html b/src/content/chapter1_functions/lesson099_documentation_comments/text.html new file mode 100644 index 0000000..c27bac6 --- /dev/null +++ b/src/content/chapter1_functions/lesson099_documentation_comments/text.html @@ -0,0 +1,16 @@ +<p> + Documentation and comments are important tools for making your code easier to + work with and understand. +</p> +<p> + As well as regular <code>//</code> comments Gleam has <code>///</code> and + <code>////</code> comments which are used for attaching documentation to code. +</p> +<p> + <code>///</code> is used for documenting types and functions, and should be + placed immediately before the type or function it is documenting. +</p> +<p> + <code>////</code> is used for documenting modules, and should be placed + at the top of the module. +</p> diff --git a/src/content/chapter2_flow_control/lesson01_case_expressions/code.gleam b/src/content/chapter2_flow_control/lesson01_case_expressions/code.gleam new file mode 100644 index 0000000..6e1ed46 --- /dev/null +++ b/src/content/chapter2_flow_control/lesson01_case_expressions/code.gleam @@ -0,0 +1,10 @@ +import gleam/io +import gleam/int + +pub fn main() { + let result = case int.random(5) { + 0 -> "It's zero!" + other -> "It's " <> int.to_string(other) + } + io.debug(result) +} diff --git a/src/content/chapter2_flow_control/lesson01_case_expressions/text.html b/src/content/chapter2_flow_control/lesson01_case_expressions/text.html new file mode 100644 index 0000000..7e9ac11 --- /dev/null +++ b/src/content/chapter2_flow_control/lesson01_case_expressions/text.html @@ -0,0 +1,7 @@ +<p> + Patterns in case expressions can also assign variables. +</p> +<p> + When a variable name is used in a pattern the value that is matched against is + assigned to that name, and can be used in the body of that clause. +</p> diff --git a/src/content/chapter2_flow_control/lesson02_variable_patterns/code.gleam b/src/content/chapter2_flow_control/lesson02_variable_patterns/code.gleam new file mode 100644 index 0000000..7bcc93c --- /dev/null +++ b/src/content/chapter2_flow_control/lesson02_variable_patterns/code.gleam @@ -0,0 +1,16 @@ +import gleam/io +import gleam/int + +pub fn main() { + let x = int.random(5) + io.debug(x) + + let result = case x { + // Match specific values + 0 -> "Zero" + 1 -> "One" + // Match any other value + _ -> "Other" + } + io.debug(result) +} diff --git a/src/content/chapter2_flow_control/lesson02_variable_patterns/text.html b/src/content/chapter2_flow_control/lesson02_variable_patterns/text.html new file mode 100644 index 0000000..8154979 --- /dev/null +++ b/src/content/chapter2_flow_control/lesson02_variable_patterns/text.html @@ -0,0 +1,17 @@ +<p> + The case expression is the most common kind of flow control in Gleam code. It + is similar to `switch` in some other languages, but more powerful than most. +</p> +<p> + It allows the programmer to say "if the data has this shape then run this + code", a process called called <em>pattern matching</em>. +</p> +<p> + Gleam performs <em>exhaustiveness checking</em> to ensure that the patterns in + a case expression cover all possible values. With this you can have confidence + that your logic is up-to-date for the design of the data you are working with. +</p> +<p> + Try commenting out patterns or adding new redundant ones, and see what + problems the compiler reports. +</p> diff --git a/src/content/chapter2_flow_control/lesson03_string_patterns/code.gleam b/src/content/chapter2_flow_control/lesson03_string_patterns/code.gleam new file mode 100644 index 0000000..d1441a0 --- /dev/null +++ b/src/content/chapter2_flow_control/lesson03_string_patterns/code.gleam @@ -0,0 +1,14 @@ +import gleam/io + +pub fn main() { + io.debug(get_name("Hello, Joe")) + io.debug(get_name("Hello, Mike")) + io.debug(get_name("System still working?")) +} + +fn get_name(x: String) -> String { + case x { + "Hello, " <> name -> name + _ -> "Unknown" + } +} diff --git a/src/content/chapter2_flow_control/lesson03_string_patterns/text.html b/src/content/chapter2_flow_control/lesson03_string_patterns/text.html new file mode 100644 index 0000000..0dd3274 --- /dev/null +++ b/src/content/chapter2_flow_control/lesson03_string_patterns/text.html @@ -0,0 +1,9 @@ +<p> + When pattern matching on strings the <code><></code> operator can be + used to match on strings with a specific prefix. +</p> +<p> + The pattern <code>"hello " <> name</code> matches any string that starts with + <code>"hello "</code> and asigns the rest of the string to the variable + <code>name</code>. +</p> diff --git a/src/content/chapter2_flow_control/lesson04_list_patterns/code.gleam b/src/content/chapter2_flow_control/lesson04_list_patterns/code.gleam new file mode 100644 index 0000000..e767d20 --- /dev/null +++ b/src/content/chapter2_flow_control/lesson04_list_patterns/code.gleam @@ -0,0 +1,17 @@ +import gleam/io +import gleam/int +import gleam/list + +pub fn main() { + let x = list.repeat(int.random(5), times: int.random(3)) + io.debug(x) + + let result = case x { + [] -> "Empty list" + [1] -> "List of just 1" + [4, ..] -> "List starting with 4" + [_, _] -> "List of 2 elements" + _ -> "Some other list" + } + io.debug(result) +} diff --git a/src/content/chapter2_flow_control/lesson04_list_patterns/text.html b/src/content/chapter2_flow_control/lesson04_list_patterns/text.html new file mode 100644 index 0000000..de55eef --- /dev/null +++ b/src/content/chapter2_flow_control/lesson04_list_patterns/text.html @@ -0,0 +1,15 @@ +<p> + Lists and the values they contain can be pattern matched on in case + expressions. +</p> +<p> + List patterns match on specific lengths of lists. The pattern <code>[]</code> + matches an empty list, and the pattern <code>[_]</code> matches a list with + one element. They will not match on lists with other lengths. +</p> +<p> + The spread pattern <code>..</code> can be used to match the rest of the list. + The pattern <code>[1, ..]</code> matches any list that starts with + <code>1</code>. The pattern <code>[_, _, ..]</code> matches any list that has + at least two elements. +</p> diff --git a/src/content/chapter2_flow_control/lesson05_list_recursion/code.gleam b/src/content/chapter2_flow_control/lesson05_list_recursion/code.gleam new file mode 100644 index 0000000..370675a --- /dev/null +++ b/src/content/chapter2_flow_control/lesson05_list_recursion/code.gleam @@ -0,0 +1,13 @@ +import gleam/io + +pub fn main() { + let sum = sum_list([18, 56, 35, 85, 91], 0) + io.debug(sum) +} + +fn sum_list(list: List(Int), total: Int) -> Int { + case list { + [first, ..rest] -> sum_list(rest, total + first) + [] -> total + } +} diff --git a/src/content/chapter2_flow_control/lesson05_list_recursion/text.html b/src/content/chapter2_flow_control/lesson05_list_recursion/text.html new file mode 100644 index 0000000..7f2351d --- /dev/null +++ b/src/content/chapter2_flow_control/lesson05_list_recursion/text.html @@ -0,0 +1,22 @@ +<p> + Most commonly functions in the + <a href="https://hexdocs.pm/gleam_stdlib/gleam/list.html"><code>gleam/list</code></a> + module are used to iterate across a list, but at times you may prefer + to work with the list directly. +</p> +<p> + Gleam doesn't have a looping syntax, instead iteration is done through + recursion and pattern matching. +</p> +<p> + The <code>[first, ..rest]</code> pattern matches on a list with at least one + element, assigning the first element to the variable <code>first</code> and + the rest of the list to the variable <code>rest</code>. + By using this pattern and a pattern for the empty list <code>[]</code> a + function can run code on each element of a list until the end is reached. +</p> +<p> + This code sums a list by recursing over the list and adding each int to a + <code>total</code> argument, returning it when the end is reached. +</p> + diff --git a/src/content/chapter2_flow_control/lesson06_multiple_subjects/code.gleam b/src/content/chapter2_flow_control/lesson06_multiple_subjects/code.gleam new file mode 100644 index 0000000..d7aa34a --- /dev/null +++ b/src/content/chapter2_flow_control/lesson06_multiple_subjects/code.gleam @@ -0,0 +1,17 @@ +import gleam/io +import gleam/int + +pub fn main() { + let x = int.random(2) + let y = int.random(2) + io.debug(x) + io.debug(y) + + let result = case x, y { + 0, 0 -> "Both are zero" + 0, _ -> "First is zero" + _, 0 -> "Second is zero" + _, _ -> "Neither are zero" + } + io.debug(result) +} diff --git a/src/content/chapter2_flow_control/lesson06_multiple_subjects/text.html b/src/content/chapter2_flow_control/lesson06_multiple_subjects/text.html new file mode 100644 index 0000000..26a7ea3 --- /dev/null +++ b/src/content/chapter2_flow_control/lesson06_multiple_subjects/text.html @@ -0,0 +1,13 @@ +<p> + Sometimes it is useful to pattern match on multiple values at the same time in + one case experession. +</p> +<p> + To do this you can give multiple subjects and multiple patterns, separated + commas. +</p> +<p> + When matching on multiple subjects there must be the same number of patterns + as there are subjects. Try removing one of the <code>_,</code> sub-patterns to + see the compile time error that is returned. +</p> diff --git a/src/content/chapter2_flow_control/lesson07_alternative_patterns/code.gleam b/src/content/chapter2_flow_control/lesson07_alternative_patterns/code.gleam new file mode 100644 index 0000000..06a6562 --- /dev/null +++ b/src/content/chapter2_flow_control/lesson07_alternative_patterns/code.gleam @@ -0,0 +1,14 @@ +import gleam/io +import gleam/int + +pub fn main() { + let number = int.random(10) + io.debug(number) + + let result = case number { + 2 | 4 | 6 | 8 -> "This is an even number" + 1 | 3 | 5 | 7 -> "This is an odd number" + _ -> "I'm not sure" + } + io.debug(result) +} diff --git a/src/content/chapter2_flow_control/lesson07_alternative_patterns/text.html b/src/content/chapter2_flow_control/lesson07_alternative_patterns/text.html new file mode 100644 index 0000000..10ad731 --- /dev/null +++ b/src/content/chapter2_flow_control/lesson07_alternative_patterns/text.html @@ -0,0 +1,17 @@ +<p> + Alternative patterns can be given for a case clause using the + <code>|</code> operator. If any of the patterns match then the clause matches. +</p> +<p> + When matching on multiple subjects there must be the same number of patterns + as there are subjects. Try removing one of the <code>_,</code> sub-patterns to + see the compile time error that is returned. +</p> +<p> + If a pattern defines a variable then all of the alternative patterns for that + clause must also define a variable with the same name and same type. +</p> +<p> + Currently it is not possible to have nested alternative patterns, so the + pattern <code>[1 | 2 | 3]</code> is not valid. +</p> diff --git a/src/content/chapter2_flow_control/lesson08_pattern_aliases/code.gleam b/src/content/chapter2_flow_control/lesson08_pattern_aliases/code.gleam new file mode 100644 index 0000000..ee40a26 --- /dev/null +++ b/src/content/chapter2_flow_control/lesson08_pattern_aliases/code.gleam @@ -0,0 +1,15 @@ +import gleam/io + +pub fn main() { + io.debug(get_first_non_empty([[], [1, 2, 3], [4, 5]])) + io.debug(get_first_non_empty([[1, 2], [3, 4, 5], []])) + io.debug(get_first_non_empty([[], [], []])) +} + +fn get_first_non_empty(lists: List(List(t))) -> List(t) { + case lists { + [[_, ..] as first, ..] -> first + [_, ..rest] -> get_first_non_empty(rest) + [] -> [] + } +} diff --git a/src/content/chapter2_flow_control/lesson08_pattern_aliases/text.html b/src/content/chapter2_flow_control/lesson08_pattern_aliases/text.html new file mode 100644 index 0000000..b737eb8 --- /dev/null +++ b/src/content/chapter2_flow_control/lesson08_pattern_aliases/text.html @@ -0,0 +1,7 @@ +<p> + The <code>as</code> operator can be used to assign sub patterns to variables. +</p> +<p> + The pattern <code>[_, ..] as it</code> will match any non-empty list and + assign that list to the variable <code>it</code>. +</p> diff --git a/src/content/chapter3_data_types/lesson00_tuples/code.gleam b/src/content/chapter3_data_types/lesson00_tuples/code.gleam new file mode 100644 index 0000000..d5c6313 --- /dev/null +++ b/src/content/chapter3_data_types/lesson00_tuples/code.gleam @@ -0,0 +1,10 @@ +import gleam/io + +pub fn main() { + let triple = #(1, 2.2, "three") + io.debug(triple) + + let #(a, _, _) = triple + io.debug(a) + io.debug(triple.1) +} diff --git a/src/content/chapter3_data_types/lesson00_tuples/text.html b/src/content/chapter3_data_types/lesson00_tuples/text.html new file mode 100644 index 0000000..f121a9d --- /dev/null +++ b/src/content/chapter3_data_types/lesson00_tuples/text.html @@ -0,0 +1,20 @@ +<p> + Lists are good for when we want a collection of one type, but sometimes we + want to combine multiple values of different types. In this case tuples are a + quick and convenient option. +</p> +<p> + The tuple access syntax can be used to get elements from a tuple without + pattern matching. <code>some_tuple.0</code> gets the first element, + <code>some_tuple.1</code> gets the second element, etc. +</p> +<p> + Tuples are generic types, they have type parameters for the types they + contain. <code>#(1, "Hi!")</code> has the type <code>#(Int, String)</code>, + and <code>#(1.4, 10, 48)</code> has the type <code>#(Float, Int, Int)</code>. +</p> +<p> + Tuples are most commonly used to return 2 or 3 values from a function. Other + times it is often is clearer to use a <em>custom type</em>, which we will + cover next. +</p> diff --git a/src/content/chapter3_data_types/lesson01_custom_types/code.gleam b/src/content/chapter3_data_types/lesson01_custom_types/code.gleam new file mode 100644 index 0000000..35629bf --- /dev/null +++ b/src/content/chapter3_data_types/lesson01_custom_types/code.gleam @@ -0,0 +1,22 @@ +import gleam/io + +pub type Season { + Spring + Summer + Autumn + Winter +} + +pub fn main() { + io.debug(weather(Spring)) + io.debug(weather(Autumn)) +} + +fn weather(season: Season) -> String { + case season { + Spring -> "Mild" + Summer -> "Hot" + Autumn -> "Windy" + Winter -> "Cold" + } +} diff --git a/src/content/chapter3_data_types/lesson01_custom_types/text.html b/src/content/chapter3_data_types/lesson01_custom_types/text.html new file mode 100644 index 0000000..dad6d12 --- /dev/null +++ b/src/content/chapter3_data_types/lesson01_custom_types/text.html @@ -0,0 +1,9 @@ +<p> + Gleam has a few built in types such as <code>Int</code>, <code>String</code>, + but custom types allow the creation of entirely new types. +</p> +<p> + A custom type is defined with the <code>type</code> keyword followed by a + constructor for each <em>variant</em> of the type. +</p> +<p>Custom type variants can be pattern matched on using a case expression.</p> diff --git a/src/content/chapter3_data_types/lesson02_records/code.gleam b/src/content/chapter3_data_types/lesson02_records/code.gleam new file mode 100644 index 0000000..bd6da3c --- /dev/null +++ b/src/content/chapter3_data_types/lesson02_records/code.gleam @@ -0,0 +1,17 @@ +import gleam/io + +pub type SchoolPerson { + Teacher(name: String, subject: String) + Student(String) +} + +pub fn main() { + let teacher1 = Teacher("Mr Schofield", "Physics") + let teacher2 = Teacher(name: "Miss Percy", subject: "Physics") + let student1 = Student("Koushiar") + let student2 = Student("Naomi") + let student3 = Student("Shaheer") + + let school = [teacher1, teacher2, student1, student2, student3] + io.debug(school) +} diff --git a/src/content/chapter3_data_types/lesson02_records/text.html b/src/content/chapter3_data_types/lesson02_records/text.html new file mode 100644 index 0000000..f515ccd --- /dev/null +++ b/src/content/chapter3_data_types/lesson02_records/text.html @@ -0,0 +1,10 @@ +<p>Variants of a record can hold other data within them.</p> +<p> + These fields can be given labels, and like function argument labels they can + be optionally used when calling the record constructor. Typically labels will + be used for variants that define them. +</p> +<p> + It is common to have a custom type with one variant that holds data, this is + the Gleam equivalent of a struct or object in other languages. +</p> diff --git a/src/content/chapter3_data_types/lesson03_record_accessors/code.gleam b/src/content/chapter3_data_types/lesson03_record_accessors/code.gleam new file mode 100644 index 0000000..63ca721 --- /dev/null +++ b/src/content/chapter3_data_types/lesson03_record_accessors/code.gleam @@ -0,0 +1,15 @@ +import gleam/io + +pub type SchoolPerson { + Teacher(name: String, subject: String) + Student(name: String) +} + +pub fn main() { + let teacher = Teacher("Mr Schofield", "Physics") + let student = Student("Koushiar") + + io.debug(teacher.name) + io.debug(student.name) + // io.debug(teacher.subject) +} diff --git a/src/content/chapter3_data_types/lesson03_record_accessors/text.html b/src/content/chapter3_data_types/lesson03_record_accessors/text.html new file mode 100644 index 0000000..e8bbbc2 --- /dev/null +++ b/src/content/chapter3_data_types/lesson03_record_accessors/text.html @@ -0,0 +1,18 @@ +<p> + The record accessor syntax <code>record.field_label</code> can be used to get + contained values from a custom type record. +</p> +<p> + The accessor syntax can only be used for fields that are in the same position + and have the same type for all variants of the custom type. +</p> +<p> + The <code>name</code> field is in the first position and has type + <code>String</code> for all variants, so it can be accessed. +</p> +<p> + The <code>subject</code> field is absent on the <code>Student</code> variant, + so it cannot be used on any variant of type <code>SchoolPerson</code>. + Uncomment the <code>teacher.subject</code> line to see the compile error from + trying to use this accessor. +</p> diff --git a/src/content/chapter3_data_types/lesson04_record_updates/code.gleam b/src/content/chapter3_data_types/lesson04_record_updates/code.gleam new file mode 100644 index 0000000..ed7b45b --- /dev/null +++ b/src/content/chapter3_data_types/lesson04_record_updates/code.gleam @@ -0,0 +1,15 @@ +import gleam/io + +pub type SchoolPerson { + Teacher(name: String, subject: String, floor: Int, room: Int) +} + +pub fn main() { + let teacher1 = Teacher(name: "Mr Dodd", subject: "ICT", floor: 2, room: 2) + + // Use the update syntax + let teacher2 = Teacher(..teacher1, subject: "PE", room: 6) + + io.debug(teacher1) + io.debug(teacher2) +} diff --git a/src/content/chapter3_data_types/lesson04_record_updates/text.html b/src/content/chapter3_data_types/lesson04_record_updates/text.html new file mode 100644 index 0000000..f23d7cd --- /dev/null +++ b/src/content/chapter3_data_types/lesson04_record_updates/text.html @@ -0,0 +1,12 @@ +<p> + The record update syntax can be used to create a new record from an existing + one of the same type, but with some fields changed. +</p> +<p> + The accessor syntax can only be used for fields that are in the same position + and have the same type for all variants of the custom type. +</p> +<p> + Gleam is an immutable language, so using the record update syntax does not + mutate or otherwise change the original record. +</p> diff --git a/src/content/chapter3_data_types/lesson05_nil/code.gleam b/src/content/chapter3_data_types/lesson05_nil/code.gleam new file mode 100644 index 0000000..c28080b --- /dev/null +++ b/src/content/chapter3_data_types/lesson05_nil/code.gleam @@ -0,0 +1,11 @@ +import gleam/io + +pub fn main() { + let x = Nil + io.debug(x) + + // let y: List(String) = Nil + + let result = io.println("Hello!") + io.debug(result == Nil) +} diff --git a/src/content/chapter3_data_types/lesson05_nil/text.html b/src/content/chapter3_data_types/lesson05_nil/text.html new file mode 100644 index 0000000..3416643 --- /dev/null +++ b/src/content/chapter3_data_types/lesson05_nil/text.html @@ -0,0 +1,15 @@ +<p> + <code>Nil</code> is Gleam's unit type. It is a value that is returned by + functions that have nothing else to return, as all functions much return + something. +</p> +<p> + <code>Nil</code> is not a valid value of any other types, that is values in + Gleam are not nullable. If the type of a value is <code>Nil</code> then it is + the value <code>nil</code>. If it is some other type then the value is not + <code>Nil</code>. +</p> +<p> + Uncomment the line that assigns <code>Nil</code> to a variable with an + incompatible type annotation to see the comile time error it produces. +</p> diff --git a/src/content/chapter3_data_types/lesson06_bit_arrays/code.gleam b/src/content/chapter3_data_types/lesson06_bit_arrays/code.gleam new file mode 100644 index 0000000..dc772ca --- /dev/null +++ b/src/content/chapter3_data_types/lesson06_bit_arrays/code.gleam @@ -0,0 +1,13 @@ +import gleam/io + +pub fn main() { + // 8 bit int. In binary: 00000011 + io.debug(<<3>>) + io.debug(<<3>> == <<3:size(8)>>) + + // 16 bit int. In binary: 0001100000000011 + io.debug(<<6147:size(16)>>) + + // A bit array of UTF8 data + io.debug(<<"Hello, Joe!":utf8>>) +} diff --git a/src/content/chapter3_data_types/lesson06_bit_arrays/text.html b/src/content/chapter3_data_types/lesson06_bit_arrays/text.html new file mode 100644 index 0000000..3214db1 --- /dev/null +++ b/src/content/chapter3_data_types/lesson06_bit_arrays/text.html @@ -0,0 +1,26 @@ +<p> + Bit arrays represent a sequence of 1s and 0s, and are a convenient syntax for + constructing and manipulating binary data. +</p> +<p> + Each segment of a bit array can be given options to specify the representation + used for that segment. +</p> +<ul> + <li><code>size</code>: the size of the segment in bits.</li> + <li><code>unit</code>: how many times to repeat the segment.</li> + <li><code>bits</code>: a nested bit array of any size.</li> + <li><code>bytes</code>: a nested byte-aligned bit array.</li> + <li><code>float</code>: a 64 bits floating point number.</li> + <li><code>int</code>: an int with a default size of 8 bits.</li> + <li><code>big</code>: big endian.</li> + <li><code>little</code>: little endian.</li> + <li><code>native</code>: the endianness of the processor.</li> + <li><code>utf8</code>: utf8 encoded text.</li> + <li><code>utf16</code>: utf16 encoded text.</li> + <li><code>utf32</code>: utf32 encoded text.</li> +</ul> +<p> + Bit arrays have limited support when compiling to JavaScript, not all options + can be used. Full bit array support will be implemented in future. +</p> diff --git a/src/try_gleam.gleam b/src/try_gleam.gleam index 8c030e9..c788a71 100644 --- a/src/try_gleam.gleam +++ b/src/try_gleam.gleam @@ -3,7 +3,6 @@ import gleam/list import htmb.{h, text} import gleam/string_builder import gleam/option.{type Option, None, Some} -import gleam/pair import gleam/string import gleam/result import simplifile @@ -25,7 +24,7 @@ const stdlib_external = "build/packages/gleam_stdlib/src" const compiler_wasm = "../gleam/compiler-wasm/pkg" -const lessons_src = "lessons/src" +const content_path = "src/content" const hello_joe = "import gleam/io @@ -45,8 +44,8 @@ pub fn main() { use _ <- result.try(make_prelude_available()) use _ <- result.try(make_stdlib_available()) use _ <- result.try(copy_wasm_compiler()) - use p <- result.try(load_pages()) - use _ <- result.try(write_pages(p)) + use p <- result.try(load_content()) + use _ <- result.try(write_content(p)) Ok(Nil) } @@ -60,8 +59,12 @@ pub fn main() { } } -type Page { - Page( +type Chapter { + Chapter(name: String, path: String, lessons: List(Lesson)) +} + +type Lesson { + Lesson( name: String, text: String, code: String, @@ -71,118 +74,175 @@ type Page { ) } -fn load_pages() -> snag.Result(List(Page)) { - use lessons <- result.try( - simplifile.read_directory(lessons_src) - |> file_error("Failed to read lessons directory"), - ) +type FileNames { + FileNames(path: String, name: String, slug: String) +} - let lessons = - lessons - |> list.sort(by: string.compare) - |> list.index_map(pair.new) - - use pages <- result.try( - list.try_map(lessons, fn(pair) { - let #(lesson, index) = pair - let path = lessons_src <> "/" <> lesson - let name = - lesson - |> string.split("_") - |> list.drop(1) - |> string.join("-") +fn load_directory_names(path: String) -> snag.Result(List(FileNames)) { + use files <- result.map( + simplifile.read_directory(path) + |> file_error("Failed to read directory " <> path), + ) + files + |> list.sort(by: string.compare) + |> list.filter(fn(file) { !string.starts_with(file, ".") }) + |> list.map(fn(file) { + let path = path <> "/" <> file + let slug = + file + |> string.split("_") + |> list.drop(1) + |> string.join("-") + let name = + slug + |> string.replace("-", " ") + |> string.capitalise + FileNames(path: path, name: name, slug: slug) + }) +} - use code <- result.try( - simplifile.read(path <> "/code.gleam") - |> file_error("Failed to read code.gleam"), - ) +fn load_chapter(names: FileNames) -> snag.Result(Chapter) { + let path = "/" <> names.slug + use lessons <- result.try(load_directory_names(names.path)) + use lessons <- result.try(list.try_map(lessons, load_lesson(path, _))) + Ok(Chapter(name: names.name, path: path, lessons: lessons)) +} - use text <- result.try( - simplifile.read(path <> "/text.html") - |> file_error("Failed to read text.html"), - ) +fn read_file(path: String) -> snag.Result(String) { + simplifile.read(path) + |> file_error("Failed to read file " <> path) +} - let path = case index { - 0 -> "/" - _ -> "/" <> name - } - - Ok(Page( - name: name, - text: text, - code: code, - path: path, - previous: None, - next: None, - )) - }), - ) +fn load_lesson(chapter_path: String, names: FileNames) -> snag.Result(Lesson) { + use code <- result.try(read_file(names.path <> "/code.gleam")) + use text <- result.try(read_file(names.path <> "/text.html")) + + Ok(Lesson( + name: names.name, + text: text, + code: code, + path: chapter_path + <> "/" + <> names.slug, + previous: None, + next: None, + )) +} - Ok(add_previous_next(pages, [], None)) +fn load_content() -> snag.Result(List(Chapter)) { + use chapters <- result.try(load_directory_names(content_path)) + use chapters <- result.try(list.try_map(chapters, load_chapter)) + Ok(add_prev_next(chapters, [], Some("/"))) } -fn write_pages(pages: List(Page)) -> snag.Result(Nil) { - use _ <- result.try(list.try_each(pages, write_page)) +fn write_content(chapters: List(Chapter)) -> snag.Result(Nil) { + let lessons = list.flat_map(chapters, fn(c) { c.lessons }) + use _ <- result.try(list.try_map(lessons, write_lesson)) - let render = fn(h) { string_builder.to_string(htmb.render(h)) } - let html = - string.concat([ - render(h("h2", [], [text("Table of contents")])), - render(h( - "ul", - [], - list.map(pages, fn(page) { - h("li", [], [ - h("a", [#("href", page.path)], [ - page.name - |> string.replace("-", " ") - |> string.capitalise - |> text, - ]), - ]) - }), - )), - ]) - - let page = - Page( + let lesson = + Lesson( name: "Index", - text: html, + text: index_list_html(chapters), code: hello_joe, path: "/index", previous: None, next: None, ) - write_page(page) + write_lesson(lesson) } -fn write_page(page: Page) -> snag.Result(Nil) { - let path = public <> page.path +fn index_chapter_html(chapter: Chapter) -> String { + string.concat([ + render_html(h("h3", [#("class", "mb-0")], [text(chapter.name)])), + render_html(h( + "ul", + [], + list.map(chapter.lessons, fn(lesson) { + h("li", [], [ + h("a", [#("href", lesson.path)], [ + lesson.name + |> string.replace("-", " ") + |> string.capitalise + |> text, + ]), + ]) + }), + )), + ]) +} + +fn render_html(html: htmb.Html) -> String { + html + |> htmb.render + |> string_builder.to_string +} + +fn index_list_html(chapters: List(Chapter)) -> String { + h("h2", [], [text("Table of contents")]) + |> render_html + |> string.append( + chapters + |> list.map(index_chapter_html) + |> string.join("\n"), + ) +} + +fn write_lesson(lesson: Lesson) -> snag.Result(Nil) { + let path = public <> lesson.path use _ <- result.try( simplifile.create_directory_all(path) |> file_error("Failed to make " <> path), ) let path = path <> "/index.html" - simplifile.write(to: path, contents: page_html(page)) + simplifile.write(to: path, contents: lesson_html(lesson)) |> file_error("Failed to write page " <> path) } -fn add_previous_next( - rest: List(Page), - acc: List(Page), +fn add_prev_next( + rest: List(Chapter), + acc: List(Chapter), previous: Option(String), -) -> List(Page) { +) -> List(Chapter) { case rest { + [chapter1, Chapter(lessons: [next, ..], ..) as chapter2, ..rest] -> { + let lessons = chapter1.lessons + let #(lessons, previous) = + add_prev_next_for_chapter(lessons, [], previous, Some(next.path)) + let chapter1 = Chapter(..chapter1, lessons: lessons) + add_prev_next([chapter2, ..rest], [chapter1, ..acc], previous) + } + + [chapter, ..rest] -> { + let lessons = chapter.lessons + let #(lessons, previous) = + add_prev_next_for_chapter(lessons, [], previous, None) + let chapter = Chapter(..chapter, lessons: lessons) + add_prev_next(rest, [chapter, ..acc], previous) + } + [] -> list.reverse(acc) - [page, next, ..rest] -> { - let page = Page(..page, previous: previous, next: Some(next.path)) - add_previous_next([next, ..rest], [page, ..acc], Some(page.path)) + } +} + +fn add_prev_next_for_chapter( + rest: List(Lesson), + acc: List(Lesson), + previous: Option(String), + last: Option(String), +) -> #(List(Lesson), Option(String)) { + case rest { + [lesson1, lesson2, ..rest] -> { + let next = Some(lesson2.path) + let lesson = Lesson(..lesson1, previous: previous, next: next) + let rest = [lesson2, ..rest] + add_prev_next_for_chapter(rest, [lesson, ..acc], Some(lesson.path), last) } - [page, ..rest] -> { - let page = Page(..page, previous: previous, next: None) - add_previous_next(rest, [page, ..acc], Some(page.path)) + [lesson, ..rest] -> { + let lesson = Lesson(..lesson, previous: previous, next: last) + add_prev_next_for_chapter(rest, [lesson, ..acc], Some(lesson.path), last) } + [] -> #(list.reverse(acc), previous) } } @@ -353,7 +413,7 @@ fn file_error( } } -fn page_html(page: Page) -> String { +fn lesson_html(page: Lesson) -> String { let navlink = fn(name, link) { case link { None -> h("span", [], [text(name)]) |