Welcome again! 🤗
In case you missed it, this post is a follow-up for my previous post, describing the Calendar date picker component we implemented with Phoenix LiveView. In each step, we paid extra attention to how ChatGPT and Github Copilot assisted us during the journey.
Keep rolling with ChatGPT
In the previous post, we described how we get an initial version of our Calendar widget. At this point, there is a significant part of the code that ChatGPT produced. In particular, I was interested to see how it implemented the iteration through days in the render
function.
<div class="days grid grid-cols-7 divide-x divide-y divide-gray-200">
<%= for day <- Enum.concat([:calendar.last_day_of_month(2023, 5) - 3, :calendar.last_day_of_month(2023, 5) - 2, :calendar.last_day_of_month(2023, 5) - 1, :calendar.last_day_of_month(2023, 5)], 1..last_day_of_month(@today), 1..3) do %>
<div class="day p-2 text-center <%= if day > :calendar.last_day_of_month(2023, 5) and day <= last_day_of_month(@today), do: "text-black", else: "text-gray-400" %>"><%= day %></div>
<% end %>
</div>
Wow, this Enum.concat
is hard to read 😝. I had to put in some effort to extract it into a function and address some issues. In particular, there was a mistake in :calendar.last_day_of_month
; the correct version is :calendar.last_day_of_the_month
.
At the same time, I added some different styling for the days in the current month that are selectable (days in the future because of business needs), the other days in the month, and the remaining ones.
Here I tried a slightly different approach, in line with my experimental mindset: I worked to arrive very quickly to the point where the calendar widget just works with the new requirements, giving it a chance ChatGPT to suggest refactor ideas. Eventually, the code I produced and the recommended refactor compared like this:
My version at the left. GPTChat version at the right.
How valid was this suggestion from ChatGPT? I see good and bad things.
I liked the ideas it applied for the two first functions. Both were more readable, so I accepted them. But what it did with last_day_of_previous_month
was a despicable crime 😛. Note it decided to change the way to compute the previous month by subtracting the current month’s amount of days from the current date, which is wrong (test it with “May 1st”, for instance). If your guard is off, it could be introducing a nasty bug!
Something amusing happened. I rushed a bit and didn’t notice that the ChatGPT hadn’t finished responding to me. It was immediately browsing the internet to check its solution. After some seconds - that happened when I was reviewing and testing what he gave me on the first try - it was able to spot the error it did in last_day_of_previous_month
. Moreover, it explained it didn’t know that it was valid to add -1
to a given date in Elixir. It is visible the benefit of the beta feature allowing ChatGPT to correct itself.
Feeling like an old programmer again
During the last interaction with ChatGPT, there was a lot of inaccurate information in the output. You may have noticed that there were several errors in the generated response:
- Date manipulation functions could have been more accurate. In particular, they include typos.
- The Elixir code was sometimes not very natural: things like
Date.from_erl({year, month, day})
are not better thanDate.new(year, month, day)
. - Apart from his obvious mistake with
last_day_of_previous_month
, it also removed what I did to differentiate the days of the month before and after the present day indays_to_display
.
All of these indicated that it was about the right time to switch again to manual mode, but for a more extended period. I closed the ChatGPT session and concentrated all my attention on the code editor, like in the good old times. Well, not exactly. Remember I said GitHub Copilot was the other tool I wanted to try? Given that I was now spending more time writing code by myself, instead of copying/pasting code snippets given to me, its assistance started to be more noticeable.
One of my subsequent tasks was to adequate the CSS styling based on an existing reference, which is not a task where ChatGPT can help a lot. But it felt good to enter the “programming flow” that happens when you don’t switch from your code editor so often.
As time passed and the calendar widget gained more capabilities, covering more cases (e.g. the ability to change the month, users able to pick an available date, etc.), the more owner I felt about the code solution. Reaching this state at some point is necessary, so it was welcome.
Copilot was sometimes helpful. Especially while refactoring pure Elixir code into the component module because it anticipates certain things you want to type, acting like a smarter autocomplete feature in your editor. However, you have to watch out when it generates full functions.
See this example of a code snippet Copilot generated for me at some point:
defp calculate_next_month(socket) do
month = socket.assigns.selected_month
year = socket.assigns.selected_year
if month == 12 do
year = year + 1
else
month = month + 1
end
end
Based on my experience, the average quality of code suggestions in Elixir seems lower than what you get for other popular stacks. In this case, many things went wrong, but the most noticeable one is that year and month bindings inside the if
/else
blocks are not affecting those bindings at the function level. I have yet to see that kind of syntax errors with Javascript or Rust, to name some languages I have been using lately.
In any case, Copilot didn’t harm me while I was developing more stuff. Ultimately, the last version of the calculate_next_month
resulted in the following. After all, it has some roots in the initial Copilot suggestion:
defp calculate_next_month(socket) do
%{visible_month: month, visible_year: year} = socket.assigns
if month == 12 do
assign(socket, visible_month: 1, visible_year: year + 1)
else
assign(socket, visible_month: month + 1)
end
end
As a final note related to Copilot, it is almost incapable of generating suggestions when editing Heex code. The tool’s activity ratio is much lower in those areas.
Wrapping up our calendar component
After some hours, I was satisfied with the component look and behavior. Moreover, I had the chance to work and refactor the code a few times. I decided it was the appropriate time to seek feedback, therefore I submitted my 191 lines of code to ChatGPT for a code review.
I received some ideas that I considered incorrect. For instance, it recommended replacing the if
statement of the calculate_next_month
with a case, but I still prefer an if
statement in this case. By contrast, it includes some valuable comments about edge cases of the functionality. It pointed out that the code didn’t guard against malformed dates. Although I deliberately avoided making defensive code at this stage, it doesn’t harm to double-think those things, so I welcomed the reminder.
In the same line, it pointed out potential issues in typical edge cases related to date calculations:
Well, it had a point! Let’s see what happens with my calendar widget when the user changes to see some months in the future. Pay attention to the last days of 2023 (the previous month).
New Year’s Eve will be crazy!
ChatGPT didn’t mention anything about the issue with the days of the previous month coming in reverse ordering, something it was happening whichever the current month was. I’m sure I did it correctly, so I suspected Copilot “introduced” the issue. But maybe it’s just me having someone else to blame 😅. Overall, it effectively notified us of both potential and actual problems.
We did it!
It was a very intense session of Elixir/LiveView programming! We developed a Calendar widget capable of picking a date, respecting some basic business rules. You can see the final version here so you have a better idea of what I’ve achieved. Testing code is among the things that are still pending, and where ChatGPT/Copilot can also assist.
If you want to join me on reflecting about how AI tools helped me, and what is the best way to use them, read the Part 3 of this series of articles.
See you there!
Resources
Here is the source code I arrived in the programming session in this programming session: https://gist.github.com/jmbejar/f1e21647968e134c91168b489b50c55e
It could serve as inspiration if you’re about to implement a similar LiveView component 😊
Header image credits: Photo taken from https://www.pxfuel.com/
Meme credits: Imgflip Meme Generator