<sub>2025-03-11 @13:16</sub>
#digital-garden #lua #programming #ieee-754 #metamethods
# Studying Lua's Relational Metamethods
Metatables allow us to give meaning to relational operators through metamethods. It's not exactly the same as operator overloading in C++ but very similar.
An interesting rabbit role can pop up when you realize there are no metamethods for `a ~= b`, `a > b` , `a >= b` and start realizing why. Instead Lua supports them through their equivalent translation.
| Not Supported | Equivalent Translation | Equivalent Metamethod |
| ------------- | --------------------- | --------------------- |
| `a ~= b` | `not (a == b)` | `__eq` |
| `a > b` | `b < a` | `__lt` |
| `a >= b` | `b <= a` | `__le` |
Seems reasonable but why not just go the extra mile to support `>`, `>=` and `~=`? The **TLDR**, Lua is designed to be **simple**! Instead of forcing you to define six different metamethods, it tries to minimize the work you have to do.
### So how does Lua help minimize the work then?
For each of the supported metamethods (e.g. `__lt`, `__le`, `__eq`), Lua can automatically use it to get the equivalent of `>`, `>=` and `~=`.
So Lua doesn't need separate metamethods for these "opposite" operators. It can just flip the logic thus getting all six for free.
There's a short but fun rabbit hole to go down when you look at the translations Lua makes here specifically with older versions of Lua (e.g. 4.0). You'll learn that `__le` was not supported but `__lt` was and the incorrect translation Lua 4.0 (and earlier) had.
# Young Lua (v4.0 and earlier)
In Lua 4.0, I learned that `__le` metamethod was not present. Lua would instead automatically translate it as being the same as `not (b < a)`. It just assumed that `<=` was opposite of `<` thus used it to compute `<=`.
### What Lua v4.0 (and earlier) got wrong
Per IEEE-754, the floating-point standard, defines NaN (Not a Number) as a special value that results from undefined mathematical operations like ...
$\frac{0}{0}$
$-1$
$-(-1)$
$\infty - \infty$
A key rule in IEEE 754 is that NaN is never comparable to anything, including itself. This means the following.
$\text{NaN} < x \quad \Rightarrow \quad \text{false}$
$\text{NaN} > x \quad \Rightarrow \quad \text{false}$
$\text{NaN} = x \quad \Rightarrow \quad \text{false}$ (yes, even NaN == NaN is false)
Note, this evaluation is incorrect but reflects what Lua 4.0 is doing. Per IEEE-754 $(\text{NaN} \leq x)$ should always be false. However, Lua's transformation incorrectly turns it into true.
$a \leq b \quad \Rightarrow \quad \neg (b < a)$
$\text{NaN} \leq x \quad \Rightarrow \quad \neg (x < \text{NaN})$
$\text{NaN} \leq x \quad \Rightarrow \quad \neg (false)$
$\text{NaN} \leq x \quad \Rightarrow \quad true$
This breaks correctness of floating-point numbers. So, Lua stopped auto-translating `<=` to `<` and now w/ Lua 5.0+ requires explicit handling for each.
We can download a copy of Lua v4.0 and compile it from source to confirm the above. The example shows how Lua v4.0 gets the translation of `<=` from `<` wrong.
```bash
# To install v4.0 ...
wget https://www.lua.org/ftp/lua-4.0.tar.gz
tar -xvzf lua-4.0.tar.gz
cd lua/
make
cd bin/
./lua -v
```
```Lua
-- Lua 4.0 gets the translation of `<=` from `<`
-- This is the problem thus incorrect per IEEE-754
print(0/0 <= 1) -- true
-- Lua 4.0 will get this right
print(not (1 < 0/0)) -- true
```
# Lua v5.0+
Great, so what should we take away from this. We should understand that ...
- Lua 5.0+ supports three metamethods (e.g. `__lt`, `__le`, `__eq`) that we (as programmers) can explicitly overload.
- Lua 5.0+ can correctly translate the three supported metamethods to support the other three missing ones (e.g. `>`, `>=`, `~=`).
- Lua <=4.0 incorrectly translated `<=` from `<` which violated the IEEE-754 floating-point standard when values like `NaN` came into play making the metamethod `__lt` flawed.
Today if we want to use `__lt` and `__le` metamethods (in Lua 5.0+) we must be explicit like the following.
```Lua
local mt = {}
mt.__lt = function(a, b)
return a.value < b.value
end
mt.__le = function(a, b)
return a.value <= b.value
end
local A = { value = 10 }
local B = { value = 20 }
setmetatable(A, mt)
setmetatable(B, mt)
print(A < B) -- true (calls __lt)
print(A <= B) -- true (calls __le)
print(B > A) -- true (flips __lt)
print(B >= A) -- true (flips __le)
```
Probably boring to most, but if you enjoy the history of programming languages, it's interesting to see how they evolve. Time for some blueberry pancakes.
Oh, and about the title ...
- A **total order** means any two elements a and b are always comparable where one is less, greater, or equal to the other.
- A **partial order** means some elements **can’t** be compared at all, like **NaN** 🙂.