Compare commits

..

5 Commits

Author SHA1 Message Date
Alexander Heldt
7d1d94c31d secure-treasure-chest v2 2025-11-08 20:56:18 +01:00
Alexander Heldt
172b59c404 secure-treasure-chest 2025-11-08 20:54:55 +01:00
Alexander Heldt
589a3b886a newsletter 2025-11-08 20:40:41 +01:00
Alexander Heldt
760f5ef4a6 expert-experiments 2025-11-08 19:32:36 +01:00
Alexander Heldt
0d5036c5c4 erlang-extraction 2025-11-08 15:49:12 +01:00
40 changed files with 1399 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
{
"authors": [
"lpil"
],
"files": {
"solution": [
"src/erlang_extraction.gleam"
],
"test": [
"test/erlang_extraction_test.gleam"
],
"exemplar": [
".meta/example.gleam"
],
"invalidator": [
"gleam.toml",
"manifest.toml"
]
},
"blurb": "Learn about external types and functions by using an Erlang library"
}

View File

@@ -0,0 +1 @@
{"track":"gleam","exercise":"erlang-extraction","id":"567185dac6004b25b6b5bc1eb51faa91","url":"https://exercism.org/tracks/gleam/exercises/erlang-extraction","handle":"fw353qwgs","is_requester":true,"auto_approve":false}

4
erlang-extraction/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
*.beam
*.ez
build
erl_crash.dump

32
erlang-extraction/HELP.md Normal file
View File

