Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 'Burger Discounts' chapter #34

Merged
merged 27 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8f00efe
Add 'burger discounts' chapter
feihong Apr 1, 2024
e1d0dba
Add first few sections
feihong Apr 2, 2024
2523f4f
Add 'record copy syntax' section
feihong Apr 3, 2024
1684ab5
Add section 'Ignoring function return values'
feihong Apr 3, 2024
7df0ee6
Add section 'Runtime representation of variants'
feihong Apr 3, 2024
76c3a32
Add section 'Arrays are mutable'
feihong Apr 3, 2024
90dd3ae
Add section 'Runtime representation of option'
feihong Apr 3, 2024
8cde85f
Add section 'Pattern matching on arrays'
feihong Apr 3, 2024
de0054c
Add section 'Array access is unsafe'
feihong Apr 3, 2024
34565a4
Add section 'Array.get array access function'
feihong Apr 3, 2024
ed78192
Add section Overview
feihong Apr 3, 2024
682864b
Edits for clarify and add footnotes
feihong Apr 4, 2024
7fab728
Add exercise 1
feihong Apr 4, 2024
276b00b
Add exercise 2
feihong Apr 4, 2024
697def7
Add exercise 3
feihong Apr 5, 2024
60f53cc
Update exercise 1 to remove wildcard
feihong Apr 5, 2024
d2967b1
Add exercise 4
feihong Apr 5, 2024
68873a0
Correct typo in getHalfOff comment
feihong Apr 8, 2024
ab23bdf
Fix burger-discounts index.html
feihong Apr 8, 2024
cc1c172
Add a friendlier explanation for the "buy 2 burgers get 1 free" promo…
feihong Apr 9, 2024
525393a
Improve phrasing in 'use full name' section
feihong Apr 9, 2024
6a12927
Add array length check to 'Array access is unsafe' section
feihong Apr 9, 2024
bd42f57
Mention that OCaml arrays are just JS arrays in runtime
feihong Apr 9, 2024
4394d9b
Add links for `when` guard and "option is a variant"
feihong Apr 9, 2024
9965b7e
Mention t hat `ignore` is more explicit and recommended
feihong Apr 9, 2024
4184366
Catching an exception is an alternate approach
feihong Apr 9, 2024
20a777b
Rephrase array access operator explanation
feihong Apr 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/.vitepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export default defineConfig({
{ text: 'Better Burgers', link: '/better-burgers/' },
{ text: 'Sandwich Tests', link: '/sandwich-tests/' },
{ text: 'Cram Tests', link: '/cram-tests/' },
{ text: 'Burger Discounts', link: '/burger-discounts/' },
]
}
],
Expand Down
252 changes: 252 additions & 0 deletions docs/burger-discounts/Discount.re
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
let _ =
(items: array(Item.t)) => {
let burgers =
items
// #region type-annotate-argument
|> Js.Array.filter(~f=(item: Item.t) =>
switch (item) {
| Burger(_) => true
| Sandwich(_)
| Hotdog => false
}
);
// #endregion type-annotate-argument
ignore(burgers);
};

let _ =
(items: array(Item.t)) => {
let burgers =
items
// #region full-name-constructors
|> Js.Array.filter(~f=item =>
switch (item) {
| Item.Burger(_) => true
| Item.Sandwich(_)
| Item.Hotdog => false
}
);
// #endregion full-name-constructors
ignore(burgers);
};

let _ =
(items: array(Item.t)) => {
let burgers =
items
// #region full-name-constructor
|> Js.Array.filter(~f=item =>
switch (item) {
| Item.Burger(_) => true
| Sandwich(_)
| Hotdog => false
}
);
// #endregion full-name-constructor
ignore(burgers);
};

let _ =
(items: array(Item.t)) => {
let burgers =
items
// #region full-name-fun
|> Js.Array.filter(
~f=
fun
| Item.Burger(_) => true
| Sandwich(_)
| Hotdog => false,
);
// #endregion full-name-fun
ignore(burgers);
};

let _ =
(items: array(Item.t)) => {
// #region swap-function-order
items
|> Js.Array.filter(~f=item =>
switch (item) {
| Item.Burger(_) => true
| Sandwich(_)
| Hotdog => false
}
)
|> Js.Array.sortInPlaceWith(~f=(item1, item2) =>
- compare(Item.toPrice(item1), Item.toPrice(item2))
)
// #endregion swap-function-order
|> ignore;
};

let _ = {
let burgers: array(Item.t) = [||];

// #region match-on-tuple
switch (burgers[0], burgers[1]) {
| (Burger(_), Burger(cheaperBurger)) =>
Some(Item.Burger.toPrice(cheaperBurger))
| _ => None
};
// #endregion match-on-tuple
};

let _ = {
let burgers: array(Item.t) = [||];

// #region check-array-length
Js.Array.length(burgers) < 2
? None
: (
switch (burgers[0], burgers[1]) {
| (Burger(_), Burger(cheaperBurger)) =>
Some(Item.Burger.toPrice(cheaperBurger))
| _ => None
}
);
// #endregion check-array-length
};

let _ = {
let burgers: array(Item.t) = [||];

// #region catch-exception
switch (burgers[0], burgers[1]) {
| exception _ => None
| (Burger(_), Burger(cheaperBurger)) =>
Some(Item.Burger.toPrice(cheaperBurger))
| _ => None
};
// #endregion catch-exception
};

