-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
327 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
*.beam | ||
*.ez | ||
build | ||
erl_crash.dump |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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/sticker_shop.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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# Hints | ||
|
||
## 1. Define the `Usd`, `Eur`, and `Jpy` types | ||
|
||
- The `Usd`, `Eur`, and `Jpy` types can be defined without constructors using the `pub type TypeName` syntax. | ||
|
||
## 2. Define the `Money` type. | ||
|
||
- The `Money` type should have a phantom type parameter for the currency. | ||
|
||
## 3. Define `dollar`, `euro`, and `yen` functions | ||
|
||
- The return annotation of the `dollar`, `euro`, and `yen` functions should be `Money(currency)` where `currency` is the appropriate type parameter of the `Money` type. | ||
|
||
## 4. Define the `total` function | ||
|
||
- The return annotation of the `total` function should use the same type parameter as the argument. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
# Sticker Shop | ||
|
||
Welcome to Sticker Shop 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 | ||
|
||
## Phantom Types | ||
|
||
Phantom types are type parameters of a custom type that are not used in any of the value constructors of that type. | ||
|
||
That's a little abstract, so here is an example: | ||
|
||
```gleam | ||
pub type Length(unit) { | ||
Length(amount: Float) | ||
} | ||
``` | ||
|
||
In this example the `unit` type parameter is not used in the `Length` value constructor, so `unit` is a phantom type. | ||
|
||
This unused type parameter may seem useless, but it can be used to add further restrictions on how `Length` values can be used. | ||
|
||
For example, we could have a function `double`, which multiplies the length. It works with lengths of any unit, so the type parameter is a generic type variable. | ||
|
||
```gleam | ||
// This function accepts all Length values | ||
pub fn double(length: Length(unit)) -> Length(unit) { | ||
Length(length.amount *. 2.0) | ||
} | ||
``` | ||
|
||
We could also have a function `add_inch`, which only works if the length is in inches. | ||
|
||
```gleam | ||
// A unit type for inches. It is never constructed so we don't | ||
// define any constructors for it. | ||
pub type Inches | ||
pub fn add_inch(length: Length(Inches)) -> Length(Inches) { | ||
Length(length.amount +. 1.0) | ||
} | ||
``` | ||
|
||
The `add_inch` function will not accept lengths of any other unit parameter, the phantom type has been used to ensure only the correct unit is used. | ||
|
||
A function can also be written to ensure that two length values are of the same unit, by using the same type variable for both. | ||
|
||
```gleam | ||
pub fn add(a: Length(unit), b: Length(unit)) -> Length(unit) { | ||
Length(a.amount +. b.amount) | ||
} | ||
``` | ||
```gleam | ||
let two_meters: Length(Meters) = Length(2.0) | ||
let two_inches: Length(Inches) = Length(2.0) | ||
add(two_meters, two_meters) | ||
// -> Length(4.0): Length(Meters) | ||
add(two_meters, two_inches) | ||
// Type error! The unit type parameters do not match. | ||
``` | ||
|
||
Phantom types can work well with opaque types. If other modules cannot construct `Length` values then we can ensure they are not constructed with an invalid unit type, and that only the functions defined above can be used with them. | ||
|
||
## Instructions | ||
|
||
Lucy has an online sticker shop, where she sells cute stickers featuring everyone's favourite programming languages. People from all around the world buy her stickers, and she's having some trouble dealing with all the different currencies. | ||
|
||
Create a program that Lucy can use to calculate prices while being sure that she's always using the correct currency. | ||
|
||
## 1. Define the `Usd`, `Eur`, and `Jpy` types | ||
|
||
These types are used to represent the different currencies that Lucy's customers use to buy her stickers. | ||
|
||
They are to be used as phantom types and do not need to have any constructors. | ||
|
||
## 2. Define the `Money` type. | ||
|
||
The `Money` type should have an `Int` field for the amount of money, a currency phantom type parameter, and it should be an opaque type. | ||
|
||
## 3. Define `dollar`, `euro`, and `yen` functions | ||
|
||
Define the `dollar`, `euro`, and `yen` functions that take an `Int` argument and return a `Money` value with the correct currency. | ||
|
||
## 4. Define the `total` function | ||
|
||
Define the `total` function which takes a list of `Money` values and returns the total amount of money in the list. | ||
|
||
```gleam | ||
total([euro(120), euro(200), euro(145)]) | ||
// -> euro(465) | ||
``` | ||
|
||
## Source | ||
|
||
### Created by | ||
|
||
- @lpil |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
name = "sticker_shop" | ||
version = "0.1.0" | ||
|
||
[dependencies] | ||
gleam_bitwise = "~> 1.2" | ||
gleam_otp = "~> 0.7 or ~> 1.0" | ||
gleam_stdlib = "~> 0.32 or ~> 1.0" | ||
simplifile = "~> 1.0" | ||
gleam_erlang = ">= 0.25.0 and < 1.0.0" | ||
|
||
[dev-dependencies] | ||
exercism_test_runner = "~> 1.4" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# This file was generated by Gleam | ||
# You typically do not need to edit this file | ||
|
||
packages = [ | ||
{ name = "argv", version = "1.0.1", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "A6E9009E50BBE863EB37D963E4315398D41A3D87D0075480FC244125808F964A" }, | ||
{ name = "exercism_test_runner", version = "1.7.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 = "2FC1BADB19BEC2AE77BFD2D3A606A014C85412A7B874CAFC4BA8CF04B0B257CD" }, | ||
{ name = "gap", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_stdlib"], otp_app = "gap", source = "hex", outer_checksum = "2EE1B0A17E85CF73A0C1D29DA315A2699117A8F549C8E8D89FA8261BE41EDEB1" }, | ||
{ name = "glance", version = "0.8.2", build_tools = ["gleam"], requirements = ["gleam_stdlib", "glexer"], otp_app = "glance", source = "hex", outer_checksum = "ACF09457E8B564AD7A0D823DAFDD326F58263C01ACB0D432A9BEFDEDD1DA8E73" }, | ||
{ name = "gleam_bitwise", version = "1.3.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_bitwise", source = "hex", outer_checksum = "B36E1D3188D7F594C7FD4F43D0D2CE17561DE896202017548578B16FE1FE9EFC" }, | ||
{ name = "gleam_community_ansi", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "FE79E08BF97009729259B6357EC058315B6FBB916FAD1C2FF9355115FEB0D3A4" }, | ||
{ name = "gleam_community_colour", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "A49A5E3AE8B637A5ACBA80ECB9B1AFE89FD3D5351FF6410A42B84F666D40D7D5" }, | ||
{ name = "gleam_erlang", version = "0.25.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "054D571A7092D2A9727B3E5D183B7507DAB0DA41556EC9133606F09C15497373" }, | ||
{ name = "gleam_json", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "8B197DD5D578EA6AC2C0D4BDC634C71A5BCA8E7DB5F47091C263ECB411A60DF3" }, | ||
{ name = "gleam_otp", version = "0.10.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "0B04FE915ACECE539B317F9652CAADBBC0F000184D586AAAF2D94C100945D72B" }, | ||
{ name = "gleam_stdlib", version = "0.36.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "C0D14D807FEC6F8A08A7C9EF8DFDE6AE5C10E40E21325B2B29365965D82EB3D4" }, | ||
{ name = "glexer", version = "0.7.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glexer", source = "hex", outer_checksum = "4484942A465482A0A100936E1E5F12314DB4B5AC0D87575A7B9E9062090B96BE" }, | ||
{ name = "simplifile", version = "1.5.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "EB9AA8E65E5C1E3E0FDCFC81BC363FD433CB122D7D062750FFDF24DE4AC40116" }, | ||
{ name = "thoas", version = "0.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "4918D50026C073C4AB1388437132C77A6F6F7C8AC43C60C13758CC0ADCE2134E" }, | ||
] | ||
|
||
[requirements] | ||
exercism_test_runner = { version = "~> 1.4" } | ||
gleam_bitwise = { version = "~> 1.2" } | ||
gleam_erlang = { version = ">= 0.25.0 and < 1.0.0"} | ||
gleam_otp = { version = "~> 0.7 or ~> 1.0" } | ||
gleam_stdlib = { version = "~> 0.32 or ~> 1.0" } | ||
simplifile = { version = "~> 1.0" } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import gleam/list | ||
|
||
pub type Usd | ||
|
||
pub type Eur | ||
|
||
pub type Jpy | ||
|
||
pub opaque type Money(currency) { | ||
Money(amount: Int) | ||
} | ||
|
||
pub fn dollar(amount: Int) -> Money(Usd) { | ||
Money(amount) | ||
} | ||
|
||
pub fn euro(amount: Int) -> Money(Eur) { | ||
Money(amount) | ||
} | ||
|
||
pub fn yen(amount: Int) -> Money(Jpy) { | ||
Money(amount) | ||
} | ||
|
||
pub fn total(prices: List(Money(currency))) -> Money(currency) { | ||
prices | ||
|> list.fold(0, fn(total, price) { total + price.amount }) | ||
|> Money | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import exercism/should | ||
import exercism/test_runner | ||
import gleam/string | ||
import simplifile | ||
import sticker_shop.{type Eur, type Jpy, type Money, type Usd} | ||
|
||
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/sticker_shop.gleam") | ||
src | ||
|> string.replace("\n", "") | ||
|> string.to_graphemes | ||
|> compact_whitespace("") | ||
|> string.replace(" (", "(") | ||
|> string.replace(")->", ") ->") | ||
} | ||
|
||
pub fn type_must_be_opaque_test() { | ||
let src = read_source() | ||
case string.contains(src, "pub opaque type Money") { | ||
True -> Nil | ||
False -> panic as "The Money type must exist and be opaque" | ||
} | ||
} | ||
|
||
pub fn dollar_function_must_return_usd_test() { | ||
let src = read_source() | ||
case string.contains(src, "-> Money(Usd)") { | ||
True -> Nil | ||
False -> panic as "The dollar function must return Usd" | ||
} | ||
} | ||
|
||
pub fn euro_function_must_return_eur_test() { | ||
let src = read_source() | ||
case string.contains(src, "-> Money(Eur)") { | ||
True -> Nil | ||
False -> panic as "The euro function must return Eur" | ||
} | ||
} | ||
|
||
pub fn yen_function_must_return_jpy_test() { | ||
let src = read_source() | ||
case string.contains(src, "-> Money(Jpy)") { | ||
True -> Nil | ||
False -> panic as "The yen function must return Jpy" | ||
} | ||
} | ||
|
||
pub fn dollar_test() { | ||
let _money: Money(Usd) = sticker_shop.dollar(1) | ||
} | ||
|
||
pub fn euro_test() { | ||
let _money: Money(Eur) = sticker_shop.euro(1) | ||
} | ||
|
||
pub fn yen_test() { | ||
let _money: Money(Jpy) = sticker_shop.yen(1) | ||
} | ||
|
||
pub fn total_dollars_test() { | ||
[ | ||
sticker_shop.dollar(120), | ||
sticker_shop.dollar(50), | ||
sticker_shop.dollar(45), | ||
sticker_shop.dollar(100), | ||
] | ||
|> sticker_shop.total | ||
|> should.equal(sticker_shop.dollar(315)) | ||
} | ||
|
||
pub fn total_euros_test() { | ||
[ | ||
sticker_shop.euro(110), | ||
sticker_shop.euro(20), | ||
sticker_shop.euro(35), | ||
sticker_shop.euro(100), | ||
] | ||
|> sticker_shop.total | ||
|> should.equal(sticker_shop.euro(265)) | ||
} | ||
|
||
pub fn total_yen_test() { | ||
[ | ||
sticker_shop.yen(480), | ||
sticker_shop.yen(340), | ||
sticker_shop.yen(455), | ||
sticker_shop.yen(165), | ||
sticker_shop.yen(100), | ||
] | ||
|> sticker_shop.total | ||
|> should.equal(sticker_shop.yen(1540)) | ||
} |