...or is there? Check how different driving speeds affect fuel consumption and travel time.
My first ClojureScript project. Also my entry to Solidabis's 2021 coding challenge.
Update: my entry won the coding challenge! ππ₯³
I was a bit surprised since my app is very basic. On the other hand, the coding challenge instructions were also very basic. Maybe ClojureScript was so exotic a choice that it netted me extra points in the contest.
I got a 300β¬ gift card to Verkkokauppa.com, a Finnish online store selling geeky stuff (but many other kinds of stuff as well). Hmm, I spent ~16.5 hours on the project, so the effective net hourly rate became ~18β¬. Not bad for a fun little side project.
Read about the coding challenge entries on Solidabis's blog (in Finnish) β
- ClojureScript: a Clojure compiler that targets JavaScript
- Reagent: a React wrapper for ClojureScript
- reagent-frontend-template for generating the project structure
- Twind: a "Tailwind-in-JS" library for styling
- Tailwind UI: official Tailwind CSS components
I did the development on Windows using the Atom editor. See Diary entry on 2021-05-25 for details.
The app is very simple but took quite a long time (see the Diary section) because this was my first ClojureScript project. There are still many things that I'd like to improve. Maybe I'll revisit this project after a while so I can take a look using fresh eyes.
I really like Reagent. It makes UI building and state management very easy and simple. It reminds me of Mithril.js which I also like very much. React seems quite convoluted in comparison.
I look forward to using re-frame for more complex state management in the future.
- If you haven't used ClojureScript before, see ClojureScript's Quick Start guide and install the required dependencies
npm install
npm start
- Open http://localhost:3000/
- Optional:
- In Atom, press Ctrl+Shift+P and select "Chlorine: Connect Socket Repl" and "Chlorine: Connect Embedded"
- Now you can evaluate code in Atom e.g. with Chlorine's example keybindings
npm run build
The ClojureScript code is compiled to JavaScript
and outputted to the public/js/
directory.
Then you can deploy the whole public/
directory.
I have deployed the app on Netlify: need-for-speed.netlify.app. It took only like 5 minutes.
This project is my solution to a coding challenge published by Solidabis, a Finnish IT firm (the site is in Finnish).
Here's a summary of the original instructions:
- The user can input a distance
- The user can input two speeds
- The user can choose between three cars
with the following fuel consumptions at 1 km/h:
- Car A: 3 liters / 100km
- Car B: 3.5 liters / 100km
- Car C: 4 liters / 100km
- As speed increases by 1 km/h, fuel consumption is multiplied by 1.009
- The app shows the travel time and fuel consumption for both speeds and also the differences between the values
- Tech stack can be chosen freely
- Back-end is optional
- Usage of third party libraries and services that perform the comparisons is forbidden
Original instructions (in Finnish) on Internet Archive β
- 1Γπ = 1 pomodoro = ~30 minutes
- Total time spent: 33Γπ = ~16.5 hours
Set up the project.
Got distracted looking for a nice Clojure IDE.
Previously I have used Cursive IDE when doing Clojure exercises on Exercism. Cursive uses Paredit for "structural editing." I'm sure it's very handy and useful, but it seems too much of a hassle to set up, especially as I'm also using IdeaVim (Vim plugin). (Update: see 2021-05-30.)
I think I'll try:
- Atom (code editor)
- vim-mode-plus (Vim plugin)
- Chlorine (Clojure REPL plugin)
- Parinfer (plugin which simplifies writing and editing Lisp code like Clojure).
Finished configuring Atom for now. So far so good!
Also asked about Reagent template's license file.
Committed this readme file. (I have been updating this every day.)
Removed the Reagent template's license file by editing the initial commit and added my own license file.
Set up Twind, a Tailwind-in-JS library.
I first tried to install it via npm and load it in core.cljs
:
(ns need-for-speed.core
(:require
[reagent.core :as r]
[reagent.dom :as d]
["twind/shim"])) ;; <- Same as `import 'twind/shim'` in JS
But for some reason I got errors in the browser's console:
app.js:1551
An error occurred when loading need_for_speed.core.js
app.js:1552
TypeError: (0 , module.pathToFileURL) is not a function
at Object.shadow$provide.module$node_modules$twind$shim$shim_cjs (:3000/js/cljs-runtime/module$node_modules$twind$shim$shim_cjs.js:3)
at Object.shadow.js.jsRequire (:3000/js/cljs-runtime/shadow.js.js:34)
at Object.shadow.js.require (:3000/js/cljs-runtime/shadow.js.js:59)
at eval (:3000/js/cljs-runtime/need_for_speed.core.js:2)
at eval (<anonymous>)
at Object.goog.globalEval (app.js:486)
at Object.env.evalLoad (app.js:1549)
at app.js:1733
(index):18
Uncaught TypeError: need_for_speed.core.init_BANG_ is not a function
at localhost/:18
So that's why I'm loading it via a CDN for now.
Started implementing the UI:
Beautiful. π
Started handling form elements' state in React/Reagent.
Finished handling form elements' state in React/Reagent.
Created initial component for showing the calculation results.
Split code into multiple files.
Didn't work on the project on this day. Sundays are for sleeping and resting (thus the sleeping emoji).
I wrote before (see 2021-05-25) that I'd like to use Cursive IDE, but chose Atom because Atom has a Parinfer plugin, and Cursive only uses Paredit (built in).
But I was wrong: Cursive supports also Parinfer out of the box. There's just no mention of Parinfer anywhere in the Cursive docs. π€¦ββοΈ
Anyway, I'm going to stick with Atom in this project. I might use Cursive for future projects because I liked it when I used it earlier. But I like Atom as well... π€
Cleaned up code.
Started showing diffs (e.g. "-3.12 liters" or "+0.22 hours") in results.
Had trouble with using JavaScript's
Number.toLocaleString()
.
Gotta continue tomorrow.
Got number formatting working! Not sure what was wrong yesterday because today my code is quite much the same. π Anyway, here's what's working:
(.toLocaleString 1234 "en" #js {:signDisplay "exceptZero"})
;; "+1,234"
Improved number formatting and cleaned up code.
Started formatting times, e.g. "1.67 hours" β "1 hour 40 minutes."
Finished formatting times and cleaned up code.
Started styling the UI.
Busy day so didn't work on the project.
Continued styling the UI... lol why did I spend so much time on this?
Final day!
- Finished UI styling.
- Improved input handling.
- Wrote more stuff to this readme file.
- Published the app on Netlify.
Then I noticed that the production builds don't work. π This code:
(ns need-for-speed.form
(:require
[goog.string :as gstring]))
;; Definition of `key` and `consumption` not shown here
(gstring/format "Car %s (%.1f liters/100 km at 1 km/h)" (name key) consumption)]])))
...caused these errors in the console:
Uncaught TypeError: ka.format is not a function
...and ka.format
referred to gstring/format
.
No idea why and there was no time to investigate. π€
So I replaced gstring/format
with str
.
I'd really like to do at least some unit tests but I ran out of time. π Maybe I'll do them afterwards. Edit: I spent 1Γπ trying to create tests, but received the following error when running the initial tests in Atom:
TypeError: "cljs.test is undefined"
All right, no tests then. Β―\(γ)/Β― π
E.g. with distance 3,500 and speeds 110 and 95:
- Fuel consumptions per 100km are 7.97 and 6.96 liters. The diff is shown as plus/minus 1.00 liter, but it should be 0.01 liters more.
- The times are 31 h 49 min and 36 h 51 min. The diff is shown as plus/minus 5 h 1 min, but it should be one minute more.
The fix might be as simple as storing and handling the fuel amounts as centiliters and the times as minutes. Before showing them in the UI, the centiliters could be divided by 100 and the minutes by 60.