Skip to content
This repository was archived by the owner on Aug 27, 2022. It is now read-only.

Haskell time tutorial - #152

Merged
felixminom merged 14 commits into
tutorialsfrom
haskell-time-tutorial
Dec 15, 2021
Merged

Haskell time tutorial#152
felixminom merged 14 commits into
tutorialsfrom
haskell-time-tutorial

Conversation

@felixminom

@felixminom felixminom commented Nov 23, 2021

Copy link
Copy Markdown
Contributor

Hey team, this is the first final version of my tutorial. Any feedback is welcome thanks for your help 🙇🏽
screencapture-localhost-4000-tutorials-haskell-haskell-time-tutorial-2021-11-22-22_11_11

@jpvillaisaza jpvillaisaza left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think that monthBoundaries, which is the first example from the tutorial, is only used there, right? Would it be useful to reimplement that function using the type classes explained later on and compare both? I don't think the versions would differ a lot, but the differences are probably in readability.

(I had the idea that the function was reimplemented, but didn't find it after reviewing.)

Comment thread tutorials/haskell/haskell-time-tutorial/tutorial.md Outdated
Comment thread tutorials/haskell/haskell-time-tutorial/tutorial.md Outdated
Comment thread tutorials/haskell/haskell-time-tutorial/tutorial.md Outdated
we were able to contribute to one of Haskell's core libraries, time.

One of our tech leads, Sebastian Estrella, started with the idea:
“What if we bring some utilities that Ruby on Rails (RoR) has to Haskell?”.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this specific to Ruby on Rails or does Ruby also has the time utilities? I'm getting results for Ruby on Rails, but can you confirm if that's the case? Just curious.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes as I far as I know this was only available in Ruby on Rails, @sestrella could you help us clarifying this?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@felixminom yes, this is a feature only available in the context of Rails since by default Ruby does not provide these methods:

Rails (REPL)

➜  bundle exec rails c
Running via Spring preloader in process 77374
Loading development environment (Rails 6.1.4.1)
irb(main):001:0> Date.today.all_month
=> Mon, 01 Nov 2021..Tue, 30 Nov 2021

Ruby (REPL)

➜  irb
irb(main):001:0> require "date"
=> true
irb(main):002:0> Date.today.all_month
Traceback (most recent call last):
        4: from /nix/store/7nja5rmprz8sb72zvjbc1v85rswwsi4j-ruby-2.7.4/bin/irb:23:in `<main>'
        3: from /nix/store/7nja5rmprz8sb72zvjbc1v85rswwsi4j-ruby-2.7.4/bin/irb:23:in `load'
        2: from /nix/store/7nja5rmprz8sb72zvjbc1v85rswwsi4j-ruby-2.7.4/lib/ruby/gems/2.7.0/gems/irb-1.2.6/exe/irb:11:in `<top (required)>'
        1: from (irb):2
NoMethodError (undefined method `all_month' for #<Date: 2021-11-26 ((2459545j,0s,0n),+0s,2299161j)>)
irb(main):003:0>

Rails monkey patch some types in the std library

Comment on lines +36 to +37
irb(main):003:0> Date.today.all_week
=> Mon, 13 Sep 2021..Sun, 19 Sep 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Note that the introduction for this example (and the Haskell code) is for months, but the Ruby on Rails example is for weeks. Also, I don't think this is comparing the same thing: the Haskell one is the implementation of the function, but the Ruby on Rails one is the call to that function.

To make it clearer, I guess the Haskell one could include how it looks using GHCi:

> monthBoundaries (read "2021-12-05")
(2021-12-01,2021-12-31)

(I'm not sure if that's exactly how it would look like.)

Copy link
Copy Markdown
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 will somehow highlight the implementation with the new approach

Comment thread tutorials/haskell/haskell-time-tutorial/tutorial.md Outdated
Comment thread tutorials/haskell/haskell-time-tutorial/tutorial.md Outdated
Comment thread tutorials/haskell/haskell-time-tutorial/tutorial.md Outdated
Comment thread tutorials/haskell/haskell-time-tutorial/tutorial.md Outdated
Comment thread tutorials/haskell/haskell-time-tutorial/tutorial.md Outdated

@jpvillaisaza jpvillaisaza left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looking good, I clarified some of my previous comments.

to open source (OSS). We believe that we have the power to change
people’s lives (or at least make it easier) by pushing the boundaries
of the software industry and we’re happy to announce that the past month
we were able to contribute to one of Haskell's core libraries, time.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you add a link to the time library in Hackage?

```ruby
today = Date.today
today.next_month.all_month
=> Wed, 01 Dec 2021..Fri, 31 Dec 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this is still missing something to make the Haskell and Ruby codes comparable. The Haskell one shows the implementation (source), but the Ruby one shows how to use the implementation.

Assuming you have the Haskell implementation (which is the one in the example), then the Haskell equivalent would be something like this:

> today <- utctDay <$> getCurrentTime
> monthBoundaries today
(2021-11-01,2021-11-30)

Which is not that different compared to the Ruby one (ignoring that I skipped over the next month part, but the important thing is that the things being compared should match).

I think that the issue that you're trying to highlight is that with Haskell you have to implement the code because it's not part of the time library.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This makes a lot of sense in that case I will rename the function. The RoR version actually returns a range so I will name the Haskell function monthAllDays and I will make it return a whole month (I was returning just starting and ending days). Also latter on, we can compare this with the periodAllDays that was implemented in the library, wdyt ?

to explain pattern synonyms in depth, instead it’s meant to be a practical
introduction to this concept.

GHC documentation describes pattern synonyms like:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you add a link to the documentation?


As you could imagine, the first and last day of a day is the day itself.

### Some `time` new features examples.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this title needs to be reworded. Maybe “Examples of some new time features”?

As we can see this function is more intuitive when comparing with the first one,
and the functions that we use now are self-decriptivec

```haskell

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What happens if you indent this block so that it's part of the item? I think it should look better.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It worked :)

the library's mainterners.
- Pattern synonyms is a Haskell feature that will make our code base more reable, and
as shown in this tutorial could be useful in many scenarios. The `YearMonthDay` pattern
is probably one of the more versatil ones, so if you're working with the `time` library

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
is probably one of the more versatil ones, so if you're working with the `time` library
is probably one of the more versatile ones, so if you're working with the `time` library

of the software industry and we’re happy to announce that the past month
we were able to contribute to one of Haskell's core libraries, time.

One of our tech leads, Sebastian Estrella, started with the idea:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@felixminom I'm more than OK about not mentioning my name here since it was a team effort

code needed to get a month's boundaries and its version in RoR.

```haskell
type MonthFirstDay = Day

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@felixminom since this is a short code snippet I think it is clear that the first element of the tuple corresponds to the first day and the second one to the last day, in that sense, maybe we could get rid of MonthFirstDay and MonthLastDay type aliases? @jpvillaisaza what do you think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, monthBoundaries :: Day -> (Day, Day) sounds better for the tutorial.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

As I mentioned before I'm going to make both functions to exactly match, so now it will be something like this: allMonth :: Day -> [Day]

type MonthLastDay = Day

monthBoundaries :: Day -> (MonthFirstDay, MonthLastDay)
monthBoundaries day = let (y, m , _) = toGregorian day

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe it is just me, but moving the let statement to the next line makes the code more readable?

monthBoundaries :: Day -> (Day, Day)
monthBoundaries day = 
  let (y, m , _) = toGregorian day
  in (fromGregorian y m 1, fromGregorian y m 31)

we could write something like:

```haskell
foo November -- or foo 11 --Both will be equivalent

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not quite sure about the intention of foo in this context, however, if you are trying to highlight the fact that November and 11 are equivalent, I think could do something like this perhaps:

> November == 11
True


```haskell
dateToHoliday :: MonthOfYear -> DayOfMonth -> String
dateToHoliday January _ = "Happy new year!"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Since you already introduced patterns before, I think we could use it here to deconstruct a Day:

dayToHoliday :: Day -> String
dayToHoliday (YearMonthDay _ January _) = ...
dayToHoliday (YearMonthDay _ November 1) = ...
dayToHoliday (YearMonthDay _ December 25) = ...
...

like:

```haskell
dateToHoliday 12 25 = "Merry Christmas jojojo"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For showcasing the usage of a function I think is useful to use doctest syntax or something similar like a REPL output:

> dateToHoliday 12 25
"Merry Christmas jojojo"

Comment thread tutorials/haskell/haskell-time-tutorial/tutorial.md
Comment on lines +24 to +25
I will show the Haskell code needed to get all days in a month and its
version in RoR.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since the order changed (from Haskell then Ruby on Rails to Ruby on Rails then Haskell), can you make it clearer that the first one is Ruby on Rails? Here's a possible way to make that clearer:

Suggested change
I will show the Haskell code needed to get all days in a month and its
version in RoR.
I will show the RoR code to get all days in a month and then a Haskell
version to do so

Comment thread tutorials/haskell/haskell-time-tutorial/tutorial.md
in [fromGregorian y m 1 .. fromGregorian y m 31]
```

and then in a `stack repl` we can invoque our function:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
and then in a `stack repl` we can invoque our function:
and then in a `stack repl` we can call our function:

Or invoke.

Also, can you either mention both ghci and stack repl or just say ghci? I guess Stack is not required for this.

[2021-11-01 .. 2021-11-30]
```

As you can see, we had to implement the Haskell code by ourselfs. Why are

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
As you can see, we had to implement the Haskell code by ourselfs. Why are
As you can see, we had to implement the Haskell code by ourselves. Why are

As you can see, we had to implement the Haskell code by ourselfs. Why are
we setting the last day to always be 31st if some months have less than 31
days? Could be crossing your mind right now. That’s because `fromGregorian`
clips the values to be correct for each month, but not, what is

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
clips the values to be correct for each month, but not, what is
clips the values to be correct for each month, but now, what is

I'm guessing you meant now here.

information.

Just to make sure that `November` and `11` are equivalent, let's test it
in a stack repl.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
in a stack repl.
in a stack repl:

I don't think this is being used for other examples, but I think switching to colon before code snippets should be better.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@jpvillaisaza answering since it does not let me to do it directly in your comment. Regarding the laws all the ones you mentioned are required, the ones I added are more like properties that some instances should satisfy. Not really sure how to proceed with this one.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Okay, that sounds good.

Comment thread tutorials/haskell/haskell-time-tutorial/tutorial.md

```bash
> today <- utctDay <$> getCurrentTime
> monthBoundaries today

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
> monthBoundaries today
> allMonth today

dayToHoliday (YearMonthDay _ January _) = "Happy new year!"
dayToHoliday (YearMonthDay _ November 1) = "Let's eat colada morada"
dayToHoliday (YearMonthDay _ December 25) = "Merry Christmas jojojo"
dayToHoliday (YearMonthDay _ _ _) = "Probably just a regular day"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
dayToHoliday (YearMonthDay _ _ _) = "Probably just a regular day"
dayToHoliday _ = "Probably just a regular day"

dayToHoliday :: Day -> String
dayToHoliday (YearMonthDay _ January _) = "Happy new year!"
dayToHoliday (YearMonthDay _ November 1) = "Let's eat colada morada"
dayToHoliday (YearMonthDay _ December 25) = "Merry Christmas jojojo"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
dayToHoliday (YearMonthDay _ December 25) = "Merry Christmas jojojo"
dayToHoliday (YearMonthDay _ December 25) = "HO HO HO Merry Christmas"

like:

```haskell
dateToHoliday 12 25 = "Merry Christmas jojojo"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
dateToHoliday 12 25 = "Merry Christmas jojojo"
dateToHoliday 12 25 = "HO HO HO Merry Christmas"


Will not be as straightforward as the function that we have right now.

I haven’t mentioned it yet, but the patterns that we built previously

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Let's try to write all the sentences in the third-person point of view.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@sestrella Can you elaborate on this comment? I'm not sure if you mean something about the “I” or the “we” here. Just making sure this is clear.

[2021-11-01 .. 2021-11-30]
```

As you can see, the Haskell code has to be implemented to latter be called. Why

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

s/latter/then (but perhaps rephrasing should be better, just something other than latter, which is a typo)

the last day of the month is always set to be 31st, if some months have less than
31 days? Could be crossing your mind right now. That’s because `fromGregorian`
clips the values to be correct for each month, but now, what is `fromGregorian`?
Well that’s exactly what this contribution wante to avoid when using the `time`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
Well that’s exactly what this contribution wante to avoid when using the `time`
Well that’s exactly what this contribution wanted to avoid when using the `time`

Or wants, I don't know which one you meant.

31 days? Could be crossing your mind right now. That’s because `fromGregorian`
clips the values to be correct for each month, but now, what is `fromGregorian`?
Well that’s exactly what this contribution wante to avoid when using the `time`
library, stop using some functions that really don't know what their purpose

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

that really don't know

Something's missing here (that don't have a clear purpose?).

Also, why doesn't fromGregorian have a clear purpose? If it doesn't, is the contribution or the YearMonthDay the one adding something clearer (I'm only asking about fromGregorian)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

There are two functions and a pattern to build a Day in the library. Those are fromGregorian, fromJulian and the YearMonthDay pattern. When we started working on this we were using the fromJulian function that has some really weird behavior, specially when the Date needs to be clipped. So I wanted to somehow highlight that using this function is not as straightforward as the way it is right now.

Back to Haskell, probably it’s pretty clear what the `dateToHoliday` function
does, the first clause matches all days of January, second one matches the All
Saints holiday, the third one matches for Christmas and the last one it’s the
fallback for all other days. By using the `MonthOfYears` pattern, it makes it a

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it YearMonthDay instead of MonthOfYears here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Actually are both, I will rephrase this.

@sestrella sestrella left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

```

To exemplify, this pattern will be used in a function that receives a `Day` and
yields if the corresponding aate is a holiday:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

typo here: aate -> date

dateToHoliday 12 25 = "HO HO HO Merry Christmas"
```

is not as straightforward and readable as the function that was presented in the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Let's capitalize the first letter here

getQuarter q = YearQuarter y q
```

- Get quarters' lenght of a given year. Why the year is needed? Because in a

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lenght -> length

`WDay` is being declared, this instance is basically a `Day` wrapper that allows
to generate random days in order to perform property testing. When using property
testing you can stop thinking of generating the data that will feed the tests
manually,and you can focus on testing that the code is behaving the way it's

@Larox Larox Dec 3, 2021

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Let's add a space after the comma (,)

that the `periodAllDays` function produces, when the `DayPeriod` is a `Month`,
belong to the same year and month.

Finally the `periodLength` was tested and in in this case the test suite is a

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

duplicated word in

@felixminom
felixminom merged commit a0f2cd5 into tutorials Dec 15, 2021
@felixminom
felixminom deleted the haskell-time-tutorial branch December 15, 2021 13:57
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants