Ordinary Clojure code

Ordinary Clojure code - can be run in the REPL or in command line with Babashka.

Introduction

In my previous Babashka related blog post, Using Clojure in Command Line with Babashka, I told a short story how I used Babashka to populate test data to some development database. In this new blog post I tell a short story how I used Babashka to fetch some electricity data for my brother.

Background

So, my brother asked me if it is not too much trouble to create a small app for him to fetch the Finnish electricity prices for the next day. He told me that there is a public API for this: porssisahko API-rajapinta. The api is really simple: just give a day and hour and it will return the price for that day and hour. People in Finland use this data to optimize their electricity consumption for those hours when electricity is cheaper.

You could do this simple script just using bash and jq. I never bothered to learn bash well enough to be fluent with it. If I needed anything beyond basic bash stuff I immediately used Python in command-line scripting.

But nowadays we have Babashka. And I thought that I would also try to do the development using Babashka.

What is Babashka?

Babashka is “Clojure for scripting”. As the implementor of Babashka, Michiel Borkent, says in the Babashka home page: “The main idea behind babashka is to leverage Clojure in places where you would be using bash otherwise.” When running Clojure code with Babashka your script is not compiled to JVM bytecode but executed with a native binary runtime which implements certain core namespaces of the Clojure language. Therefore:

  • If you need to do programming tasks and startup is not an issue - use Clojure on the JVM.
  • If you need Clojure for scripting and fast startup is important - use Babashka.

Babashka home page provides a good explanation regarding the Differences with Clojure.

Development with Babashka

You can run your Clojure scripts really fast with Babashka (or nbb). For development, you have several options:

  • Use Clojure JVM REPL.
  • Use Clojurescript Node REPL.
  • Use Babashka REPL.

I thought I would use Babashka with nrepl to have an idea how the development differs compared using Clojure JVM REPL. And also, you cannot use some Java library that is not embedded into the Babashka binary, since you are using the Babashka environment itself for development.

When using Babashka you can define your environment using bb.edn file:

{:paths ["src" "test"]
 :deps  {cider/cider-nrepl {:mvn/version,"0.28.6"}}}

I add the cider-nrepl dependency to provide Calva support for VSCode editor. You can read more about my Calva configurations in these blog articles:

NOTE: You don’t need nrepl dependency in the bb.edn since nrepl support is built into the Babashka binary - Michiel Borkent kindly told me this in the Clojurians slack / babashka channel - a great place to ask Babashka related questions.

Then I start the Babashka nrepl session in the command line:

bb --nrepl-server 1667

Unlike JVM REPL, Babashka REPL starts immediately.

Then you can connect VSCode/Calva to this nrepl as explained in my previous Calva related blog posts. Now you are ready to start implementing the Clojure script with your editor and do the usual REPL driven development.

The Solution

The solution is really simple:

(ns get-eprice
  (:require [babashka.curl :as curl]
            [cheshire.core :as json]))

; http -v GET 'https://api.porssisahko.net/v1/price.json?date=2022-12-06&hour=02'
; (curl/get "https://api.porssisahko.net/v1/price.json?date=2022-12-06&hour=02")

(defn hour-converter
  "Converts numeric `hour` to two digit string."
  [hour]
  (if (<= hour 9)
    (str "0" hour)
    (str hour)))

(comment (hour-converter 9)
         (hour-converter 19))

(defn get-eprice
  "Returns a map with date and price for given `date` and `hour`.
   NOTE: hour must be a two digit string (even though the api says otherwise)."
  [date hour]
  (let [hour (str hour)
        url (str "https://api.porssisahko.net/v1/price.json?date=" date "&hour=" hour)
        response (curl/get url)
        body (json/parse-string (:body response))
        price (get body "price")]
    {:date (str date "T" hour ":00:00")
     :price price}))

(comment (get-eprice "2022-12-06" (hour-converter 2))
         (get-eprice "2022-12-06" (hour-converter 12)))

(defn get-prices
  "Returns a vector of maps with date and price for given `date`."
  [date]
  (let [prices (map #(get-eprice date (hour-converter %)) (range 0 24))]
    prices))

(comment (get-prices "2022-12-06")
         )

;; TODO: Remember to open the next day comment, and comment out this day.
(defn print-day-prices
  "Prints tomorrow's electricity prices to standard output."
  []
  (let [#_#_my-date (str
                     (-> (java.time.LocalDate/now)
                         (.plusDays 1)))
        my-date (str (java.time.LocalDate/now))
        prices (get-prices my-date)]
    (doseq [{:keys [date price]} prices]
      (println (str date "," price)))))


(comment (print-day-prices))


(print-day-prices)

You can see that after each function I have a rich comment, i.e. inside the comment a simple test to manually call the function with some test data using the REPL. The calls are in comments so that in production Babashka is not evaluating those test calls.

Then I created a simple cron configuration to call the Babashka script once a day:

39 14 * * * cd /opt/sahko; ./run-eprice.sh

… and run-eprice.sh:

#!/usr/bin/env bash

/usr/local/bin/bb /opt/sahko/src/clj/get_eprice.clj >> /opt/sahko/data/eprices.txt

I.e. my brother gets tomorrow’s prices but since I append the prices to the same file, he gets the historical prices as well, to satisfy his obsession to fiddle everything with Excel.

The eprices.txt file will have rows like:

...
2022-12-04T17:00:00,39.756
2022-12-04T18:00:00,39.6
2022-12-04T19:00:00,39.601
...

Conclusions

After a few years of Clojure programming I’m pretty fluent with Clojure and I really like the language. The language design is wonderful, the standard library is great, and the REPL driven development makes programming really enjoyable and fast. Thanks to Babashka, I can now use Clojure when I previously used Bash or Python.

The writer is working at a major international IT corporation building cloud infrastructures and implementing applications on top of those infrastructures.

Kari Marttila