Open adamchalmers opened 2 weeks ago
Would solution 2 support split range? I.e. a low range works and a high range works, but in the middle, it's an error.
let screwDiameter = selectFromRange(holeDiameter, "screw size", [
minimum 0.16,
up to 0.17 => 0.1875,
up to 0.18 => 0.1920,
up to 0.19 => 0.1900,
maximum 0.19
minimum 0.21,
up to 0.22 => 0.22,
maximum 0.22
])
Would solution 2 support variables, or do they need to be constant literals? For example, could I write this?
fn thing = (holeDiameter) => {
let a = 0.16
let increment = holeDiameter / 3
let screwDiameter = selectFromRange(holeDiameter, "screw size", [
minimum a,
up to a + 1 * increment => 0.1875,
up to a + 2 * increment => 0.1900,
up to a + 3 * increment => 0.1920,
maximum a + 4 * increment
])
}
Solution 2 feels very specialized to me. Based on the above, I'm wishing that there were some more primitive concepts that we could build solution 2 out of.
If it were a functional language, the primitives would be pattern matching combined with special underderstanding of how numbers work. You'd pattern match on holeDiameter
, and the implementation could detect that you've covered all the cases, possibly with a catch-all. Maybe we can add syntax sugar if this is common.
let screwDiameter = select(holeDiameter, "screw size", [
0.16 .. 0.17 => 0.1875,
.. 0.18 => 0.1920,
.. 0.19 => 0.1900,
])
In the above, to the left of =>
is any pattern, so the name selectFromRange
doesn't need to be a range anymore, even though we've used ranges here.
The above would be a "compile" error (or whatever phase we can check, ideally before runtime) because it's non-exhaustive.
let screwDiameter = select(holeDiameter, "screw size", [
0.16 .. 0.17 => 0.1875,
.. 0.18 => 0.1920,
.. 0.19 => 0.1900,
_ => error
])
This uses a catch-all to make the match exhaustive. In that case, it evaluates to an error. It's actually a value that select
recognizes and converts into a real runtime error with context of "screw size" and the runtime value of holeDiameter
.
These are all just half-baked ideas off the top of my head.
Motivation
@jgomez720 describes this KCL user story: you're a mechanical engineer trying to design a wheel or something. The wheel needs holes for screws. The wheel is parametric, so its radius is given by the user. The screw size needs to vary with the wheel's radius too.
Right now, the only way to do this in KCL is to make the screw size a function of the wheel radius, e.g.
let screwSize = someRatio * radius
.Problem
Screw sizes are not continuous! Not all numbers are valid screw sizes. In practice, MEs have a finite set of screw sizes to choose from, e.g. a set of 10 screws with known sizes like 0.18. 0.1825, 0.1850, etc etc.
Currently, there's no way to express the logic "if the wheel radius is between M and N, choose screw number 4. If it's greater than N, choose screw number 5."
Solution 1: if expressions
Add a new
if cond { x } else { y }
syntax and AST node. Also addif-else
expressions likeif cond0 { x } else if cond1 { y} else { z }
. This would let the user solve the screw problem:Solution 2:
selectFromRange
The
if-else
chain above has two problems:if
statement is brittle and makes it easy to cause logic errors. It's very sensitive to order, if the user accidentally puts the 0.17 block first, it'll trigger and stop the 0.18 or 0.19 blocks from being evaluated. Or if they get the > wrong and put < instead it'll all get messed up. These errors are hard to notice and unless the user exhaustively tests all possible values, they won't notice the mistake.So we should consider a dedicated syntax for this, as it's likely to be a common pattern. One hypothetical example syntax:
the KCL executor would check that all the selections are in the right order, and that all possible values are covered. It forces users to explicitly consider the minimum/maximum ranges they support. It'd output a nicely-formatted error if the user goes above/below the range, e.g. if you put in 0.11 it'd error with
We'd need some way to distinguish <= and <, e.g. "up to 0.18" vs. "up to but not 0.18"