diff --git a/exercises/practice/leap/.approaches/case-expression/content.md b/exercises/practice/leap/.approaches/case-expression/content.md new file mode 100644 index 00000000..3d356d5b --- /dev/null +++ b/exercises/practice/leap/.approaches/case-expression/content.md @@ -0,0 +1,38 @@ +# Case expression + +```elm +isLeapYear : Int -> Bool +isLeapYear year = + let + isDivisibleByFour = (modBy 4 year) == 0 + isDivisibleBy100 = (modBy 100 year) == 0 + isDivisibleBy400 = (modBy 400 year) == 0 + in + case (isDivisibleByFour, isDivisibleBy100, isDivisibleBy400) of + (True, _, True) -> True + (True, False, _) -> True + _ -> False +``` + +## In this approach + +In this approach we use a [`case` expression][case-expression], and match on a [`Tuple`][tuple]. +It turns out that, if we are careful to ask questions in the right order, we can always potentially attain certainty about the answer by asking one more question. +This is a [truth-table][truth-table] like approach, which can be easier to read for complicated logic. + +## When to use a case expression? + +Using a case expression with a `Tuple` is idiomatic in Elm, but tuples have a maximum of 3 values, so can't be used for anything larger than this. + +Strings and lists can hold more values and can also be used with case expressions, which are useful in many circumstances. +Using a list with a case expression and recursion is especially common. + +[case-expression]: + https://elmprogramming.com/case-expression.html + "case expressions in Elm" +[tuple] + https://elmprogramming.com/tuple.html + "Tuples in Elm" +[truth-table]: + https://brilliant.org/wiki/truth-tables/ + "Truth tables" diff --git a/exercises/practice/leap/.approaches/case-expression/snippet.txt b/exercises/practice/leap/.approaches/case-expression/snippet.txt new file mode 100644 index 00000000..68a534cc --- /dev/null +++ b/exercises/practice/leap/.approaches/case-expression/snippet.txt @@ -0,0 +1,4 @@ +case (isDivisibleByFour, isDivisibleBy100, isDivisibleBy400) of + (True, _, True) -> True + (True, False, _) -> True + _ -> False diff --git a/exercises/practice/leap/.approaches/config.json b/exercises/practice/leap/.approaches/config.json new file mode 100644 index 00000000..0d6a2130 --- /dev/null +++ b/exercises/practice/leap/.approaches/config.json @@ -0,0 +1,36 @@ +{ + "introduction": { + "authors": [ + "ceddlyburge" + ] + }, + "approaches": [ + { + "uuid": "243cf336-7c0b-430b-a064-c976adea8d70", + "slug": "logical-expression", + "title": "Logical expression", + "blurb": "Use logical operators to combine several tests into one.", + "authors": [ + "ceddlyburge" + ] + }, + { + "uuid": "31ad676e-299b-4b7a-912c-3e4f68b5d873", + "slug": "case-expression", + "title": "Case expression", + "blurb": "Use a case expression.", + "authors": [ + "ceddlyburge" + ] + }, + { + "uuid": "9546e09d-9cde-4da1-b78a-0ef33d7d6db3", + "slug": "if-expression", + "title": "If expression", + "blurb": "Use an if expression.", + "authors": [ + "ceddlyburge" + ] + } + ] +} diff --git a/exercises/practice/leap/.approaches/if-expression/content.md b/exercises/practice/leap/.approaches/if-expression/content.md new file mode 100644 index 00000000..9586177c --- /dev/null +++ b/exercises/practice/leap/.approaches/if-expression/content.md @@ -0,0 +1,50 @@ +# If expression + +```elm +isLeapYear : Int -> Bool +isLeapYear year = + let + divisibleBy number = + modBy number year == 0 + in + if divisibleBy 100 then + divisibleBy 400 + + else + divisibleBy 4 +``` + +An [`if` expression][if-expression] (`if … then … else …`) is a compound expression that uses a test to determine which of two alternatives to evaluate to. +Many other languages feature a similar construct, often termed 'ternary operator'. + +## In this approach + +This approach uses exactly two tests to determine whether a year is a leap year. + +The first test is for divisibility by 100. +Once we know if the year is a multiple of 100, we know which further test to perform. + +- If the year is evenly divisible by 100, then `divisibleBy 100` will evaluate to `True` and the entire `if` expression will evaluate to whatever `divisibleBy 400` evaluates to. +- If the year is _not_ evenly divisible by 100, then `divisibleBy 100` is `False` and so the `if` expression evaluates to `divisibleBy 4`. + +This approach is not as concise as the [logical-expression][logical-expression], but it is [easier to describe][describable-code], which makes it easier to comunicate the problem domain. + +## When to use `if`? + +[`if` expressions][if-expression] might be a good fit when you + +- need an expression that +- chooses between exactly two options +- depending on a single `Bool`. + +When you have something other than a `Bool`, use `case` instead. + +[if-expression]: + https://elm-lang.org/docs/syntax#conditionals + "if expressions in Elm" +[logical-expression]: + https://exercism.org/tracks/elm/exercises/leap/approaches/conditional-expression + "Approach: a conditional expression" +[describable-code]: + https://www.freecodecamp.org/news/writing-describable-code/ + "Writing easily describable code" diff --git a/exercises/practice/leap/.approaches/if-expression/snippet.txt b/exercises/practice/leap/.approaches/if-expression/snippet.txt new file mode 100644 index 00000000..8590458f --- /dev/null +++ b/exercises/practice/leap/.approaches/if-expression/snippet.txt @@ -0,0 +1,5 @@ +if divisibleBy 100 then + divisibleBy 400 + +else + divisibleBy 4 diff --git a/exercises/practice/leap/.approaches/introduction.md b/exercises/practice/leap/.approaches/introduction.md new file mode 100644 index 00000000..6c52e82c --- /dev/null +++ b/exercises/practice/leap/.approaches/introduction.md @@ -0,0 +1,101 @@ +# Introduction + +There are various idiomatic approaches to solve Leap. +All approaches listed below check for divisibility by 4, 100, and 400. +However, they differ in the ways in which they combine these checks. + +## Approach: a logical expression + +```elm +isLeapYear : Int -> Bool +isLeapYear year = + let + divisibleBy number = + modBy number year == 0 + in + divisibleBy 4 && (not (divisibleBy 100) || divisibleBy 400) +``` + +A logicial expression is the most concise approach. +[Read more about this approach][logical-expression]. + +## Approach: an if expression + +```elm +isLeapYear : Int -> Bool +isLeapYear year = + let + divisibleBy number = + modBy number year == 0 + in + if divisibleBy 100 then + divisibleBy 400 + + else + divisibleBy 4 +``` + +An if expression is not as concise, but is [easier to describe][describable-code]. +[Read more about this approach][if-expression]. + +## Approach: a case expression + +```elm +isLeapYear : Int -> Bool +isLeapYear year = + let + isDivisibleByFour = (modBy 4 year) == 0 + isDivisibleBy100 = (modBy 100 year) == 0 + isDivisibleBy400 = (modBy 400 year) == 0 + in + case (isDivisibleByFour, isDivisibleBy100, isDivisibleBy400) of + (True, _, True) -> True + (True, False, _) -> True + _ -> False +``` + +This takes a [truth-table][truth-table] like approach, which can be easier to read for complicated logic. +[Read more about this approach][case-expression]. + +## Other approaches + +There are also more esoteric approaches, [such as this one using recursion, a list and a case expression][esoteric-approach], that are not idiomatic, but that can be interesting to look at, and probably come about by trying to solve the problem using some artificial constraints, as you would often do in a code kata. + +## General guidance + +The key to determining whether a given year is a leap year is to know whether the year is evenly divisible by `4`, `100`, and `400`. +For determining that, you can use the [`modBy` function][modby-function], which yields the remainder after division. + +You don't need to use a ['let' expression][let-expression] to define an `isDivisibleBy` function, although all the examples here do to aid readability. + +## Which approach to use? + +Code exists primarily for humans to read and reason about. +Therefore, in general, go with the approach that _makes the most sense_. + +All approaches listed here are valid choices unless marked otherwise. + +[logical-expression]: + https://exercism.org/tracks/elm/exercises/leap/approaches/logical-expression + "Approach: a conditional expression" +[if-expression]: + https://exercism.org/tracks/elm/exercises/leap/approaches/if-expression + "Approach: an if expression" +[case-expression]: + https://exercism.org/tracks/elm/exercises/leap/approaches/case-expression + "Approach: a case expression" +[describable-code]: + https://www.freecodecamp.org/news/writing-describable-code/ + "Writing easily describable code" +[truth-table]: + https://brilliant.org/wiki/truth-tables/ + "Truth tables" +[esoteric-approach] + https://exercism.org/tracks/elm/exercises/leap/solutions/edgerunner + "An esoteric solution to leap, using recursion, a list and a case expression" +[modby-function] + https://package.elm-lang.org/packages/elm/core/latest/Basics#modBy + "modBy function in Elm" +[let-expression] + https://elm-lang.org/docs/syntax#let-expressions + "let expressions in Elm" diff --git a/exercises/practice/leap/.approaches/logical-expression/content.md b/exercises/practice/leap/.approaches/logical-expression/content.md new file mode 100644 index 00000000..11d5f59e --- /dev/null +++ b/exercises/practice/leap/.approaches/logical-expression/content.md @@ -0,0 +1,34 @@ +# Logical expression + +```elm +isLeapYear : Int -> Bool +isLeapYear year = + let + divisibleBy number = + modBy number year == 0 + in + divisibleBy 4 && (not (divisibleBy 100) || divisibleBy 400) +``` + +We can combine smaller logical expressions into larger ones using the logical operators `&&` (and), `||` (or), and `not` (negation). + +A logicial expression is the most concise approach. + +## Precedence + +In school they teach you that `2 + 3 * 4` is to be read as meaning `2 + (3 * 4)`. +This is a convention, chosen for its convenience. +We say that the `*` operator has _higher [precedence][precedence]_ than `+`. + +In logic similar ambiguities exist, and these are similarly resolved. + +- _and_ has higher precedence than _or_, and +- _not_ has higher precedence than both _and_ and _or_. + +For example, `p || q && r` means the same as `p || (q && r)`. + +If you don't wish to remember the precendence of all the things, or force readers of your code to do so, you can use parentheses to make it explicit. + +[precedence]: + https://en.wikipedia.org/wiki/Order_of_operations + "Wikipedia: Order of operations" diff --git a/exercises/practice/leap/.approaches/logical-expression/snippet.txt b/exercises/practice/leap/.approaches/logical-expression/snippet.txt new file mode 100644 index 00000000..09a3286d --- /dev/null +++ b/exercises/practice/leap/.approaches/logical-expression/snippet.txt @@ -0,0 +1,7 @@ +isLeapYear : Int -> Bool +isLeapYear year = + let + divisibleBy number = + modBy number year == 0 + in + divisibleBy 4 && (not (divisibleBy 100) || divisibleBy 400)