module Array = {
// #region module-array
// Safe array access function
let get: (array('a), int) => option('a) =
(array, index) =>
switch (index) {
| index when index < 0 || index >= Js.Array.length(array) => None
| index => Some(Stdlib.Array.get(array, index))
};
// #endregion module-array
};

let _ = {
let burgers: array(Item.t) = [||];

// #region custom-array-get
switch (burgers[0], burgers[1]) {
| (Some(Burger(_)), Some(Burger(cheaperBurger))) =>
Some(Item.Burger.toPrice(cheaperBurger))
| _ => None
};
// #endregion custom-array-get
};

// #region improved-get-free-burger
// Buy 2 burgers, get 1 free
let getFreeBurger = (items: array(Item.t)) => {
let burgers =
items
|> Js.Array.filter(~f=item =>
switch (item) {
| Item.Burger(_) => true
| Sandwich(_)
| Hotdog => false
}
)
|> Js.Array.map(~f=Item.toPrice)
|> Js.Array.sortInPlaceWith(~f=(x, y) => - compare(x, y));

switch (burgers[0], burgers[1]) {
| (Some(_), Some(cheaperPrice)) => Some(cheaperPrice)
| (None | Some(_), None | Some(_)) => None
};
};
// #endregion improved-get-free-burger

// #region get-half-off-one
// Buy 1+ burger with 1 of every topping, get half off
let getHalfOff = (items: array(Item.t)) => {
let meetsCondition =
items
|> Js.Array.some(
~f=
fun
| Item.Burger({
lettuce: true,
tomatoes: true,
onions: 1,
cheese: 1,
bacon: 1,
}) =>
true
| Burger(_)
| Sandwich(_)
| Hotdog => false,
);

switch (meetsCondition) {
| false => None
| true =>
let total =
items
|> Js.Array.reduce(~init=0.0, ~f=(total, item) =>
total +. Item.toPrice(item)
);
Some(total /. 2.0);
};
};
// #endregion get-half-off-one
ignore(getHalfOff);

// #region get-half-off
// Buy 1+ burger with 1+ of every topping, get half off
let getHalfOff = (items: array(Item.t)) => {
let meetsCondition =
items
|> Js.Array.some(
~f=
fun
| Item.Burger({
lettuce: true,
tomatoes: true,
onions,
cheese,
bacon,
})
when onions > 0 && cheese > 0 && bacon > 0 =>
true
| Burger(_)
| Sandwich(_)
| Hotdog => false,
);

switch (meetsCondition) {
| false => None
| true =>
let total =
items
|> Js.Array.reduce(~init=0.0, ~f=(total, item) =>
total +. Item.toPrice(item)
);
Some(total /. 2.0);
};
};
// #endregion get-half-off

let _ =
name => {
// #region return-variant-at-end
let result =
name
|> String.split_on_char(' ')
|> List.map(String.map(c => c |> Char.code |> (+)(1) |> Char.chr))
|> String.concat(" ")
|> String.cat("Hello, ");

Some(result);
// #endregion return-variant-at-end
};
122 changes: 122 additions & 0 deletions docs/burger-discounts/DiscountTests.re
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// #region first-three
open Fest;

test("0 burgers, no discount", () =>
expect
|> equal(
Discount.getFreeBurger([|
Hotdog,
Sandwich(Ham),
Sandwich(Turducken),
|]),
None,
)
);

test("1 burger, no discount", () =>
expect
|> equal(
Discount.getFreeBurger([|
Hotdog,
Sandwich(Ham),
Burger({
lettuce: false,
onions: 0,
cheese: 0,
tomatoes: false,
bacon: 0,
}),
|]),
None,
)
);

test("2 burgers of same price, discount", () =>
expect
|> equal(
Discount.getFreeBurger([|
Hotdog,
Burger({
lettuce: false,
onions: 0,
cheese: 0,
tomatoes: false,
bacon: 0,
}),
Sandwich(Ham),
Burger({
lettuce: false,
onions: 0,
cheese: 0,
tomatoes: false,
bacon: 0,
}),
|]),
Some(15.),
)
);
// #endregion first-three

// #region burger-record
let burger: Item.Burger.t = {
lettuce: false,
onions: 0,
cheese: 0,
tomatoes: false,
bacon: 0,
};
// #endregion burger-record

// #region refactor-use-burger-record
test("1 burger, no discount", () =>
expect
|> equal(
Discount.getFreeBurger([|Hotdog, Sandwich(Ham), Burger(burger)|]),
None,
)
);

test("2 burgers of same price, discount", () =>
expect
|> equal(
Discount.getFreeBurger([|
Hotdog,
Burger(burger),
Sandwich(Ham),
Burger(burger),
|]),
Some(15.),
)
);
// #endregion refactor-use-burger-record

// #region different-price-test
test("2 burgers of different price, discount of cheaper one", () =>
expect
|> equal(
Discount.getFreeBurger([|
Hotdog,
Burger({...burger, tomatoes: true}), // 15.05
Sandwich(Ham),
Burger({...burger, bacon: 2}) // 16.00
|]),
Some(15.05),
)
);
// #endregion different-price-test

// #region three-burgers
test("3 burgers of different price, return Some(15.15)", () =>
expect
|> equal(
Discount.getFreeBurger([|
Burger(burger), // 15
Hotdog,
Burger({...burger, tomatoes: true, cheese: 1}), // 15.15
Sandwich(Ham),
Burger({...burger, bacon: 2}) // 16.00
|]),
Some(15.15),
)
);
// #endregion three-burgers
Loading