From 29da9b51c0d918b86108bdbf0f4c6e914b240283 Mon Sep 17 00:00:00 2001 From: Louis Pilfold Date: Thu, 11 Jan 2024 18:25:46 +0000 Subject: Advanced features --- .../lesson00_todo/code.gleam | 7 ---- .../lesson00_todo/text.html | 14 -------- .../lesson00_use/code.gleam | 38 ++++++++++++++++++++++ .../lesson00_use/text.html | 31 ++++++++++++++++++ .../lesson01_panic/code.gleam | 15 --------- .../lesson01_panic/text.html | 11 ------- .../lesson01_use_continued/code.gleam | 30 +++++++++++++++++ .../lesson01_use_continued/text.html | 30 +++++++++++++++++ .../lesson02_todo/code.gleam | 7 ++++ .../lesson02_todo/text.html | 14 ++++++++ .../lesson03_panic/code.gleam | 15 +++++++++ .../lesson03_panic/text.html | 11 +++++++ .../lesson04_externals/code.gleam | 18 ++++++++++ .../lesson04_externals/text.html | 25 ++++++++++++++ .../lesson05_multi_target_externals/code.gleam | 11 +++++++ .../lesson05_multi_target_externals/text.html | 22 +++++++++++++ .../lesson06_external_gleam_fallbacks/code.gleam | 18 ++++++++++ .../lesson06_external_gleam_fallbacks/text.html | 13 ++++++++ 18 files changed, 283 insertions(+), 47 deletions(-) delete mode 100644 src/content/chapter5_advance_features/lesson00_todo/code.gleam delete mode 100644 src/content/chapter5_advance_features/lesson00_todo/text.html create mode 100644 src/content/chapter5_advance_features/lesson00_use/code.gleam create mode 100644 src/content/chapter5_advance_features/lesson00_use/text.html delete mode 100644 src/content/chapter5_advance_features/lesson01_panic/code.gleam delete mode 100644 src/content/chapter5_advance_features/lesson01_panic/text.html create mode 100644 src/content/chapter5_advance_features/lesson01_use_continued/code.gleam create mode 100644 src/content/chapter5_advance_features/lesson01_use_continued/text.html create mode 100644 src/content/chapter5_advance_features/lesson02_todo/code.gleam create mode 100644 src/content/chapter5_advance_features/lesson02_todo/text.html create mode 100644 src/content/chapter5_advance_features/lesson03_panic/code.gleam create mode 100644 src/content/chapter5_advance_features/lesson03_panic/text.html create mode 100644 src/content/chapter5_advance_features/lesson04_externals/code.gleam create mode 100644 src/content/chapter5_advance_features/lesson04_externals/text.html create mode 100644 src/content/chapter5_advance_features/lesson05_multi_target_externals/code.gleam create mode 100644 src/content/chapter5_advance_features/lesson05_multi_target_externals/text.html create mode 100644 src/content/chapter5_advance_features/lesson06_external_gleam_fallbacks/code.gleam create mode 100644 src/content/chapter5_advance_features/lesson06_external_gleam_fallbacks/text.html (limited to 'src/content/chapter5_advance_features') diff --git a/src/content/chapter5_advance_features/lesson00_todo/code.gleam b/src/content/chapter5_advance_features/lesson00_todo/code.gleam deleted file mode 100644 index d5abe8f..0000000 --- a/src/content/chapter5_advance_features/lesson00_todo/code.gleam +++ /dev/null @@ -1,7 +0,0 @@ -pub fn main() { - todo as "I haven't written this code yet!" -} - -pub fn todo_without_reason() { - todo -} diff --git a/src/content/chapter5_advance_features/lesson00_todo/text.html b/src/content/chapter5_advance_features/lesson00_todo/text.html deleted file mode 100644 index 4a2c433..0000000 --- a/src/content/chapter5_advance_features/lesson00_todo/text.html +++ /dev/null @@ -1,14 +0,0 @@ -

- The todo keyword is used to specify that some code is not yet - implemented. -

-

- The as "some string" is optional, though you may wish to include - the message if you have more than one code block marked as - todo in your code. -

-

- When used the Gleam compiler will print a warning to remind you the code is - unfinished, and if the code is run then the program will crash with the given - message. -

