Ask HN: What piece of code/codebase blew your mind when you saw it?
Mine was when I learned a subset of recursion called mutual recursion. It was for a pair of function to determine if a number was odd or even.
(define (odd? x)
(cond
[(zero? x) #f]
[else (even? (sub1 x))]))
(define (even? x) (cond
[(zero? x) #t]
[else (odd? (sub1 x))]))
295 comments
[ 4.0 ms ] story [ 273 ms ] threadNow, if it had been declared int, I would probably have been on the lookout for it using something other than 1 or 0 as an error condition, but why bother to explicitly invent a BOOL type and then abuse it that way?
Amazing that someone can write correct, fast code like that.
"The formulae... defeated me completely; I had never seen anything in the least like them before. A single look at them is enough to show that they could only have been written by a mathematician of the highest class. They must be true because, if they were not true, no one would have the imagination to invent them."
Years later I could tell my colleagues how a device driver/plugin actually worked (before COM/etc) just with this.
- https://en.wikipedia.org/wiki/Tower_of_Hanoi#Recursive_imple...
I remeber thinking "where's the rest of the code? It surely can't be just those lines!"
My mind is always blown by the tiny demoscene demos.
This is not true with a good compiler that does, e.g., tail call optimization, or is it?
Even in languages where TCO is supported, it's usually not safe to rely on from an engineer's perspective because there is usually no way to reliably assert that TCO is actually applied to any given function.
And again, when they teach recursion in school, they don't normally tell you that it can be unsafe unless your compiler happens to apply TCO or your language explicitly supports tail recursion.
A lot of languages do support TCO. For example, Scala (used by Twitter), OCaml (used by Jane Street for everything, used as the host language for Coq, originally used to implement the Rust compiler), Kotlin, Haskell, Clojure, Lua (used as an embedded language in many places), Elixir, Perl. Not necessarily your popular bread and butter languages but definitely used in production and available if necessary.
Your point that it is generally difficult to know whether TCO is applied is also well-taken. But in OCaml, you can annotate the recursive function call with `[@tailcall]` to verify that the compiler performs TCO.[1] Likewise, you can annotate your functions in Scala.[2] In languages without such annotations, one can get a sense by memory profiling (possibly not emphasized enough in those intro CS courses).
[1] https://v2.ocaml.org/manual/tail_mod_cons.html
[2] https://www.scala-lang.org/api/2.12.1/scala/annotation/tailr...
For modern computers/tablets I have never experienced an issue. Granted, what really matters is your data/recursion level, but even hundreds of recursive calls are not a problem for most applications.
Recursive solutions are usually so much easier to understand than stack/array based looping solutions that they are my go to for things like tree traversals or searching.
A very subjective statement. Yes, your data matters. Also, the scale and security model/trust boundary of your application matters.
Some actual problems with recursion based algorithms:
* They will break unexpectedly as you scale them up.
* They are not memory efficient, so you won't be able to process much in parallel if your data is non-trivial.
* They can make your service trivially DoSable, so now you have to worry about either sanitizing your input or monitoring your stack instead of just timeouts/rate limits.
I've lost track of the number of times I've had to tweak JVM settings, write up security issues, or just straight up tell people things won't work because an academic researcher decided to implement an algorithm with recursion, and then engineers were asked to productize it.
If A is less than B it calls itself with the terms reversed.
I was so surprised, since I had learned the other way first that it is actually easier to teach functional approaches with teenagers.
arr.map(x => x2) was abundantly clearer than
const newArr = [] for(let i, i < arr.length, i++) { newArr.push(arr[i]2) }
And to be perfectly honest, I might have goofed the for loop.
Yeah, you did! It should have used semi-colons. (And HN has lost the carets …)
And I agree completely with you.
And to be perfectly honest, I might have goofed the for loop.
You did, the commas must be semicolons :)
I can't tell you how much time I spent chasing a bug when it was a date that was passed by reference.
Rather than trying to keep track, I just return copies of arrays and objects and it's way easier.
Even a simple
``` function yeppers(input) => ({...input, x: 7}) ```
(An overly simple example)
has made things smoother.
Ok… it’s Footgun prone and callback hell is real.
But I still try to obtain that in other more boring language.
If you tried something like c = (('d','e','f'),('g','h')) and then a,b = zip(*c) you'd get a = ('d','g') and b = ('e','h'). The 'f' that was originally at c[0][2] is dropped so you can't zip back to the original value of c, unless that's how inverses work for this scenario and my math is rusty.
Instead you should use the Sieve of Eratosthenes algorithm. Unfortunately this algorithm is easier to implement in an imperative, eager fashion than in a lazy, functional fashion.
See the paper "The Genuine Sieve of Eratosthenes" for more details: https://www.cs.hmc.edu/~oneill/papers/Sieve-JFP.pdf
isPrime is a function that checks that x is a prime number by taking numbers whose squares don't exceed x from... primes list... and then making sure they all divide x with a reminder.
It only works because isPrime never touches an unevaluated element of primes list. Otherwise, the program would loop.
So, that.
There was a lot of code, about 1.5 million lines over two version of Oracle, which included a bunch of applications as well as the RDMS.
Always love blog posts that cite Knuth.
Then I read it was about loop unrolling, and suddenly it clicked.
arr[index] is equivalent to index[arr]
This works because arr points to the memory location of the zeroth index of the array and index is the offset from that memory location. Since addition is commutative so too is the array notation.
edit: fixed escaped asterisks :-)
Contestants in the International Obfuscated C Code Contest will (and probably already have) find delightful ways to use and abuse this syntactical curiosity.
[0] My one-track mind always wants to jump to pirate jokes.
EDIT: If you like to explore this area, I highly recommend "Expert C Programming - Deep C Secrets" (get the pun? There's a fish on the cover...) by Peter Van Der Linden. It's pretty old (it talks about DOS memory models, among other things), but it is a fun deep dive into the murky corners of C. I wish there was an updated version and/or books covering other languages in that style.
The OCCC is where I learned about this in the first place!
Surely it counts as useful if it could be used to confuse The Enemy. ;)
This is up there for me or there's also that amazing recursive template class for n-dimentional vectorspaces written by Tim Sweeney. I can't find the page anymore but there is reference to it in the archive https://www.flipcode.com/archives/Unrolling_Loops_With_Meta-...
In addition to just the basic loop-unrolling (which I'm pretty sure you usually don't need to do by hand with modern compilers), it works really well when you need to jump into the middle of a pattern. Like if you're sieving primes in a wheel-factorized array.
[1] https://github.com/patricksjackson/primes-cpp/blob/master/pr...
*to++ = *from++;
(define (cons x y) (lambda (m) (m x y)))
(define (car z) (z (lambda (p q) p)))
(define (cdr z) (z (lambda (p q) q)))
Then if you really want to go down the rabbit hole, there's SKI, which is lambda calculus without a lambda operator, just 3 named, curried functions(S, K and I, but I is techically definable in terms of S and K). Any function that can be expressed in lambda calculus can be expressed in terms of calling these various functions with eachother as arguments.
Assuming LAMBDA is automatically curried, to save myself some typing:
Alternatively, I can be defined as follows: SKI is implemented in the Unlambda esolang, with some additional functions defined for IO and convenienceIf variable environments are implemented as hash tables, you're just making a pair out of a hash table. In other words, it's basically equivalent to:
awk '!seen[$0]++'
https://m.youtube.com/watch?v=HxaD_trXwRE
I'm a total .NET noob, but this attempt in Compiler Explorer shows it not using the xor trick.
https://godbolt.org/z/jhzqxEKzT
Maybe I need some extra compiler flags or something.
If the compiler had decided to put two locals located already in registers, and had the swap in a branch, like so:
Then the registers might get swapped with an xchg instruction or something, I don’t know. The compiler’s goal is to have pipelined execution be as fast as possible, so there’s no way it’s going to use bit-mangling operations that would get in the way of that.It better not. The CLR trick introduces a data dependency that slows down the code on modern CPUs.
Compilers sometimes also can compile this down to zero instructions. Nothing says the variable-to-register mapping has to be constant in a single function.
But I guess what really happened is that when you come up writing assembly code at a low level it's a lot more obvious when you've been neck deep in bits, shifts, and logic ops all day.
https://medium.com/hard-mode/the-legendary-fast-inverse-squa...
also love the story behind it.
http://www.lomont.org/Math/Papers/2003/InvSqrt.pdf
It's a library to read/write java class files. The core at that time would fit into 2 java files.
It was a reader and a writer built around a visitor pattern. At that time apache bcel was also big, a library to build in-memory class file representations which you would then manipulate. ASM allowed you to do many transformation without ever holding the whole file in memory.
ASM also had an in-memory representation as an add-on. It was all nicely layered and the core was just a marvelous small piece of code, with incredible low memory consumption.
https://www.shadertoy.com/view/MdB3Dw
Well, the code itself is simple GLSL. The math is (and I have not verified this) not that complex either, as I believe it's all about integrating the raytracing equation for a moving sphere over the time variable. I guess what I find most impressive is just the fact that it works and that someone even thought of doing that. I also think this is currently the only example of this method being used anywhere, and it might mostly stay that way because the method might not be super generalisable.
https://www.youtube.com/watch?v=8--5LwHRhjk
Also you may like Marble Marcher made by Code Parade, it's a game that uses a similar approach to rendering: https://youtu.be/9U0XVdvQwAI
In all of the samples there is some iteration constant you can adjust. Take this below a certain threshold and things get really interesting. You can almost reason about how it's all working mathematically by plugging in different iteration counts.
In fact so much of what is on shadertoy looks very familiar to video game 'game physics' programmers because a lot of it is related to collision detection. See the Orange book on real-time collision detection (Christer Ericson) for a lot of good code.
Before warching Casey program, it had not occurred to me you could sit down with a compiler and write an entire video game from scratch.
Then, I found out Jonathan Blow sat down and wrote his own compiler, followed by an engine, and now has a game in flight.
It's a truly incredible what these guys (and their teams) get done.
Last time I checked (a couple of months ago) there wasn't even a formal language spec yet.
I'm working on trying to reach their level (it is reachable) but I sometimes feel like I started with a handicap of comfort from modern OSs and tools
Specifically, Roller Coaster Tycoon, hand-written in x86 assembler by Chris Sawyer.
Edit: guess I could just download it and disassemble ;)
What impresses me though are old Atari games that were implemented in hardware without a microprocessor.