# Temperature Scale Converter in Clojure

Problem statement:

Write a program that converts all given temperatures from a given input temperature scale to a given output temperature scale. The temperature scales to be supported are Kelvin, Celsius, Fahrenheit, Rankine, Delisle, Newton, Rømer, Réaumur.

Synopsis: tempconv INPUT_SCALE OUTPUT_SCALE [TEMPERATURE]...

The INPUT_SCALE and OUTPUT_SCALE shall be given as follows:

• K for Kelvin
• C for Celsius
• F for Fahrenheit
• R for Rankine
• D for Delisle
• N for Newton
• Rø for Rømer
• Ré for Réaumur.

Example:

tempconv K C 0 273.15 373.15
-273.15
0.0
100.0


My solution in Clojure:

#!/usr/bin/env -S clojure

(defrecord TemperatureConverter [toKelvin fromKelvin names])
(def converters [
(TemperatureConverter. (fn [kelvin]     kelvin)                                  (fn [kelvin] kelvin)                                  #{"K" "k"})
(TemperatureConverter. (fn [celsius]    (+ celsius 273.15))                      (fn [kelvin] (- kelvin 273.15))                       #{"°C" "C" "c"})
(TemperatureConverter. (fn [delisle]    (- 373.15 (* delisle (/ 2 3))))          (fn [kelvin] (/ (* (- 373.15 kelvin) 3) / 2))         #{"°De" "De" "DE" "de"})
(TemperatureConverter. (fn [fahrenheit] (+ fahrenheit (* 459.67 (/ 5 9))))       (fn [kelvin] (- (* kelvin (/ 9 5)) 459.67))           #{"°F" "F" "f"})
(TemperatureConverter. (fn [newton]     (+ (* newton * (/ 100  33)) 273.15))     (fn [kelvin] (/ (* (- kelvin 273.15) 33) 100))        #{"°N" "N" "n"})
(TemperatureConverter. (fn [rankine]    (* rankine (/ 5 9)))                     (fn [kelvin] (/ (* kelvin 9) 5))                      #{"°R" "R" "r"})
(TemperatureConverter. (fn [réaumur]    (+ (* réaumur (/ 5 4)) 273.15))          (fn [kelvin] (/ (* (- kelvin 273.15) 4) 5))           #{"°Ré" "°Re" "Ré" "RÉ" "ré" "Re" "RE" "re"})
(TemperatureConverter. (fn [rømer]      (+ (/ (* (- rømer 7.5) 40 ) 21) 273.15)) (fn [kelvin] (+ (/ (* (- kelvin 273.15) 21) 40) 7.5)) #{"°Rø" "°Ro" "Rø" "RØ" "rø" "Ro" "RO" "ro"})
])

(defn getconv [name] (first (filter #(contains? (.names %) name) converters)))

(if-let [[inputScale outputScale & args] *command-line-args*]
(do
(def toKelvin (. (getconv inputScale) toKelvin))
(def fromKelvin (. (getconv outputScale) fromKelvin))
(doseq [arg args]
)
)
)


I seek to understand if there is anything that can be improved, especially if there is some smart way to use a macro to simplify the code that I missed.

Note:

• For now, error handling (user inputs text instead of a number, or an unknown scale) is not of concern.
• I'm aware that my "brace style" with closing parentheses on separate lines is not the usual Lisp/Clojure style that would collect them at the end of the last line. I prefer this way because it causes less noise in diffs in version control.
• I'm aware that the lines defining the temperature converters are long. I usually don't like long lines, but in this case, a tabular layout makes the source code easier to read.

• Use kebab-case for names of record fields: toKelvin fromKelvin -> to-kelvin from-kelvin
• Use different constructor for records: TemperatureConverter. -> ->TemperatureConverter
• All from-kelvin functions have the same name of the argument, so I would also consider rewriting them with anonymous function literal #(...) (and that first one is identity):
(fn [kelvin] kelvin) -> identity
(fn [kelvin] (- kelvin 273.15)) -> #(- % 273.15)
...

• You should access record fields with keywords: :to-kelvin, :from-kelvin, :names...
• Function getconv: you can rewrite this with ->> ("thread-last") macro:
(defn getconv [name]
(->> converters
(filter #(contains? (:names %) name))
first))

• Because your if-let has only one branch, when-let will be better (and with that, you can also remove do).
• Also use kebab-case for names of variables: inputScale outputScale -> input-scale output-scale, toKelvin fromKelvin -> to-kelvin from-kelvin
• def creates global variables and you should use it only on the top-level, use let instead
• You shouldn't use read-string for reading data from an untrusted source. While there is safe equivalent clojure.edn/read-string, I think you're actually looking for parse-double

Main function with all suggested changes:

(when-let [[input-scale output-scale & args] *command-line-args*]
(let [to-kelvin (:to-kelvin (getconv input-scale))
from-kelvin (:from-kelvin (getconv output-scale))]
(doseq [arg args]
(println (from-kelvin (to-kelvin (parse-double arg)))))))


(You can add some more -> or ->> for readability.)