diff --git a/src/content/chapter5_advance_features/lesson00_use/code.gleam b/src/content/chapter5_advance_features/lesson00_use/code.gleam new file mode 100644 index 0000000..37624ab --- /dev/null +++ b/src/content/chapter5_advance_features/lesson00_use/code.gleam @@ -0,0 +1,38 @@ +import gleam/io +import gleam/result + +pub fn main() { + io.debug(without_use()) + io.debug(with_use()) +} + +pub fn without_use() { + result.try(get_usename(), fn(username) { + result.try(get_password(), fn(password) { + result.map(log_in(username, password), fn(greeting) { + greeting <> ", " <> username + }) + }) + }) +} + +pub fn with_use() { + use username <- result.try(get_usename()) + use password <- result.try(get_password()) + use greeting <- result.map(log_in(username, password)) + greeting <> ", " <> username +} + +// Here are some pretend functions for this example: + +fn get_usename() { + Ok("alice") +} + +fn get_password() { + Ok("hunter2") +} + +fn log_in(_username: String, _password: String) { + Ok("Welcome") +} diff --git a/src/content/chapter5_advance_features/lesson00_use/text.html b/src/content/chapter5_advance_features/lesson00_use/text.html new file mode 100644 index 0000000..e295dda --- /dev/null +++ b/src/content/chapter5_advance_features/lesson00_use/text.html @@ -0,0 +1,31 @@ +

+ Gleam lacks exceptions, macros, type classes, early returns, and a variety of + other features, instead going all-in with just first-class-functions and + pattern matching. This makes Gleam code easier to understand, but it can + sometimes result in excessive indentation. +

+

+ Gleam's use expression helps out here by enabling us to write code that uses + callbacks in an unindented style, as shown in the code window. +

+ +

+ The higher order function being called goes on the right hand side of the + <- operator. It must take a callback function as its final + argument. +

+

+ The argument names for the callback function go on the left hand side of the + <- operator. The function can take any number of arguments, + including zero. +

+

+ All the following code in the {} block becomes the + body of the callback function. +

+

+ This is a very capable and useful feature, but excessive application of + use may result in code that is unclear otherwise, especially to + beginners. Often using the regular function call syntax will result in more + approachable code! +

diff --git a/src/content/chapter5_advance_features/lesson01_panic/code.gleam b/src/content/chapter5_advance_features/lesson01_panic/code.gleam deleted file mode 100644 index fce9d66..0000000 --- a/src/content/chapter5_advance_features/lesson01_panic/code.gleam +++ /dev/null @@ -1,15 +0,0 @@ -import gleam/io - -pub fn main() { - print_score(10) - print_score(100_000) - print_score(-1) -} - -pub fn print_score(score: Int) { - case score { - score if score > 1000 -> io.println("High score!") - score if score > 0 -> io.println("Still working on it") - _ -> panic as "Scores should never be negative!" - } -} diff --git a/src/content/chapter5_advance_features/lesson01_panic/text.html b/src/content/chapter5_advance_features/lesson01_panic/text.html deleted file mode 100644 index 843a65b..0000000 --- a/src/content/chapter5_advance_features/lesson01_panic/text.html +++ /dev/null @@ -1,11 +0,0 @@ -

- The panic keyword is similar to todo keyword, but it - is used to crash the program when the program has reached a point that should - never be reached. -

-

- This keyword should almost never be used! It may be useful in initial - prototypes and scripts, but its use in a library or production application is - a sign that the design could be improved. With well designed types the type - system can typically be used to make these invalid states unrepresentable. -

diff --git a/src/content/chapter5_advance_features/lesson01_use_continued/code.gleam b/src/content/chapter5_advance_features/lesson01_use_continued/code.gleam new file mode 100644 index 0000000..ac61062 --- /dev/null +++ b/src/content/chapter5_advance_features/lesson01_use_continued/code.gleam @@ -0,0 +1,30 @@ +import gleam/io +import gleam/result + +pub fn main() { + let x = { + use username <- result.try(get_usename()) + use password <- result.try(get_password()) + use greeting <- result.map(log_in(username, password)) + greeting <> ", " <> username + } + + case x { + Ok(greeting) -> io.println(greeting) + Error(error) -> io.println_error(error) + } +} + +// Here are some pretend functions for this example: + +fn get_usename() { + Ok("alice") +} + +fn get_password() { + Ok("hunter2") +} + +fn log_in(_username: String, _password: String) { + Ok("Welcome") +} diff --git a/src/content/chapter5_advance_features/lesson01_use_continued/text.html b/src/content/chapter5_advance_features/lesson01_use_continued/text.html new file mode 100644 index 0000000..e28c843 --- /dev/null +++ b/src/content/chapter5_advance_features/lesson01_use_continued/text.html @@ -0,0 +1,30 @@ +

