6 ms·
This is an awesome post. At the risk of shedding it to bikes, one point that the author makes is that Zig's lack of operator overloading makes him write vector
by skrebbel 7y ago
This is an awesome post.
At the risk of shedding it to bikes, one point that the author makes is that Zig's lack of operator overloading makes him write vector math like this:
if (discriminant > 0.0) {
// I stared at this monster for a while to ensure I got it right
return uv.sub(n.mul(dt)).mul(ni_over_nt).sub(n.mul(math.sqrt(discriminant)));
}
He signs off with:
> How do C programmers manage?
The answer is simple: we assign names to intermediate results. Now, I have absolutely no idea what that expression computes, because I suck at graphics programming and math in general. Please pretend that these are proper mathy terms:
if (discriminant > 0.0) {
const banana = uv.sub(n.mul(dt))
const apple = banana.mul(ni_over_nt)
const pear = n.mul(math.sqrt(discriminant)
return apple.sub(pear)
}
I'm convinced that there's a proper mathy/lighting-y word for each component in that expression. Of course this approach totally breaks down if you're copying expressions from papers or articles without understanding why they are correct (which is how I do all my graphics programming). I do find that naming variables is often a great way to force myself to grok what's going on.
- geokon 7y agoIn my experience writing math code the intermediary values get quite goofy Ex: orthogonal-vector-to-plane-bisecting-input-vector-and-first-column-vector But I rather use crazy descriptive names than hiding it away. Otherwise it gets quite incomprehensible when you reread it 3 months later Anyone else hit this problem? I suspect most people just reference a paper or book and use the letters to match the source ( x/y/n/m/etc. )
- skrebbel 7y agoYep, agree! I think extremely long variable names (when used locally), if necessary, are wildly underrated. I mean, we all know horror stories like SimpleBeanFactoryAwareAspectInstanceFactory, but they are really design problems much more than naming problems. They gave long variables a bad rep, undeservedly so. Inside an expression, names like that truly do wonders.
- dagw 7y agoI suspect most people just reference a paper or book and use the letters to match the source ( x/y/n/m/etc. ) If you do this (and I'll certainly admit that I do this as well) make sure you link to the paper in question somewhere in either comments or the documentation so that future developers can find the canonical reference to what the variables mean.
- forrestthewoods 7y agoThat assumes there is a geometrically meaningful description. A lot of times a computation has no intrinsic meaning. Algebra is going to rearrange and cancel terms into nonsense. A long name can tell you _what_ a value is. But it’s of zero use in explaining _why_ it’s being used. The only solution is a lengthy comment explaining the process. Here some ballistic trajectory code I wrote awhile back. Good luck making sense of any single term! https://github.com/forrestthewoods/lib_fts/blob/26a0d115eb44495d0af30948397b11becd2e8e0a/code/fts_ballistic_trajectory.cs#L343 https://github.com/forrestthewoods/lib_fts/blob/26a0d115eb44...
- socialdemocrat 7y agoI think people's brains must work different, because for me this would be a terrible way to do it. I simply cannot read and comprehend math with very long descriptive variable names. Whenever I see people do that, I have to write down the equation with single letters, and then look at it. I am more of a literate programming type of person. I prefer writing longer explanations of code. But usually as a header. I like keeping the core of the code as clean and noise free as possible. So I write code more like a math or physics book I guess. The equations are kept simple and clutter free, and then there is a body of text above or below explaining how to think about it. I tend to prefer using a lot of unicode, because following conventions helps me a lot. If I see a t₀, t and Δt variable e.g. I immediately get a sense of what sort of variables this is and how they are related. If instead it said start_time_of_incident, current_time and time_difference_between_events I could not quickly parse and internalize that.
- geokon 7y agoYeah, I feel you on that and I started out writing in a similar style b/c it's terse and easy to digest. To be clear, this is mostly a hobby things for me so I'm not speaking from a position of authority, but in my limited experience once the problem gets bigger and you worry about performance/caching/recursion/etc. the math and equation start to diverge from the code substantially. For instance over the last few months I've been playing with BLAS and the BLAS operators just don't really map cleanly to equations - and the way you structure efficient solutions just ends up being more nuanced and complicated than the simple clean "theoretical" on-paper solution. All the nuances need to be captured in the variable names to have any chance of making the code understandable
- Fronzie 7y agothe distance between BLAS and mathematics is quite big. Have you had a look at, for example, the Eigen library? Although it's still more verbose than math notation, it stays much closer and, in my experience, does allow for nearly all the optimization-wiggle-room one could want.
- 0815test 7y agoThe most principled approach there, I think, would be to build a little Expression data-structure, and then feed it to an evaluation routine, trusting the optimizer to compile the whole thing down to something efficient. If Rust didn't have operator overloading anyway, you could do the job with procedural macros. In practice, if I had to write that quasi-monstrosity in something like C, I'd probably just comment it as clearly and liberally as possible, to the point where I manage to reassure the reader that the final expression is correct; and then add a // See above: Please DO NOT edit this expression directly!// comment as an extra caution.
- BubRoss 7y agoWhy would you need to do that with small vectors? The compiler is going to do that anyway. Eigen originally took this approach because it could avoid heap allocating temporary variable before C++ had move operations. This isn't applicable here.
- tjelen 7y agoSo why not just write the original formula into the comment above the code? Maybe it's not the purest approach, but it sure would help parsing the code in this case.
- minxomat 7y agoYep. If the code doesn't clearly show the original intent, document your intent in a comment next to the code so future you or others can double check. Esp in gfx engines where some blocks are just math translated one to one to code.
- pjc50 7y agoWhy not one step further, and let the original formula be the code? Directly compilable Latex: \vec r = \frac {n_{1}}{n_{2}} \cdot (\vec v - dot(\vec v, \vec n) \cdot \vec N) - \sqrt{1 - \frac {n_{1}^2}{n_{2}^2} \cdot (1 - dot(\vec v, \vec n))} \cdot \vec N (This is partly a joke and partly a serious suggestion, there are definitely people out there who would find that easier to write, especially if the IDE rendered it properly. I stole it by searching for ni_over_nt and finding http://viclw17.github.io/2018/08/05/raytracing-dielectric-materials/ http://viclw17.github.io/2018/08/05/raytracing-dielectric-ma... )
- OoooooooO 7y agoYou could probably do it that way in Julia.
- ddragon 7y agoJulia (a language focused on science/math) does it partially. For example in the editor or REPL you can just type \sqrt [tab] \alpha [tab] and it will generate √α which is valid code. Most math symbols in unicode are supported and overloadable. And you can also write matrix like a table, such as [1 2 3 4 5 6 7 8 9]. And you can transform Julia expressions to LaTeX: https://github.com/korsbo/Latexify.jl https://github.com/korsbo/Latexify.jl But even they didn't go as far as being able to parse full LaTeX expressions (though you technically could with reader macros plus editor support).
- drcongo 7y agoThis is how I've been helping my daughter understand some maths problems at school. Break it down and name everything, then write the sum with the names in and it makes sense.
- dahart 7y ago> I’m convinced that there’s a proper mathy / lighting-y word for each component in that expression. Sometimes yes, but often no. Frequently this kind of expression is the result of solving an equation, so it’s just an expression. Graphics people often use two approaches for sub-expressions: - You can name them with the same letters that are in the expression, just with the punctuation & operators removed, for example: const uvMinusNTimesDt = uv.sub(n.mul(dt)) - Alternatively, just like with equations, math people often freely assign single letter names to variables without worrying about semantic meaning. const q = uv.sub(n.mul(dt)) Nothing really wrong with naming sub-expressions after fruits, or single letters, or spelling them out explicitly. It might be worth reflecting on what the goals are with your naming, and whether it matters what they’re named. As software engineers, our biases lean toward making choices that improve readability and maintainability. But for a specific equation that will never change once it works correctly, our preconceived notions about good software design and best practices might not actually apply to this situation. It might be more important to document the source of the equation than to make the implementation readable.
- fiddlerwoaroof 7y agoPeople say “the two hardest problems in computer science are cache invalidation, naming and off-by-one errors” and we often design systems so that we don’t have to think about cache invalidation and off by one errors (e.g. list.map rather than a for loop). I often wonder if we should think about minimizing how often we name things as well.
- Skunkleton 7y agoAren’t features like if-expressions just that?
- dahart 7y ago> I often wonder if we should think about minimizing how often we name things as well. Personally, I think that depends, so I like to challenge my own assumptions and examine things case by case. The implementation of a math equation, especially one that is the result of solving a different equation, and so went through a symbolic/algebraic transformation, that is one place where adding more names might not clarify the implementation. In general, though, my personal experience has been one of not seeing things named enough. Specifically, feature creep in software tends to try to add code with a light touch without disturbing too much of the surrounding code, but with new concepts that deserve more naming than they get. This can and does lead to code that is difficult to maintain, and I think I’ve seen more of that mistake than of naming things too much, and I suspect it’s much easier to not just shoot yourself in the foot, but cut your leg off, by under-naming rather than by over-naming.
- mhh__ 7y agoThe fruity method should be preferred: Any compiler worth using will immediately eliminate/register-allocate the variables and now you don't have undebuggable spaghetti code