Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions exercises/practice/space-age/.approaches/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"introduction": {
"authors": [
"MatthijsBlom"
]
},
"approaches": []
}
117 changes: 117 additions & 0 deletions exercises/practice/space-age/.approaches/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Introduction

Suppose you are on Mercury.
The [orbital period][wikipedia-orbital-period] of Mercury is approximately one fourth of an Earth year.
This means that in one Earth year, Mercury will complete about four whole orbits around the sun.
As a consequence, your 'age' on Mercury is about four times your age on Earth.

More generally, your 'age' on a certain planet is the number of seconds since your birth, divided by the number of seconds it takes the planet to orbit the sun once.

The various planets' orbital periods as compared to the Earth's are given.
To convert them from _Earth years_ per orbit to _seconds_ per orbit you need to multiply this by the number of seconds in one Earth year.


## Approach: give meaningful names to important values

```haskell
ageOn :: Planet -> Float -> Float
ageOn planet seconds = seconds / (periodInEarthYears * secondsPerEarthYear)
where
secondsPerEarthYear = 60 * 60 * 24 * 365.25

periodInEarthYears = case planet of
Mercury -> 0.2408467
Venus -> 0.61519726
Earth -> 1
Mars -> 1.8808158
Jupiter -> 11.862615
Saturn -> 29.447498
Uranus -> 84.016846
Neptune -> 164.79132
```

This approach uses a `case` expression to choose the relevant orbital period.
Also a `where` clause is used to give names to important values.
This tends to greatly improve readability.


## General guidance

### `where` clauses are your friend!

Giving meaningful names to subexpressions can do wonders for code readability.
`let` expressions allow the same.
Being expressions – which `where` clauses aren't – `let` expressions are a bit more flexible in their use.
However, `where` clauses list the local definitions _after_ the main expression.
This allows you to paint the broad strokes of your strategy first, and to fill in the details later.
This is so convenient that it amply compensates for `where` clauses not being expressions.

More on `where` and `let` elsewhere:

- Haskell Wiki: [Let vs. Where][haskellwiki-let-vs-where]
- Haskell Wikibook:
- [`where` clauses][wikibook-where]
- [`let` bindings][wikibook-let]
- [`let` and `where` revisited][wikibook-let-vs-where]


### `case` expressions are also your friend!

Many beginning Haskellers write code like

```haskell
ageOn planet seconds
| planet == Mercury = _
| planet == Venus = _
| {- etc. -} = _
```

Using guards like this is an [anti-pattern][wikipedia-anti-pattern].
Pattern-match instead, for example using a `case` expression:

```haskell
ageOn planet seconds =
case planet of
Mercury -> _
Venus -> _
{- etc. -} -> _
```

Pattern matching is a fundamental concept in Haskell, but this document is too short to be able to fully explain it.
Please consult your other learning resources.
The track docs include an article on [Haskell learning resources][learning-resources].

Pattern matching with `case` has benefits over using guards:
Copy link
Member

@petertseng petertseng Feb 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will it be readily apparent to a learner that "using guards" refers to the above solution that includes | planet == "Mercury"? If it is already apparent, that was my only question about this pull request, so I think it is ready.

If it would not be apparent and needs to be made that way, mentioning guards somewhere around the sentence that reads "this is an anti-pattern" would make it clear.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I have changed

- This is an [anti-pattern][wikipedia-anti-pattern].
+ Using guards like this is an [anti-pattern][wikipedia-anti-pattern].


- `case` expressions are _expressions_.
Therefore as pieces of code they are very easy to move around during code composition.
You can give them names (e.g. in a `where` clause) and also pass them to functions as arguments.
- The compiler is able to check that you handle all possible cases.
If you overlook some cases and use guards, the compiler will not help you.
But if you are pattern matching it will!
- When you use pattern matching, the compiler can use its understanding of your code to apply code transformations that improve performance.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you use pattern matching, the compiler can use its understanding of your code to apply code transformations that improve performance.

I suggest linking to another resource that explains the code transformation that it applies. As someone starting to learn Haskell, I curious to what and how Haskell do the code transformations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this would be nice. However, I know of no (good) such resources off the top of my head. I'll look for a bit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't been able to find any and do not wish to delay merging for this, but would definitely welcome a PR in the future.



[learning-resources]:
https://exercism.org/docs/tracks/haskell/learning
"How to learn Haskell"


[haskellwiki-let-vs-where]:
https://wiki.haskell.org/Let_vs._Where
"Haskell Wiki: Let vs. Where"
[wikibook-let-vs-where]:
https://en.wikibooks.org/wiki/Haskell/More_on_functions#let_and_where_revisited
"Haskell Wikibook: let and where revisited"
[wikibook-let]:
https://en.wikibooks.org/wiki/Haskell/Next_steps#let_bindings
"Haskell Wikibook: let bindings"
[wikibook-where]:
https://en.wikibooks.org/wiki/Haskell/Variables_and_functions#where_clauses
"Haskell Wikibook: where clauses"
[wikipedia-anti-pattern]:
https://en.wikipedia.org/wiki/Anti-pattern
"Wikipedia: Anti-pattern"
[wikipedia-orbital-period]:
https://en.wikipedia.org/wiki/Orbital_period
"Wikipedia: Orbital period"