# Comparing Two Temperatures

It doesn't matter what temperature the room is — it's always room temperature.

— Steven Wright

We're at work, and we are tasked with writing two functions that compare any two temperatures:

- One that gets the minimum value
- One that gets the maximum value

We're not sure why they don't use `Math.min`

and `Math.max`

themselves, but we
do what they ask.

```
const minTemperature = (a, b) => Math.min(a, b)
const maxTemperature = (a, b) => Math.max(a, b)
minTemperature(100, 200) // 100
maxTemperature(100, 200) // 200
```

Once we share that solution, we are reminded that task is to compare
*any two temperatures*; for example, `100`

, `200`

, `'50F'`

, `'25C'`

.

Dangit... now we look silly. Okay, let's try this again to make it handle strings:

```
const minTemperature = (a, b) => a > b ? b : a
const maxTemperature = (a, b) => a < b ? b : a
minTemperature(100, 200) // 100
maxTemperature(100, 200) // 200
minTemperature('200F', '100F') // '100F'
maxTemperature('200F', '100F') // '200F'
```

Then we realize that Ramda has `min`

and
`max`

functions, so we use those and
pat ourselves on the back:

```
import { max, min } from 'ramda'
min(100, 200) // 100
max(100, 200) // 200
min('200F', '100F') // '100F'
max('200F', '100F') // '200F'
```

We go back and tell our team to use Ramda's functions, but we're wrong again, and we realize that we probably should have asked for a thorough list of inputs.

But what could we have missed?

Compare any two temperatures.

Oh, no... They might not be part of the same measurement system! This
means that `'50F'`

could be compared with `'15C'`

! Luckily, we're told that we don't
have to worry about Kelvin, Rankine, nor Réaumur; we only care about Celsius
and Fahrenheit. There will also *never* be any unit-less numbers, so we can
ignore that case.

This problem just took on a whole new dimension!

Good news: in the last
chapter, we wrote
our `celsiusToFahrenheit`

and `fahrenheitToCelsius`

functions, so we have those
functions we can use to do those calculations for us.

Let's try again:

```
import { max, min } from 'ramda'
const celsiusToFahrenheit =
compose(add(32), multiply(divide(9, 5)))
const minTemperature = (a, b) => {
const unitA = a.slice(-1) // get the last character, like C or F
const unitB = b.slice(-1)
const floatA = parseFloat(a)
const floatB = parseFloat(b)
// same temperature type
if (unitA === unitB) {
const maxT = min(floatA, floatB)
return maxT === floatA ? a : b
}
// a is Fahrenheit but b is Celsius
if (unitA === 'F') {
const floatBAsF = celsiusToFahrenheit(floatB)
const minF = min(floatA, floatBAsF)
return minF === floatA ? a : b
}
// a is Celsius but b is Fahrenheit
const floatAAsF = celsiusToFahrenheit(floatA)
const minF = min(floatAAsF, floatB)
return minF === floatB ? b : a
}
const maxTemperature = (a, b) => {
const unitA = a.slice(-1) // get the last character, like C or F
const unitB = b.slice(-1)
const floatA = parseFloat(a)
const floatB = parseFloat(b)
// same temperature type
if (unitA === unitB) {
const maxT = max(floatA, floatB)
return maxT === floatA ? a : b
}
// a is Fahrenheit but b is Celsius
if (unitA === 'F') {
const floatBAsF = celsiusToFahrenheit(floatB)
const maxF = max(floatA, floatBAsF)
return maxF === floatA ? a : b
}
// a is Celsius but b is Fahrenheit
const floatAAsF = celsiusToFahrenheit(floatA)
const maxF = max(floatAAsF, floatB)
return maxF === floatB ? b : a
}
minTemperature('200F', '100F') // '100F'
maxTemperature('200F', '100F') // '200F'
minTemperature('50F', '25C') // '50F'
maxTemperature('50F', '25C') // '25C'
```

(View this large min/max temperature example in the Ramda REPL)

Goodness gracious! While we could extract some common functionality into many small functions, there must be a simpler way. After all, isn't functional programming supposed to help us simplify things?