+ The use expression is syntactic sugar for a regular function call + and an anonymous function. +

+ +

This code:

+
+use a, b <- my_function
+next(a)
+next(b)
+
+ +

Expands into this code:

+
+my_function(fn(a, b) {
+  next(a)
+  next(b)
+})
+
+ +

+ To ensure that your use code works and is as understandable as + possible, the right-hand-side ideally should be a function call rather than a + pipeline or other expression, which is typically more difficult to read. +

+ +

+ use is an expression like everything else in Gleam, so it can be + placed within blocks. +

diff --git a/src/content/chapter5_advance_features/lesson02_todo/code.gleam b/src/content/chapter5_advance_features/lesson02_todo/code.gleam new file mode 100644 index 0000000..d5abe8f --- /dev/null +++ b/src/content/chapter5_advance_features/lesson02_todo/code.gleam @@ -0,0 +1,7 @@ +pub fn main() { + todo as "I haven't written this code yet!" +} + +pub fn todo_without_reason() { + todo +} diff --git a/src/content/chapter5_advance_features/lesson02_todo/text.html b/src/content/chapter5_advance_features/lesson02_todo/text.html new file mode 100644 index 0000000..4a2c433 --- /dev/null +++ b/src/content/chapter5_advance_features/lesson02_todo/text.html @@ -0,0 +1,14 @@ +

+ The todo keyword is used to specify that some code is not yet + implemented. +

+

+ The as "some string" is optional, though you may wish to include + the message if you have more than one code block marked as + todo in your code. +

+

+ When used the Gleam compiler will print a warning to remind you the code is + unfinished, and if the code is run then the program will crash with the given + message. +

diff --git a/src/content/chapter5_advance_features/lesson03_panic/code.gleam b/src/content/chapter5_advance_features/lesson03_panic/code.gleam new file mode 100644 index 0000000..fce9d66 --- /dev/null +++ b/src/content/chapter5_advance_features/lesson03_panic/code.gleam @@ -0,0 +1,15 @@ +import gleam/io + +pub fn main() { + print_score(10) + print_score(100_000) + print_score(-1) +} + +pub fn print_score(score: Int) { + case score { + score if score > 1000 -> io.println("High score!") + score if score > 0 -> io.println("Still working on it") + _ -> panic as "Scores should never be negative!" + } +} diff --git a/src/content/chapter5_advance_features/lesson03_panic/text.html b/src/content/chapter5_advance_features/lesson03_panic/text.html new file mode 100644 index 0000000..843a65b --- /dev/null +++ b/src/content/chapter5_advance_features/lesson03_panic/text.html @@ -0,0 +1,11 @@ +

+ The panic keyword is similar to todo keyword, but it + is used to crash the program when the program has reached a point that should + never be reached. +

+

+ This keyword should almost never be used! It may be useful in initial + prototypes and scripts, but its use in a library or production application is + a sign that the design could be improved. With well designed types the type + system can typically be used to make these invalid states unrepresentable. +

diff --git a/src/content/chapter5_advance_features/lesson04_externals/code.gleam b/src/content/chapter5_advance_features/lesson04_externals/code.gleam new file mode 100644 index 0000000..be4aff0 --- /dev/null +++ b/src/content/chapter5_advance_features/lesson04_externals/code.gleam @@ -0,0 +1,18 @@ +import gleam/io + +// A type with no Gleam constructors +pub type DateTime + +// An external function that creates an instance of the type +@external(javascript, "./my_package_ffi.mjs", "now") +pub fn now() -> DateTime + +// The `now` function in `./my_package_ffi.mjs` looks like this: +// external function now() { +// return new Date(); +// } + +pub fn main() { + io.debug(now()) +} + diff --git a/src/content/chapter5_advance_features/lesson04_externals/text.html b/src/content/chapter5_advance_features/lesson04_externals/text.html new file mode 100644 index 0000000..81202d6 --- /dev/null +++ b/src/content/chapter5_advance_features/lesson04_externals/text.html @@ -0,0 +1,25 @@ +

