Haskell time tutorial - #152
Conversation
jpvillaisaza
left a comment
There was a problem hiding this comment.
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.)
| 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?”. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Yes as I far as I know this was only available in Ruby on Rails, @sestrella could you help us clarifying this?
There was a problem hiding this comment.
@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
| irb(main):003:0> Date.today.all_week | ||
| => Mon, 13 Sep 2021..Sun, 19 Sep 2021 |
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
Good catch I will somehow highlight the implementation with the new approach
jpvillaisaza
left a comment
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
What happens if you indent this block so that it's part of the item? I think it should look better.
| 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 |
There was a problem hiding this comment.
| 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: |
There was a problem hiding this comment.
@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 |
There was a problem hiding this comment.
@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?
There was a problem hiding this comment.
Yes, monthBoundaries :: Day -> (Day, Day) sounds better for the tutorial.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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!" |
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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"| I will show the Haskell code needed to get all days in a month and its | ||
| version in RoR. |
There was a problem hiding this comment.
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:
| 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 |
| in [fromGregorian y m 1 .. fromGregorian y m 31] | ||
| ``` | ||
|
|
||
| and then in a `stack repl` we can invoque our function: |
There was a problem hiding this comment.
| 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 |
There was a problem hiding this comment.
| 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 |
There was a problem hiding this comment.
| 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. |
There was a problem hiding this comment.
| 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.
There was a problem hiding this comment.
@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.
|
|
||
| ```bash | ||
| > today <- utctDay <$> getCurrentTime | ||
| > monthBoundaries today |
There was a problem hiding this comment.
| > 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" |
There was a problem hiding this comment.
| 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" |
There was a problem hiding this comment.
| dayToHoliday (YearMonthDay _ December 25) = "Merry Christmas jojojo" | |
| dayToHoliday (YearMonthDay _ December 25) = "HO HO HO Merry Christmas" |
| like: | ||
|
|
||
| ```haskell | ||
| dateToHoliday 12 25 = "Merry Christmas jojojo" |
There was a problem hiding this comment.
| 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 |
There was a problem hiding this comment.
Let's try to write all the sentences in the third-person point of view.
There was a problem hiding this comment.
@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 |
There was a problem hiding this comment.
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` |
There was a problem hiding this comment.
| 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 |
There was a problem hiding this comment.
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)?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Is it YearMonthDay instead of MonthOfYears here?
There was a problem hiding this comment.
Actually are both, I will rephrase this.
| ``` | ||
|
|
||
| To exemplify, this pattern will be used in a function that receives a `Day` and | ||
| yields if the corresponding aate is a holiday: |
| dateToHoliday 12 25 = "HO HO HO Merry Christmas" | ||
| ``` | ||
|
|
||
| is not as straightforward and readable as the function that was presented in the |
There was a problem hiding this comment.
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 |
| `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 |
There was a problem hiding this comment.
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 |
Hey team, this is the first final version of my tutorial. Any feedback is welcome thanks for your help 🙇🏽
