Dividing ponies
A lot of UX considerations can go into a programming language. Every constraint you put in place, due to your design goals, will have vast repercussions.
I decided that in my programming language, currently named “tenecs” (more on that some other day), I don’t want to have things that shortcircuit the call stack. What I mean is I don’t want to have the control flow that is usually enabled by keywords such as “panic” in Golang or “throw” in Java, Javascript and many others.
If you can’t throw, how should division by zero work?
Mathematically it’s not defined. So it shouldn’t be a supported operation. But I don’t have a non-zero integer type. Division is defined over the `Int` type the same way `plus` and `times` are.
What do we do when a function can technically take a value as input that is not a valid input? In a language where errors aren’t thrown, the error is returned. But in the case of division, maybe it will feel clunky and unnecessary? When doing math operations in programming we expect to be able to chain them and not have to deal with intermediate error handling.
Gladly, I’m definitely not the first one to stumble upon this problem!
The Pony programming language had a similar problem and has a section on their tutorial dedicated to Divide by Zero:
In Pony, integer division by zero results in zero
Inspired by their approach, I decided to support both normal “div” and “ponyDiv”:
package example
import tenecs.error.Error
import tenecs.list.fold
import tenecs.list.length
import tenecs.int.div
import tenecs.int.ponyDiv
import tenecs.int.sum
// Average that returns an error when you pass an empty List<Int>
avg := (values: List<Int>): Int | Error => {
val total = fold(values, 0, (acc, elem) => { acc + elem })
div(total, length(values))
}
// Average that returns zero when you pass an empty List<Int>
ponyAvg := (values: List<Int>): Int => {
val total = fold(values, 0, (acc, elem) => { acc + elem })
ponyDiv(total, length(values))
}
If you prefer reading code with pipe syntax, the language supports it using “->”. Here’s the same code using that syntax:
package example
import tenecs.error.Error
import tenecs.list.fold
import tenecs.list.length
import tenecs.int.div
import tenecs.int.ponyDiv
import tenecs.int.sum
// Average that returns an error when you pass an empty List<Int>
avg := (values: List<Int>): Int | Error => {
val total = values->fold(0, (acc, elem) => { acc + elem })
total->div(values->length())
}
// Average that returns zero when you pass an empty List<Int>
ponyAvg := (values: List<Int>): Int => {
val total = values->fold(0, (acc, elem) => { acc + elem })
total->ponyDiv(values->length())
}
Given either div or ponyDiv it's possible for users to implement the other, so I might drop one of them at some point. But for now, users have the choice.