+ Sometimes in our projects we want to use code written in other languages, most + commonly Erlang and JavaScript, depending on which runtime is being used. + Gleam's external functions and external types allow us to + import and use this non-Gleam code. +

+

+ An external type is a one that has no constructors. Gleam doesn't know what + shape it has or how to create one, it only knows that it exists. +

+

+ An external function is one that has the @external attribute on + it, directing the compiler to use the specified module function as the + implementation, instead of Gleam code. +

+

+ The compiler can't tell the types of functions written in other languages, so + when the external attribute is given type annotations must be provided. Gleam + trusts that the type given is correct so an inaccurate type annotation can + result in unexpected behaviour and crashes at runtime. Be careful! +

+

+ External functions are useful but should be used sparingly. Prefer to write + Gleam code where possible. +

diff --git a/src/content/chapter5_advance_features/lesson05_multi_target_externals/code.gleam b/src/content/chapter5_advance_features/lesson05_multi_target_externals/code.gleam new file mode 100644 index 0000000..b62a735 --- /dev/null +++ b/src/content/chapter5_advance_features/lesson05_multi_target_externals/code.gleam @@ -0,0 +1,11 @@ +import gleam/io + +pub type DateTime + +@external(erlang, "calendar", "local_time") +@external(javascript, "./my_package_ffi.mjs", "now") +pub fn now() -> DateTime + +pub fn main() { + io.debug(now()) +} diff --git a/src/content/chapter5_advance_features/lesson05_multi_target_externals/text.html b/src/content/chapter5_advance_features/lesson05_multi_target_externals/text.html new file mode 100644 index 0000000..dc10a19 --- /dev/null +++ b/src/content/chapter5_advance_features/lesson05_multi_target_externals/text.html @@ -0,0 +1,22 @@ +

+ Multiple external implementations can be specified for the same function, + enabling the function to work on both Erlang and JavaScript. +

+

+ If a function doesn't have an implementation for the currently compiled-for + target then the compiler will return an error. +

+

+ You should try to implement functons for all targets, but this isn't always + possible due to incompatibilities in how IO and concurreny works in Erlang and + JavaScript. With Erlang concurrent IO is handled transparently by the runtime, + while in JavaScript concurrent IO requires the use of promises or callbacks. + If your code uses the Erlang style it is typically not possible to implement + in JavaScript, while if callbacks are used then it won't be compatible with + most Gleam and Erlang code as it forces any code that calls the function to + also use callbacks. +

+

+ Libraries that make use of concurrent IO will typically have to decide whether + they support Erlang or JavaScript, and document this in their README. +

diff --git a/src/content/chapter5_advance_features/lesson06_external_gleam_fallbacks/code.gleam b/src/content/chapter5_advance_features/lesson06_external_gleam_fallbacks/code.gleam new file mode 100644 index 0000000..a97b8fc --- /dev/null +++ b/src/content/chapter5_advance_features/lesson06_external_gleam_fallbacks/code.gleam @@ -0,0 +1,18 @@ +import gleam/io + +@external(erlang, "lists", "reverse") +pub fn reverse_list(items: List(e)) -> List(e) { + tail_recursive_reverse(items, []) +} + +fn tail_recursive_reverse(items: List(e), reversed: List(e)) -> List(e) { + case items { + [] -> reversed + [first, ..rest] -> tail_recursive_reverse(rest, [first, ..reversed]) + } +} + +pub fn main() { + io.debug(reverse_list([1, 2, 3, 4, 5])) + io.debug(reverse_list(["a", "b", "c", "d", "e"])) +} diff --git a/src/content/chapter5_advance_features/lesson06_external_gleam_fallbacks/text.html b/src/content/chapter5_advance_features/lesson06_external_gleam_fallbacks/text.html new file mode 100644 index 0000000..243c7ea --- /dev/null +++ b/src/content/chapter5_advance_features/lesson06_external_gleam_fallbacks/text.html @@ -0,0 +1,13 @@ +

+ It's possible for a function to have both a Gleam implementation and an + external implementation. If there exists an external implementation for the + currently compiled-for target then it will be used, otherwise the Gleam + implementation is used. +

+

+ This may be useful if you have a function that can be implemented in Gleam, + but there is an optimised implementation that can be used for one target. For + example, the Erlang virtual machine has a built-in list reverse function that + is implemented in native code. The code here uses this implementation when + running on Erlang, as it is then available. +

-- cgit v1.2.3