(Hint: yes!)

Ramda has `minBy`

and
`maxBy`

functions that will compare two
values *after* they have been transformed by some transformation function.
Here's their example from the docs:

```
import { maxBy } from 'ramda'
const square = n => n * n
maxBy(square, -3, 2) // -3
```

In this example, `maxBy`

will call `square`

with `-3`

and `2`

, and it will then
compare each of *those* results. Whatever value has the largest result after
being applied to `square`

will be the returned value. Here, `-3 * -3`

is `9`

,
whereas `2 * 2`

is `4`

, so since `9 > 4`

, `-3`

is our result.

Let's refactor our functions:

```
import { maxBy, minBy } from 'ramda'
const asF = x => {
if (x.slice(-1) === 'C') {
return celsiusToFahrenheit(parseFloat(x))
}
return x
}
const minTemperature = (a, b) => minBy(asF, a, b)
const maxTemperature = (a, b) => maxBy(asF, a, b)
minTemperature('200F', '100F') // '100F'
maxTemperature('200F', '100F') // '200F'
minTemperature('50F', '25C') // '50F'
maxTemperature('50F', '25C') // '25C'
maxTemperature('50C', '25C') // '50C'
```

(View this v1 minBy/maxBy temperature example in the Ramda REPL)

By casting all temperatures as a single unit (I chose Fahrenheit here), we can
compare any temperatures and get back their original values! `25C`

is indeed
hotter than `50F`

!

But wait – there's more.

Just like we noticed in the last chapter, our `minTemperature`

and
`maxTemperature`

functions are taking in `(a, b)`

and are merely forwarding
those arguments on. If you recall, Ramda doesn't care *when* you provide
arguments, so check this out...

```
// before
const minTemperature = (a, b) => minBy(asF, a, b)
const maxTemperature = (a, b) => maxBy(asF, a, b)
// after
const minTemperature = minBy(asF)
const maxTemperature = maxBy(asF)
```

(View this v2 minBy/maxBy temperature example in the Ramda REPL)

Oh, and one more thing; here's a preview of some upcoming chapters' content
retroactively applied to making our `asF`

function cleaner:

```
// before
const asF = x => {
if (x.slice(-1) === 'C') {
return celsiusToFahrenheit(parseFloat(x))
}
return x
}
// after
// Celsius = String // "100C"
// Fahrenheit = String // "100F"
// Temperature = Celsius | Fahrenheit
// isC :: Temperature -> Bool
const isC = compose(equals('C'), slice(-1))
// stringCToF :: Celsius -> Number
const stringCToF = compose(celsiusToFahrenheit, parseFloat)
// asF :: Temperature -> Fahrenheit
const asF = when(isC, stringCToF)
```

And here is what all of the new functions together could look like!

```
// Celsius = String // "100C"
// Fahrenheit = String // "100F"
// Temperature = Celsius | Fahrenheit
// isC :: Temperature -> Bool
const isC = compose(equals('C'), slice(-1))
// stringCToF :: Celsius -> Number
const stringCToF = compose(celsiusToFahrenheit, parseFloat)
// asF :: Temperature -> Fahrenheit
const asF = when(isC, stringCToF)
// minTemperature :: Temperature -> Temperature -> Temperature
const minTemperature = minBy(asF)
// maxTemperature :: Temperature -> Temperature -> Temperature
const maxTemperature = maxBy(asF)
```

(View this v3 minBy/maxBy temperature example in the Ramda REPL)

Cool, right?

Fear not! We'll cover `when`

in the
"Unshakeable Logic" section, `slice`

in the
"All About ['L','i','s','t','s']" section, and the pseudo-type signatures in the
"Core Ramda Ideas" section, but you should know the pseudo-types are optional,
not exact, and aren't a substitute for tools like jsdoc.

## Wrapping Up

The person who requested these functions is going to be blown away!

What started as a simple "which temperature is smaller or larger?" question turned out to be an exercise in asking good questions about requirements up front.

We also walked through implementing a naïve solution and refactored it to a more elegant one by leveraging Ramda and functional programming.