@@ -0,0 +1,32 @@
# Help
## Running the tests
To run the tests, run the command `gleam test` from within the exercise directory.
## Submitting your solution
You can submit your solution using the `exercism submit src/erlang_extraction.gleam` command.
This command will upload your solution to the Exercism website and print the solution page's URL.
It's possible to submit an incomplete solution which allows you to:
- See how others have completed the exercise
- Request help from a mentor
## Need to get help?
If you'd like help solving the exercise, check the following pages:
- The [Gleam track's documentation](https://exercism.org/docs/tracks/gleam)
- The [Gleam track's programming category on the forum](https://forum.exercism.org/c/programming/gleam)
- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5)
- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
To get help if you're having trouble, you can use one of the following resources:
- [gleam.run](https://gleam.run/documentation/) is the gleam official documentation.
- [Discord](https://discord.gg/Fm8Pwmy) is the discord channel.
- [StackOverflow](https://stackoverflow.com/questions/tagged/gleam) can be used to search for your problem and see if it has been answered already. You can also ask and answer questions.

View File

@@ -0,0 +1,23 @@
# Hints
## 1. Define the `GbTree` external type
- External types can be defined using the `pub type TypeName(a, b, c)` syntax.
## 2. Define the `new_gb_tree` function
- External functions can be defined using the `@external(erlang, "module", "function")` syntax.
- The function should use the `gb_trees:empty/0`.
## 3. Define the `insert` function
- Check the `gb_trees:insert/3` documentation to confirm the correct order of arguments.
- The external function can be a private function that is called by the `insert` function in order to change the order of the arguments.
- The function should use the `gb_trees:insert/3`.
## 4. Define the `delete` function
- Check the `gb_trees:delete_any/2` documentation to confirm the correct order of arguments.
- The external function can be a private function that is called by the `delete` function in order to change the order of the arguments.
- The function should use the `gb_trees:delete_any/2`.
- The `gb_trees:delete/2` function will crash if the key is not found, so it should not be used.

108
erlang-extraction/README.md Normal file
View File

@@ -0,0 +1,108 @@
# Erlang Extraction
Welcome to Erlang Extraction on Exercism's Gleam Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
## Introduction
## External Functions
Gleam can run on the Erlang virtual machine (BEAM), or on JavaScript runtimes. There are many other languages that use these runtimes, and it is often useful to be able to call code written in these languages from Gleam.
Gleam's _external functions_ feature permits functions in other languages to be imported into Gleam and called with no runtime overhead.
If your Gleam project runs on the Erlang virtual machine and you wish to call the `reverse` function from the Erlang `lists` module you can do it by adding the `@external` attribute to a Gleam function head like this:
```gleam
@external(erlang, "lists", "reverse")
pub fn reverse_list(x: List(a)) -> List(a)
```
This can then be called as a normal Gleam function:
```gleam
let reversed = reverse_list([1, 2, 3])
// -> [3, 2, 1]
```
If you attempt to compile this code for JavaScript runtimes it will fail with an error message as there is no implementation for JavaScript. Another implementation can be specified for JavaScript runtimes like this:
```gleam
@external(erlang, "lists", "reverse")
@external(javascript, "./my_module.mjs", "reverse")
pub fn reverse_list(x: List(a)) -> List(a)
```
It is also possible to write a Gleam implementation that will be used when there is no external implementation for the current compile target:
```gleam
@external(erlang, "lists", "reverse")
pub fn reverse_list(x: List(a)) -> List(a) {
tail_recursive_reverse(x, [])
}
fn tail_recursive_reverse(list, reversed) {
case list {
[] -> reversed
[x, ..xs] -> tail_recursive_reverse(xs, [x, ..reversed])
}
}
```
## External Types
External types can be used to refer to data types defined in other languages, such as Erlang or JavaScript.
To define an external type declare a type but do not provide any constructors. This can then be used in the same way as any other type.
```gleam
pub type OrderedDictionary(element)
```
## Instructions
Tadhg has found the perfect Erlang library to help with his project. Being an older Erlang library it is using the `gb_trees` module rather than the newer `maps` module for storing data.
Help out Tadgh by creating external types and functions for working with `gb_trees` in Gleam.
## 1. Define the `GbTree` external type
The `GbTree` type should have two type parameters, a key type and a value type. It should have no constructors, making it an external type.
## 2. Define the `new_gb_tree` function
The `new_gb_tree` function should take no arguments and return an empty `GbTree`.
It should use the [`gb_trees:empty/0` function][empty] from the Erlang standard library.
## 3. Define the `insert` function
The `insert` function should take a `GbTree` and a key and value to insert into the tree. It should return a new `GbTree` with the key and value inserted.
The function should take three arguments:
1. The `GbTree` to insert into.
2. The key to insert.
3. The value to insert.
It should use the [`gb_trees:insert/3` function][insert] from the Erlang standard library.
## 4. Define the `delete` function
The `delete` function should take a `GbTree` and a key to delete from the tree. It should return a new `GbTree` with the key and value deleted.
The function should take two arguments:
1. The `GbTree` to delete from.
2. The key to delete.
It should use the [`gb_trees:delete_any/2` function][delete_any] from the Erlang standard library.
[empty]: https://www.erlang.org/doc/apps/stdlib/gb_trees.html#empty/0
[insert]: https://www.erlang.org/doc/apps/stdlib/gb_trees.html#insert/3
[delete_any]: https://www.erlang.org/doc/apps/stdlib/gb_trees.html#delete_any/2
## Source
### Created by
- @lpil

View File

@@ -0,0 +1,14 @@
name = "erlang_extraction"
version = "0.1.0"
[dependencies]
gleam_otp = "~> 0.7 or ~> 1.0"
gleam_stdlib = ">= 0.54.0 or ~> 1.0"
simplifile = "~> 1.0"
gleam_erlang = ">= 0.25.0 and < 1.0.0"
gleam_yielder = ">= 1.1.0 and < 2.0.0"
gleam_regexp = ">= 1.1.0 and < 2.0.0"
gleam_deque = ">= 1.0.0 and < 2.0.0"
[dev-dependencies]
exercism_test_runner = "~> 1.9"

View File

@@ -0,0 +1,31 @@
# This file was generated by Gleam
# You typically do not need to edit this file
packages = [
{ name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" },
{ name = "exercism_test_runner", version = "1.9.0", build_tools = ["gleam"], requirements = ["argv", "gap", "glance", "gleam_community_ansi", "gleam_erlang", "gleam_json", "gleam_stdlib", "simplifile"], otp_app = "exercism_test_runner", source = "hex", outer_checksum = "0B17BB25F2FF1E60266467C24FE0CA04005410306AA05E9A4B41B1852D72865C" },
{ name = "filepath", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "67A6D15FB39EEB69DD31F8C145BB5A421790581BD6AA14B33D64D5A55DBD6587" },
{ name = "gap", version = "1.1.3", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_stdlib"], otp_app = "gap", source = "hex", outer_checksum = "6EF5E3B523FDFBC317E9EA28D5163EE04744A97C007106F90207569789612291" },
{ name = "glance", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "glexer"], otp_app = "glance", source = "hex", outer_checksum = "E155BA1A787FD11827048355021C0390D2FE9A518485526F631A9D472858CC6D" },
{ name = "gleam_community_ansi", version = "1.4.3", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_regexp", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "8A62AE9CC6EA65BEA630D95016D6C07E4F9973565FA3D0DE68DC4200D8E0DD27" },
{ name = "gleam_community_colour", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "FDD6AC62C6EC8506C005949A4FCEF032038191D5EAAEC3C9A203CD53AE956ACA" },
{ name = "gleam_deque", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_deque", source = "hex", outer_checksum = "64D77068931338CF0D0CB5D37522C3E3CCA7CB7D6C5BACB41648B519CC0133C7" },
{ name = "gleam_erlang", version = "0.34.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "0C38F2A128BAA0CEF17C3000BD2097EB80634E239CE31A86400C4416A5D0FDCC" },
{ name = "gleam_json", version = "2.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "C55C5C2B318533A8072D221C5E06E5A75711C129E420DD1CE463342106012E5D" },
{ name = "gleam_otp", version = "0.16.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "50DA1539FC8E8FA09924EB36A67A2BBB0AD6B27BCDED5A7EF627057CF69D035E" },
{ name = "gleam_regexp", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "7F5E0C0BBEB3C58E57C9CB05FA9002F970C85AD4A63BA1E55CBCB35C15809179" },
{ name = "gleam_stdlib", version = "0.55.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "32D8F4AE03771516950047813A9E359249BD9FBA5C33463FDB7B953D6F8E896B" },
{ name = "gleam_yielder", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_yielder", source = "hex", outer_checksum = "8E4E4ECFA7982859F430C57F549200C7749823C106759F4A19A78AEA6687717A" },
{ name = "glexer", version = "2.2.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glexer", source = "hex", outer_checksum = "5C235CBDF4DA5203AD5EAB1D6D8B456ED8162C5424FE2309CFFB7EF438B7C269" },
{ name = "simplifile", version = "1.7.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "1D5DFA3A2F9319EC85825F6ED88B8E449F381B0D55A62F5E61424E748E7DDEB0" },
]
[requirements]
exercism_test_runner = { version = "~> 1.9" }
gleam_deque = { version = ">= 1.0.0 and < 2.0.0" }
gleam_erlang = { version = ">= 0.25.0 and < 1.0.0" }
gleam_otp = { version = "~> 0.7 or ~> 1.0" }
gleam_regexp = { version = ">= 1.1.0 and < 2.0.0" }
gleam_stdlib = { version = ">= 0.54.0 or ~> 1.0" }
gleam_yielder = { version = ">= 1.1.0 and < 2.0.0" }
simplifile = { version = "~> 1.0" }

View File

@@ -0,0 +1,20 @@
// Please define the GbTree type
pub type GbTree(k, v)
@external(erlang, "gb_trees", "empty")
pub fn new_gb_tree() -> GbTree(k, v)
pub fn insert(tree: GbTree(k, v), key: k, value: v) -> GbTree(k, v) {
gb_trees_insert(key, value, tree)
}
@external(erlang, "gb_trees", "insert")
fn gb_trees_insert(key: k, value: v, tree: GbTree(k, v)) -> GbTree(k, v)
pub fn delete(tree: GbTree(k, v), key: k) -> GbTree(k, v) {
gb_trees_delete_any(key, tree)
}
@external(erlang, "gb_trees", "delete_any")
fn gb_trees_delete_any(key: k, tree: GbTree(k, v)) -> GbTree(k, v)

View File

@@ -0,0 +1,54 @@
import erlang_extraction.{type GbTree}
import exercism/should
import exercism/test_runner
pub fn main() {
test_runner.main()
}
@external(erlang, "gb_trees", "to_list")
fn to_list(tree: GbTree(k, v)) -> List(#(k, v))
pub fn new_gb_tree_test() {
erlang_extraction.new_gb_tree()
|> to_list
|> should.equal([])
}
pub fn insert_int_string_test() {
erlang_extraction.new_gb_tree()
|> erlang_extraction.insert(1, "one")
|> erlang_extraction.insert(2, "two")
|> erlang_extraction.insert(3, "three")
|> to_list
|> should.equal([#(1, "one"), #(2, "two"), #(3, "three")])
}
pub fn insert_string_int_test() {
erlang_extraction.new_gb_tree()
|> erlang_extraction.insert("one", 1)
|> erlang_extraction.insert("two", 2)
|> erlang_extraction.insert("three", 3)
|> to_list
|> should.equal([#("one", 1), #("three", 3), #("two", 2)])
}
pub fn delete_test() {
erlang_extraction.new_gb_tree()
|> erlang_extraction.insert(1, "one")
|> erlang_extraction.insert(2, "two")
|> erlang_extraction.insert(3, "three")
|> erlang_extraction.delete(2)
|> to_list
|> should.equal([#(1, "one"), #(3, "three")])
}
pub fn delete_non_existing_test() {
erlang_extraction.new_gb_tree()
|> erlang_extraction.insert(1, "one")
|> erlang_extraction.insert(2, "two")
|> erlang_extraction.insert(3, "three")
|> erlang_extraction.delete(4)
|> to_list
|> should.equal([#(1, "one"), #(2, "two"), #(3, "three")])
}

View File

@@ -0,0 +1,21 @@
{
"authors": [
"lpil"
],
"files": {
"solution": [
"src/expert_experiments.gleam"
],
"test": [
"test/expert_experiments_test.gleam"
],
"exemplar": [
".meta/example.gleam"
],
"invalidator": [
"gleam.toml",
"manifest.toml"
]
},
"blurb": "Learn about use expressions by conducting some experiments"
}

View File

@@ -0,0 +1 @@
{"track":"gleam","exercise":"expert-experiments","id":"4f12bf49f75c4ebaba7dc8a949bd874b","url":"https://exercism.org/tracks/gleam/exercises/expert-experiments","handle":"fw353qwgs","is_requester":true,"auto_approve":false}

4
expert-experiments/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
*.beam
*.ez
build
erl_crash.dump

View File

@@ -0,0 +1,32 @@
# Help
## Running the tests
To run the tests, run the command `gleam test` from within the exercise directory.
## Submitting your solution
You can submit your solution using the `exercism submit src/expert_experiments.gleam` command.
This command will upload your solution to the Exercism website and print the solution page's URL.
It's possible to submit an incomplete solution which allows you to:
- See how others have completed the exercise
- Request help from a mentor
## Need to get help?
If you'd like help solving the exercise, check the following pages:
- The [Gleam track's documentation](https://exercism.org/docs/tracks/gleam)
- The [Gleam track's programming category on the forum](https://forum.exercism.org/c/programming/gleam)
- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5)
- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
To get help if you're having trouble, you can use one of the following resources:
- [gleam.run](https://gleam.run/documentation/) is the gleam official documentation.
- [Discord](https://discord.gg/Fm8Pwmy) is the discord channel.
- [StackOverflow](https://stackoverflow.com/questions/tagged/gleam) can be used to search for your problem and see if it has been answered already. You can also ask and answer questions.

View File

@@ -0,0 +1,15 @@
# Hints
## 1. Define the `with_retry` function
- A `case` expression can be used to pattern match on a result.
## 2. Define the `record_timing` function
- The `time_logger` function should be called even if the `experiment` function returns an `Error` value.
## 3. Define the `run_experiment` function
- The [`result.try` function][result-try] can be used in a `use` expression to stop if a result is an `Error` value.
[result-try]: https://hexdocs.pm/gleam_stdlib/gleam/result.html#try

View File

@@ -0,0 +1,149 @@
# Expert Experiments
Welcome to Expert Experiments on Exercism's Gleam Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
## Introduction
## Use Expressions
In Gleam it is common to write and use higher order functions, that is functions that take other functions as arguments. Sometimes when using many higher order functions at once the code can become difficult to read, with many layers of indentation.
For example, here is a function that calls several functions that return `Result(Int, Nil)`, and sums the values if all four are successful.
```gleam
import gleam/result
pub fn main() -> Result(Int, Nil) {
result.try(function1(), fn(a) {
result.try(function2(), fn(b) {
result.try(function3(), fn(c) {
result.try(function4(), fn(d) {
Ok(a + b + c + d)
})
})
})
})
}
```
Gleam's `use` expressions allow us to write this code without the indentation, often making it easier to read.
```gleam
import gleam/result
pub fn main() -> Result(Int, Nil) {
use a <- result.try(function1())
use b <- result.try(function2())
use c <- result.try(function3())
use d <- result.try(function4())
Ok(a + b + c + d)
}
```
A `use` expression collects all the following statements in the block and passes it as a callback function as the final argument to the function call. The variables between the `use` keyword and the `<-` symbol are the names of the arguments that will be passed to the callback function.
```gleam
// This use expression
use a <- function(1, 2)
io.println("Hello!")
a
// Is equivalent to this normal function call
function(1, 2, fn(a) {
io.println("Hello!")
a
})
```
The callback function can take any number of arguments, or none at all.
```gleam
use a, b, c, d <- call_4_function()
use <- call_0_function()
```
There are no special requirements to create a function that can be called with a `use` expression, other than taking a callback function as the final argument.
```gleam
pub fn call_twice(function: fn() -> t) -> #(t, t) {
let first = function()
let second = function()
#(first, second)
}
```
Gleam's `use` expressions are a very powerful feature that can be applied to lots of problems, but when overused they can make code difficult to read. It is generally preferred to use the normal function call syntax and only reach for `use` expressions when they make the code easier to read.
## Instructions
Daphne has been working on a system to run and record the results of her experiments. Some of the code has become a bit verbose and repetitive, so she's asked you to write some `use` expressions to help clean it up.
## 1. Define the `with_retry` function
Sometimes experiments can fail due to a one-off mistake, so if an experiment fails Daphne wants to retry it again to see if it works the second time.
Define the `with_retry` function that takes a result returning function as an argument.
If the function returns an `Ok` value then `with_retry` should return that value.
If the function returns an `Error` value then `with_retry` should call the function again and return the result of that call.
Daphne will use the function like this:
```gleam
pub fn main() {
use <- with_retry
// Perform the experiment here
}
```
## 2. Define the `record_timing` function
Daphne records how long each experiment takes to run by calling a time logging function before and after each experiment.
Define the `record_timing` function that takes two arguments:
- A time logging function which takes no arguments and returns `Nil`.
- An experiment function which takes no arguments and returns a result.
`record_timing` should call the time logging function, then call the experiment function, then call the time logging function again, and finally return the result of the experiment function.
Daphne will use the function like this:
```gleam
pub fn main() {
use <- record_timing(time_logger)
// Perform the experiment here
}
```
## 3. Define the `run_experiment` function
Experiments are made up of three phases. The setup, the action, and the recording. All three phases return results, and each phase needs the successful result of the previous phase to run.
Define the `run_experiment` function that takes four arguments:
- The name of the experiment as a `String`.
- A setup function which takes no arguments and returns a result.
- An action function which takes the `Ok` value of the setup function as an argument and returns a result.
- A recording function which takes the `Ok` value of the setup and action functions as arguments and returns a result.
If all three functions succeed then `run_experiment` should return `Ok(#(experiment_name, recording_data))`.
If any of the functions return an `Error` value then `run_experiment` should return that value.
Daphne will use the function like this:
```gleam
pub fn main() {
use setup_data, action_data <- run_experiment("Test 1", setup, action)
// Record the results here
}
```
## Source
### Created by
- @lpil

View File

@@ -0,0 +1,14 @@
name = "expert_experiments"
version = "0.1.0"
[dependencies]
gleam_otp = "~> 0.7 or ~> 1.0"
gleam_stdlib = ">= 0.54.0 or ~> 1.0"
simplifile = "~> 1.0"
gleam_erlang = ">= 0.25.0 and < 1.0.0"
gleam_yielder = ">= 1.1.0 and < 2.0.0"
gleam_regexp = ">= 1.1.0 and < 2.0.0"
gleam_deque = ">= 1.0.0 and < 2.0.0"
[dev-dependencies]
exercism_test_runner = "~> 1.9"

View File

@@ -0,0 +1,31 @@
# This file was generated by Gleam
# You typically do not need to edit this file
packages = [
{ name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" },
{ name = "exercism_test_runner", version = "1.9.0", build_tools = ["gleam"], requirements = ["argv", "gap", "glance", "gleam_community_ansi", "gleam_erlang", "gleam_json", "gleam_stdlib", "simplifile"], otp_app = "exercism_test_runner", source = "hex", outer_checksum = "0B17BB25F2FF1E60266467C24FE0CA04005410306AA05E9A4B41B1852D72865C" },
{ name = "filepath", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "67A6D15FB39EEB69DD31F8C145BB5A421790581BD6AA14B33D64D5A55DBD6587" },
{ name = "gap", version = "1.1.3", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_stdlib"], otp_app = "gap", source = "hex", outer_checksum = "6EF5E3B523FDFBC317E9EA28D5163EE04744A97C007106F90207569789612291" },
{ name = "glance", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "glexer"], otp_app = "glance", source = "hex", outer_checksum = "E155BA1A787FD11827048355021C0390D2FE9A518485526F631A9D472858CC6D" },
{ name = "gleam_community_ansi", version = "1.4.3", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_regexp", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "8A62AE9CC6EA65BEA630D95016D6C07E4F9973565FA3D0DE68DC4200D8E0DD27" },
{ name = "gleam_community_colour", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "FDD6AC62C6EC8506C005949A4FCEF032038191D5EAAEC3C9A203CD53AE956ACA" },
{ name = "gleam_deque", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_deque", source = "hex", outer_checksum = "64D77068931338CF0D0CB5D37522C3E3CCA7CB7D6C5BACB41648B519CC0133C7" },
{ name = "gleam_erlang", version = "0.34.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "0C38F2A128BAA0CEF17C3000BD2097EB80634E239CE31A86400C4416A5D0FDCC" },
{ name = "gleam_json", version = "2.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "C55C5C2B318533A8072D221C5E06E5A75711C129E420DD1CE463342106012E5D" },
{ name = "gleam_otp", version = "0.16.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "50DA1539FC8E8FA09924EB36A67A2BBB0AD6B27BCDED5A7EF627057CF69D035E" },
{ name = "gleam_regexp", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "7F5E0C0BBEB3C58E57C9CB05FA9002F970C85AD4A63BA1E55CBCB35C15809179" },
{ name = "gleam_stdlib", version = "0.55.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "32D8F4AE03771516950047813A9E359249BD9FBA5C33463FDB7B953D6F8E896B" },
{ name = "gleam_yielder", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_yielder", source = "hex", outer_checksum = "8E4E4ECFA7982859F430C57F549200C7749823C106759F4A19A78AEA6687717A" },
{ name = "glexer", version = "2.2.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glexer", source = "hex", outer_checksum = "5C235CBDF4DA5203AD5EAB1D6D8B456ED8162C5424FE2309CFFB7EF438B7C269" },
{ name = "simplifile", version = "1.7.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "1D5DFA3A2F9319EC85825F6ED88B8E449F381B0D55A62F5E61424E748E7DDEB0" },
]
[requirements]
exercism_test_runner = { version = "~> 1.9" }
gleam_deque = { version = ">= 1.0.0 and < 2.0.0" }
gleam_erlang = { version = ">= 0.25.0 and < 1.0.0" }
gleam_otp = { version = "~> 0.7 or ~> 1.0" }
gleam_regexp = { version = ">= 1.1.0 and < 2.0.0" }
gleam_stdlib = { version = ">= 0.54.0 or ~> 1.0" }
gleam_yielder = { version = ">= 1.1.0 and < 2.0.0" }
simplifile = { version = "~> 1.0" }

View File

@@ -0,0 +1,31 @@
import gleam/result
pub fn with_retry(experiment: fn() -> Result(t, e)) -> Result(t, e) {
case experiment() {
Error(_) -> experiment()
Ok(result) -> Ok(result)
}
}
pub fn record_timing(
time_logger: fn() -> Nil,
experiment: fn() -> Result(t, e),
) -> Result(t, e) {
time_logger()
let result = experiment()
time_logger()
result
}
pub fn run_experiment(
name: String,
setup: fn() -> Result(t, e),
action: fn(t) -> Result(u, e),
record: fn(t, u) -> Result(v, e),
) -> Result(#(String, v), e) {
use setup_result <- result.try(setup())
use action_result <- result.try(action(setup_result))
use record_result <- result.try(record(setup_result, action_result))
Ok(#(name, record_result))
}

View File

@@ -0,0 +1,143 @@
import exercism/should
import exercism/test_runner
import expert_experiments
import gleam/erlang/process
import gleam/list
pub fn main() {
test_runner.main()
}
fn mutable_yielder(elements: List(t)) -> fn() -> t {
let subject = process.new_subject()
list.each(elements, fn(element) { process.send(subject, element) })
fn() {
case process.receive(subject, 0) {
Ok(element) -> element
Error(_) -> panic as "Callback called too many times"
}
}
}
fn logger() -> #(fn(String) -> Nil, fn() -> List(String)) {
let subject = process.new_subject()
let writer = fn(message) { process.send(subject, message) }
let reader = fn() { read_all(subject, []) }
#(writer, reader)
}
fn read_all(
subject: process.Subject(String),
read: List(String),
) -> List(String) {
case process.receive(subject, 0) {
Ok(message) -> read_all(subject, [message, ..read])
Error(_) -> list.reverse(read)
}
}
pub fn with_retry_pass_test() {
let function = mutable_yielder([Ok("First"), Error("Second")])
{
use <- expert_experiments.with_retry
function()
}
|> should.equal(Ok("First"))
}
pub fn with_retry_fail_fail_test() {
let function = mutable_yielder([Error("First"), Error("Second")])
{
use <- expert_experiments.with_retry
function()
}
|> should.equal(Error("Second"))
}
pub fn with_retry_fail_pass_test() {
let function = mutable_yielder([Error("First"), Ok("Second")])
{
use <- expert_experiments.with_retry
function()
}
|> should.equal(Ok("Second"))
}
pub fn record_timing_pass_test() {
let #(writer, reader) = logger()
{
use <- expert_experiments.record_timing(fn() { writer("timer") })
writer("experiment")
Ok(0)
}
|> should.equal(Ok(0))
reader()
|> should.equal(["timer", "experiment", "timer"])
}
pub fn record_timing_fail_test() {
let #(writer, reader) = logger()
{
use <- expert_experiments.record_timing(fn() { writer("timer") })
writer("experiment")
Error(Nil)
}
|> should.equal(Error(Nil))
reader()
|> should.equal(["timer", "experiment", "timer"])
}
pub fn run_experiment_fail_test() {
{
let setup = fn() { Error("Setup failed") }
let action = fn(_) { panic as "Should not run action" }
use _, _ <- expert_experiments.run_experiment("Experiment 1", setup, action)
panic as "Should not run record"
}
|> should.equal(Error("Setup failed"))
}
pub fn run_experiment_pass_fail_test() {
{
let setup = fn() { Ok(1) }
let action = fn(x) {
should.equal(x, 1)
Error("Action failed")
}
use _, _ <- expert_experiments.run_experiment("Experiment 1", setup, action)
panic as "Should not run record"
}
|> should.equal(Error("Action failed"))
}
pub fn run_experiment_pass_pass_fail_test() {
{
let setup = fn() { Ok(1) }
let action = fn(x) {
should.equal(x, 1)
Ok("2")
}
use x, y <- expert_experiments.run_experiment("Experiment 1", setup, action)
should.equal(x, 1)
should.equal(y, "2")
Error("Record failed")
}
|> should.equal(Error("Record failed"))
}
pub fn run_experiment_pass_pass_pass_test() {
{
let setup = fn() { Ok(1) }
let action = fn(x) {
should.equal(x, 1)
Ok("2")
}
use x, y <- expert_experiments.run_experiment("Experiment 1", setup, action)
should.equal(x, 1)
should.equal(y, "2")
Ok("Success!")
}
|> should.equal(Ok(#("Experiment 1", "Success!")))
}

View File

@@ -0,0 +1,24 @@
{
"authors": [
"lpil"
],
"files": {
"solution": [
"src/newsletter.gleam"
],
"test": [
"test/newsletter_test.gleam"
],
"exemplar": [
".meta/example.gleam"
],
"invalidator": [
"gleam.toml",
"manifest.toml"
]
},
"forked_from": [
"elixir/newsletter"
],
"blurb": "Learn about IO by sending out a newsletter."
}

View File

@@ -0,0 +1 @@
{"track":"gleam","exercise":"newsletter","id":"ec3e4622cebd4906aff5b7b7f8a3e90b","url":"https://exercism.org/tracks/gleam/exercises/newsletter","handle":"fw353qwgs","is_requester":true,"auto_approve":false}

6
newsletter/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
*.beam
*.ez
build
erl_crash.dump
log.txt
emails.txt

32
newsletter/HELP.md Normal file
View File

@@ -0,0 +1,32 @@
# Help
## Running the tests
To run the tests, run the command `gleam test` from within the exercise directory.
## Submitting your solution
You can submit your solution using the `exercism submit src/newsletter.gleam` command.
This command will upload your solution to the Exercism website and print the solution page's URL.
It's possible to submit an incomplete solution which allows you to:
- See how others have completed the exercise
- Request help from a mentor
## Need to get help?
If you'd like help solving the exercise, check the following pages:
- The [Gleam track's documentation](https://exercism.org/docs/tracks/gleam)
- The [Gleam track's programming category on the forum](https://forum.exercism.org/c/programming/gleam)
- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5)
- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
To get help if you're having trouble, you can use one of the following resources:
- [gleam.run](https://gleam.run/documentation/) is the gleam official documentation.
- [Discord](https://discord.gg/Fm8Pwmy) is the discord channel.
- [StackOverflow](https://stackoverflow.com/questions/tagged/gleam) can be used to search for your problem and see if it has been answered already. You can also ask and answer questions.

24
newsletter/HINTS.md Normal file
View File

@@ -0,0 +1,24 @@
# Hints
## 1. Read email addresses from a file
- The [`simplifile.read` function][file-read] can be used to read the contents of a file.
## 2. Create a log file for writing
- The [`simplifile.create_file` function][file-create] can be used to create an empty file.
## 3. Log a sent email
- The [`simplifile.append` function][file-append] can be used to append text to a file.
## 5. Send the newsletter
- All the necessary operations on files were already implemented in the previous steps.
- The [`result.try` function][result-try] and [`list.try_each` function][list-try-each] can be used to work with the `Result` type.
[file-read]: https://hexdocs.pm/simplifile/simplifile.html#read
[file-create]: https://hexdocs.pm/simplifile/simplifile.html#create_file
[file-append]: https://hexdocs.pm/simplifile/simplifile.html#append
[result-try]: https://hexdocs.pm/gleam_stdlib/gleam/result.html#try
[list-try-each]: https://hexdocs.pm/gleam_stdlib/gleam/list.html#try_each

98
newsletter/README.md Normal file
View File

@@ -0,0 +1,98 @@
# Newsletter
Welcome to Newsletter on Exercism's Gleam Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
## Introduction
## Nil
`Nil` in Gleam is a type with a single value, also called `Nil`. It is similar to `void` in other languages in that it is used when a function does not have any more suitable value to return.
```gleam
io.println("Hello, Joe!")
// -> Nil
```
Values in Gleam are not "nil-able" or "nullable" like in some other languages. A value can only be `Nil` if it's type is `Nil`, and a value of any other type can never be `Nil`.
## IO
Like most programming languages Gleam has "side effects", so functions can read and change the state of the world, as well as returning a value.
The `gleam/io` module in the Gleam standard library provides functions for printing strings to the console.
```gleam
io.println("Hello, Joe!")
// Hello, Joe!
// -> Nil
```
Other packages may provide other IO functions, such as `simplifile`, a package which provides functions for reading and writing files.
```gleam
simplifile.read("favourite-colour.txt")
// -> Ok("Pink\n")
```
## Instructions
Boris is a big model train enthusiast and has decided to share their passion with the world by starting a newsletter. They'll start by sending the first issue of the newsletter to friends and acquaintances that share the hobby, their email addresses are stored in a text file.
Hint: Use the [simplifile](https://hexdocs.pm/simplifile/simplifile.html) module for file operations.
## 1. Read email addresses from a file
Implement the `read_emails` function. It takes a path string to a text file that contains email addresses separated by newlines, and returns a list of the email addresses from the file.
```gleam
read_emails("/home/my_user/documents/model_train_friends_emails.txt")
// -> Ok(["rick@example.com", "choochoo42@example.com", "anna@example.com"])
```
## 2. Create a log file for writing
Sending an email is a task that might fail for many unpredictable reasons, like a typo in the email address or temporary network issues. To ensure that you can retry sending the emails to all your friends without sending duplicates, you need to log the email addresses that already received the email. For this, you'll need a log file.
Implement the `create_log_file` function. It takes a file path and creates a new empty file at that location.
```gleam
create_log_file("/home/my_user/documents/newsletter_issue1_log.txt")
// -> Ok(Nil)
```
## 3. Log a sent email
Implement the `log_sent_email` function. It takes a path to a log file and a string with the email address. It writes the email address to the file, followed by a newline.
```gleam
log_sent_email(
"/home/my_user/documents/newsletter_issue1_log.txt",
"joe@example.com",
)
// -> Ok(Nil)
```
## 4. Send the newsletter
Now that you have all of the building blocks of the email sending procedure, you need to combine them together in a single function.
Implement the `send_newsletter` function. It takes a path of the file with email addresses, a path of a log file, and an anonymous function that sends an email to a given email address.
It should read all the email addresses from the given file and attempt to send an email to every one of them. If the anonymous function that sends the email returns an `Ok` value, write the email address to the log file. Make sure to do it as soon as the email is sent.
```gleam
send_newsletter(
"model_train_friends_emails.txt",
"newsletter_issue1_log.txt",
send_email,
)
// -> Ok(Nil)
```
## Source
### Created by
- @lpil

14
newsletter/gleam.toml Normal file
View File

@@ -0,0 +1,14 @@
name = "newsletter"
version = "0.1.0"
[dependencies]
gleam_otp = "~> 0.7 or ~> 1.0"
gleam_stdlib = ">= 0.54.0 or ~> 1.0"
simplifile = "~> 1.0"
gleam_erlang = ">= 0.25.0 and < 1.0.0"
gleam_yielder = ">= 1.1.0 and < 2.0.0"
gleam_regexp = ">= 1.1.0 and < 2.0.0"
gleam_deque = ">= 1.0.0 and < 2.0.0"
[dev-dependencies]
exercism_test_runner = "~> 1.9"

31
newsletter/manifest.toml Normal file
View File

@@ -0,0 +1,31 @@
# This file was generated by Gleam
# You typically do not need to edit this file
packages = [
{ name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" },
{ name = "exercism_test_runner", version = "1.9.0", build_tools = ["gleam"], requirements = ["argv", "gap", "glance", "gleam_community_ansi", "gleam_erlang", "gleam_json", "gleam_stdlib", "simplifile"], otp_app = "exercism_test_runner", source = "hex", outer_checksum = "0B17BB25F2FF1E60266467C24FE0CA04005410306AA05E9A4B41B1852D72865C" },
{ name = "filepath", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "67A6D15FB39EEB69DD31F8C145BB5A421790581BD6AA14B33D64D5A55DBD6587" },
{ name = "gap", version = "1.1.3", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_stdlib"], otp_app = "gap", source = "hex", outer_checksum = "6EF5E3B523FDFBC317E9EA28D5163EE04744A97C007106F90207569789612291" },
{ name = "glance", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "glexer"], otp_app = "glance", source = "hex", outer_checksum = "E155BA1A787FD11827048355021C0390D2FE9A518485526F631A9D472858CC6D" },
{ name = "gleam_community_ansi", version = "1.4.3", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_regexp", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "8A62AE9CC6EA65BEA630D95016D6C07E4F9973565FA3D0DE68DC4200D8E0DD27" },
{ name = "gleam_community_colour", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "FDD6AC62C6EC8506C005949A4FCEF032038191D5EAAEC3C9A203CD53AE956ACA" },
{ name = "gleam_deque", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_deque", source = "hex", outer_checksum = "64D77068931338CF0D0CB5D37522C3E3CCA7CB7D6C5BACB41648B519CC0133C7" },
{ name = "gleam_erlang", version = "0.34.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "0C38F2A128BAA0CEF17C3000BD2097EB80634E239CE31A86400C4416A5D0FDCC" },
{ name = "gleam_json", version = "2.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "C55C5C2B318533A8072D221C5E06E5A75711C129E420DD1CE463342106012E5D" },
{ name = "gleam_otp", version = "0.16.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "50DA1539FC8E8FA09924EB36A67A2BBB0AD6B27BCDED5A7EF627057CF69D035E" },
{ name = "gleam_regexp", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "7F5E0C0BBEB3C58E57C9CB05FA9002F970C85AD4A63BA1E55CBCB35C15809179" },
{ name = "gleam_stdlib", version = "0.55.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "32D8F4AE03771516950047813A9E359249BD9FBA5C33463FDB7B953D6F8E896B" },
{ name = "gleam_yielder", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_yielder", source = "hex", outer_checksum = "8E4E4ECFA7982859F430C57F549200C7749823C106759F4A19A78AEA6687717A" },
{ name = "glexer", version = "2.2.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glexer", source = "hex", outer_checksum = "5C235CBDF4DA5203AD5EAB1D6D8B456ED8162C5424FE2309CFFB7EF438B7C269" },
{ name = "simplifile", version = "1.7.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "1D5DFA3A2F9319EC85825F6ED88B8E449F381B0D55A62F5E61424E748E7DDEB0" },
]
[requirements]
exercism_test_runner = { version = "~> 1.9" }
gleam_deque = { version = ">= 1.0.0 and < 2.0.0" }
gleam_erlang = { version = ">= 0.25.0 and < 1.0.0" }
gleam_otp = { version = "~> 0.7 or ~> 1.0" }
gleam_regexp = { version = ">= 1.1.0 and < 2.0.0" }
gleam_stdlib = { version = ">= 0.54.0 or ~> 1.0" }
gleam_yielder = { version = ">= 1.1.0 and < 2.0.0" }
simplifile = { version = "~> 1.0" }

View File

@@ -0,0 +1,55 @@
import gleam/string
import simplifile
pub fn read_emails(path: String) -> Result(List(String), Nil) {
case simplifile.read(path) {
Error(_) -> Error(Nil)
Ok(content) ->
string.trim(content)
|> string.split("\n")
|> Ok
}
}
pub fn create_log_file(path: String) -> Result(Nil, Nil) {
case simplifile.create_file(path) {
Error(_) -> Error(Nil)
Ok(_) -> Ok(Nil)
}
}
pub fn log_sent_email(path: String, email: String) -> Result(Nil, Nil) {
case simplifile.append(path, email <> "\n") {
Error(_) -> Error(Nil)
Ok(_) -> Ok(Nil)
}
}
pub fn send_newsletter(
emails_path: String,
log_path: String,
send_email: fn(String) -> Result(Nil, Nil),
) -> Result(Nil, Nil) {
case create_log_file(log_path), read_emails(emails_path) {
Ok(_), Ok(emails) -> send_emails_and_log(emails, send_email, log_path)
_, _ -> Error(Nil)
}
}
fn send_emails_and_log(
emails: List(String),
send_fn: fn(String) -> Result(Nil, Nil),
log_path: String,
) -> Result(Nil, Nil) {
let _ = case emails {
[] -> Ok(Nil)
[email, ..rest] -> {
let _ = case send_fn(email) {
Error(_) -> Error(Nil)
Ok(_) -> log_sent_email(log_path, email)
}
send_emails_and_log(rest, send_fn, log_path)
}
}
}
// It should read all the email addresses from the given file and attempt to send an email to every one of them. If the anonymous function that sends the email returns an `Ok` value, write the email address to the log file. Make sure to do it as soon as the email is sent.

View File

@@ -0,0 +1,85 @@
import exercism/should
import exercism/test_runner
import newsletter
import simplifile
pub fn main() {
test_runner.main()
}
pub fn read_emails_test() {
let emails =
"lucy@example.com
thomas@example.com
sid@example.com
"
let assert Ok(_) = simplifile.write("emails.txt", emails)
let assert Ok(emails) = newsletter.read_emails("emails.txt")
emails
|> should.equal(["lucy@example.com", "thomas@example.com", "sid@example.com"])
}
pub fn create_log_file_test() {
let _ = simplifile.delete("log.txt")
let assert Ok(Nil) = newsletter.create_log_file("log.txt")
let assert Ok(log) = simplifile.read("log.txt")
log
|> should.equal("")
}
pub fn log_sent_email_test() {
let _ = simplifile.delete("log.txt")
let assert Ok(Nil) = newsletter.create_log_file("log.txt")
let assert Ok(Nil) =
newsletter.log_sent_email("log.txt", "janice@example.com")
let assert Ok(log) = simplifile.read("log.txt")
log
|> should.equal("janice@example.com\n")
let assert Ok(Nil) = newsletter.log_sent_email("log.txt", "joe@example.com")
let assert Ok(log) = simplifile.read("log.txt")
log
|> should.equal("janice@example.com\njoe@example.com\n")
}
pub fn send_newsletter_test() {
let _ = simplifile.delete("log.txt")
let emails =
"bushra@example.com
abdi@example.com
bell@example.com
"
let assert Ok(Nil) = simplifile.write("emails.txt", emails)
let send_email = fn(email) {
case email {
"bushra@example.com" -> {
let assert Ok(log) = simplifile.read("log.txt")
log
|> should.equal("")
Ok(Nil)
}
"abdi@example.com" -> {
let assert Ok(log) = simplifile.read("log.txt")
log
|> should.equal("bushra@example.com\n")
Error(Nil)
}
"bell@example.com" -> {
let assert Ok(log) = simplifile.read("log.txt")
log
|> should.equal("bushra@example.com\n")
Ok(Nil)
}
_ -> panic as "Unexpected email given to send_email function"
}
}
let assert Ok(Nil) =
newsletter.send_newsletter("emails.txt", "log.txt", send_email)
let assert Ok(log) = simplifile.read("log.txt")
log
|> should.equal("bushra@example.com\nbell@example.com\n")
}

View File

@@ -0,0 +1,24 @@
{
"authors": [
"lpil"
],
"files": {
"solution": [
"src/secure_treasure_chest.gleam"
],
"test": [
"test/secure_treasure_chest_test.gleam"
],
"exemplar": [
".meta/example.gleam"
],
"invalidator": [
"gleam.toml",
"manifest.toml"
]
},
"forked_from": [
"elm/secure-treasure-chest"
],
"blurb": "Learn about opaque types by making a more secure treasure chest"
}

View File

@@ -0,0 +1 @@
{"track":"gleam","exercise":"secure-treasure-chest","id":"c5dbff3c2a1342d389388a95cc2a7f88","url":"https://exercism.org/tracks/gleam/exercises/secure-treasure-chest","handle":"fw353qwgs","is_requester":true,"auto_approve":false}

4
secure-treasure-chest/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
*.beam
*.ez
build
erl_crash.dump

View File

@@ -0,0 +1,32 @@
# Help
## Running the tests
To run the tests, run the command `gleam test` from within the exercise directory.
## Submitting your solution
You can submit your solution using the `exercism submit src/secure_treasure_chest.gleam` command.
This command will upload your solution to the Exercism website and print the solution page's URL.
It's possible to submit an incomplete solution which allows you to:
- See how others have completed the exercise
- Request help from a mentor
## Need to get help?
If you'd like help solving the exercise, check the following pages:
- The [Gleam track's documentation](https://exercism.org/docs/tracks/gleam)
- The [Gleam track's programming category on the forum](https://forum.exercism.org/c/programming/gleam)
- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5)
- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
To get help if you're having trouble, you can use one of the following resources:
- [gleam.run](https://gleam.run/documentation/) is the gleam official documentation.
- [Discord](https://discord.gg/Fm8Pwmy) is the discord channel.
- [StackOverflow](https://stackoverflow.com/questions/tagged/gleam) can be used to search for your problem and see if it has been answered already. You can also ask and answer questions.

View File

@@ -0,0 +1,15 @@
# Hints
## 1. Define the `TreasureChest` opaque type.
- Opaque types are defined using the `opaque` keyword.
## 2. Define the `create` function.
- The [`string.length` function][length] can be used to get the length of a string.
## 3. Define `open` function.
- The `==` operator can be used to compare two strings for equality.
[length]: https://hexdocs.pm/gleam_stdlib/gleam/string.html#length

View File

@@ -0,0 +1,79 @@
# Secure Treasure Chest
Welcome to Secure Treasure Chest on Exercism's Gleam Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
## Introduction
## Opaque Types
Opaque types in Gleam are custom types where only the module that defines the type can construct or pattern match values of the type. This is useful for creating types that should only be used in a specific way.
Opaque types are defined using the `opaque` keyword.
```gleam
pub opaque type PositiveInt {
PositiveInt(inner: Int)
}
```
This `PositiveInt` type is to be used in situations where an int is wanted, but it has to be zero or greater. A regular Gleam `Int` could not be used as they can also be negative.
The module that defines this type can define a function to get the inner int value, and a function for creating the `PositiveInt` type which will return an error if the value is not positive.
```gleam
pub fn from_int(i: Int) -> Result(PositiveInt, String) {
case i {
_ if i < 0 -> Error("Value must be positive")
_ -> Ok(PositiveInt(i))
}
}
pub fn to_int(i: PositiveInt) -> Int {
i.inner
}
```
With this API other modules cannot construct a `PositiveInt` with a negative value, so any function that takes a `PositiveInt` can be sure that the value is positive.
## Instructions
Sharp eyed students will have noticed that the `TreasureChest` type in the previous exercise wasn't that secure!
If you used the `get_treasure` function you had to supply the password, but you could still destructure the `TreasureChest` type to get the treasure without having to know the password.
Let's fix that by using an Opaque Type.
## 1. Define the `TreasureChest` opaque type.
The `TreasureChest` contains two fields:
- A password that is a `String`.
- A treasure that is a generic type.
The `TreasureChest` type must be opaque.
## 2. Define the `create` function.
This function takes two arguments:
- A password `String`.
- A treasure value of any type.
The function returns a `TreasureChest` containing the password and the value.
If the password is shorter than 8 characters then the function should return an error saying `Password must be at least 8 characters long`.
## 3. Define `open` function.
This function takes two arguments:
- A `TreasureChest`.
- A password `String`.
If the password matches the password in the `TreasureChest` then the function should return the treasure, otherwise it should return an error saying `Incorrect password`.
## Source
### Created by
- @lpil

View File

@@ -0,0 +1,14 @@
name = "secure_treasure_chest"
version = "0.1.0"
[dependencies]
gleam_otp = "~> 0.7 or ~> 1.0"
gleam_stdlib = ">= 0.54.0 or ~> 1.0"
simplifile = "~> 1.0"
gleam_erlang = ">= 0.25.0 and < 1.0.0"
gleam_yielder = ">= 1.1.0 and < 2.0.0"
gleam_regexp = ">= 1.1.0 and < 2.0.0"
gleam_deque = ">= 1.0.0 and < 2.0.0"
[dev-dependencies]
exercism_test_runner = "~> 1.9"

View File

@@ -0,0 +1,31 @@
# This file was generated by Gleam
# You typically do not need to edit this file
packages = [
{ name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" },
{ name = "exercism_test_runner", version = "1.9.0", build_tools = ["gleam"], requirements = ["argv", "gap", "glance", "gleam_community_ansi", "gleam_erlang", "gleam_json", "gleam_stdlib", "simplifile"], otp_app = "exercism_test_runner", source = "hex", outer_checksum = "0B17BB25F2FF1E60266467C24FE0CA04005410306AA05E9A4B41B1852D72865C" },
{ name = "filepath", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "67A6D15FB39EEB69DD31F8C145BB5A421790581BD6AA14B33D64D5A55DBD6587" },
{ name = "gap", version = "1.1.3", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_stdlib"], otp_app = "gap", source = "hex", outer_checksum = "6EF5E3B523FDFBC317E9EA28D5163EE04744A97C007106F90207569789612291" },
{ name = "glance", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "glexer"], otp_app = "glance", source = "hex", outer_checksum = "E155BA1A787FD11827048355021C0390D2FE9A518485526F631A9D472858CC6D" },
{ name = "gleam_community_ansi", version = "1.4.3", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_regexp", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "8A62AE9CC6EA65BEA630D95016D6C07E4F9973565FA3D0DE68DC4200D8E0DD27" },
{ name = "gleam_community_colour", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "FDD6AC62C6EC8506C005949A4FCEF032038191D5EAAEC3C9A203CD53AE956ACA" },
{ name = "gleam_deque", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_deque", source = "hex", outer_checksum = "64D77068931338CF0D0CB5D37522C3E3CCA7CB7D6C5BACB41648B519CC0133C7" },
{ name = "gleam_erlang", version = "0.34.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "0C38F2A128BAA0CEF17C3000BD2097EB80634E239CE31A86400C4416A5D0FDCC" },
{ name = "gleam_json", version = "2.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "C55C5C2B318533A8072D221C5E06E5A75711C129E420DD1CE463342106012E5D" },
{ name = "gleam_otp", version = "0.16.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "50DA1539FC8E8FA09924EB36A67A2BBB0AD6B27BCDED5A7EF627057CF69D035E" },
{ name = "gleam_regexp", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "7F5E0C0BBEB3C58E57C9CB05FA9002F970C85AD4A63BA1E55CBCB35C15809179" },
{ name = "gleam_stdlib", version = "0.55.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "32D8F4AE03771516950047813A9E359249BD9FBA5C33463FDB7B953D6F8E896B" },
{ name = "gleam_yielder", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_yielder", source = "hex", outer_checksum = "8E4E4ECFA7982859F430C57F549200C7749823C106759F4A19A78AEA6687717A" },
{ name = "glexer", version = "2.2.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glexer", source = "hex", outer_checksum = "5C235CBDF4DA5203AD5EAB1D6D8B456ED8162C5424FE2309CFFB7EF438B7C269" },
{ name = "simplifile", version = "1.7.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "1D5DFA3A2F9319EC85825F6ED88B8E449F381B0D55A62F5E61424E748E7DDEB0" },
]
[requirements]
exercism_test_runner = { version = "~> 1.9" }
gleam_deque = { version = ">= 1.0.0 and < 2.0.0" }
gleam_erlang = { version = ">= 0.25.0 and < 1.0.0" }
gleam_otp = { version = "~> 0.7 or ~> 1.0" }
gleam_regexp = { version = ">= 1.1.0 and < 2.0.0" }
gleam_stdlib = { version = ">= 0.54.0 or ~> 1.0" }
gleam_yielder = { version = ">= 1.1.0 and < 2.0.0" }
simplifile = { version = "~> 1.0" }

View File

@@ -0,0 +1,25 @@
import gleam/string
pub opaque type TreasureChest(a) {
TreasureChest(password: String, treasure: a)
}
pub fn create(
password: String,
contents: treasure,
) -> Result(TreasureChest(treasure), String) {
case string.length(password) {
pass if pass >= 8 -> Ok(TreasureChest(password, contents))
_ -> Error("Password must be at least 8 characters long")
}
}
pub fn open(
chest: TreasureChest(treasure),
password: String,
) -> Result(treasure, String) {
case chest.password == password {
False -> Error("Incorrect password")
True -> Ok(chest.treasure)
}
}

View File

@@ -0,0 +1,55 @@
import exercism/should
import exercism/test_runner
import gleam/string
import secure_treasure_chest
import simplifile
pub fn main() {
test_runner.main()
}
fn compact_whitespace(graphemes: List(String), acc: String) -> String {
case graphemes {
[] -> acc
[" ", " ", ..rest] -> compact_whitespace([" ", ..rest], acc)
[grapheme, ..rest] -> compact_whitespace(rest, acc <> grapheme)
}
}
fn read_source() -> String {
let assert Ok(src) = simplifile.read("src/secure_treasure_chest.gleam")
compact_whitespace(string.to_graphemes(src), "")
}
pub fn type_must_be_opaque_test() {
let src = read_source()
case string.contains(src, "pub opaque type TreasureChest") {
True -> Nil
False -> panic as "The TreasureChest type must exist and be opaque"
}
}
pub fn create_is_ok_with_long_password_test() {
let assert Ok(_) = secure_treasure_chest.create("12345678", "My treasure")
}
pub fn create_is_error_with_short_password_test() {
secure_treasure_chest.create("1234567", "My treasure")
|> should.equal(Error("Password must be at least 8 characters long"))
}
pub fn open_is_ok_with_the_correct_password_test() {
let assert Ok(chest) = secure_treasure_chest.create("wwwibble", 100)
secure_treasure_chest.open(chest, "wwwibble")
|> should.equal(Ok(100))
let assert Ok(chest) = secure_treasure_chest.create("wwwobble", 1.5)
secure_treasure_chest.open(chest, "wwwobble")
|> should.equal(Ok(1.5))
}
pub fn open_is_an_error_with_an_incorrect_password_test() {
let assert Ok(chest) = secure_treasure_chest.create("wwwibble", 100)
secure_treasure_chest.open(chest, "wwwobble")
|> should.equal(Error("Incorrect password"))
}