I wish I still had one of the earliest programs I wrote. It would have been 1996, I was 16, and I had a linear algebra book which had an appendix entry on computer graphics. I had taken a programming class the prior quarter.
I got hooked writing a program to draw rotating wireframes of a few different shapes. I almost failed the class because I was so distracted by this.
I didn't know about arrays yet. Every vertex was its own variable with a hardcoded default. Every entry in the rotation matrices was its own variable. The code to do matrix multiplication therefore didn't have loops, just a long list of calculations. Which had to be duplicated and adapted to every vertex.
I did know about pointers, I had to draw to the screen after all, which was done by writing to memory starting at a known address. So at least I had loops rasterizing the lines between vertices.
Which means I definitely had the concept of arrays and indexing into them, I just didn't know enough to make my own.
Same. When I was 12ish trying to write a Pac-Man game in basic, I dreaded writing the logic for the 4 ghosts. I'd need separate code for the ghosts at (x1,y1)....(x4,y4) and said to my dad it would be cool if I could use xn and yn inside a for loop, but those are just 2 more variables. I wanted n to indicate which one. This seemed kind of weird, but it's what I really really wanted. He grabbed the little basic book and showed me that x(n) was actually a thing!
I think back to this in discussions about teaching. Abstract concepts are best understood when the student has a legitimate need for them. You can explain something all day and have their eyes glaze over wondering why something is important, but if it solves a problem they have it will click in seconds or minutes.
This comes up pretty often in (well-designed) video games. Say you have colored doors that can only be opened by keys of the same color. If you put the blue key on the path to the blue door, then players will never learn that you need the color of the key to match the color of the door. But if you put the blue key on a side path and have the player encounter a blue door relatively early on, then they will encounter the door first and cannot progress until they find the key. The frustration at not being able to progress is much more effective at teaching the player about the "colored keys for colored doors" mechanic than any amount of explaining you could have done.
When I started programming at 12-13 I had no idea loops or arrays existed and implemented an absolutely criminal Tic-Tac-Toe game.
The same year me and a friend were studying for the national Informatics Olympiad and we were completely stumped when faced with a problem that required a list: "How do we know how many variables to declare if we don't know the N beforehand?".
(The problem if anyone is interested: https://neps.academy/br/exercise/384. Given a list of up to 50k distinct numbers (with values up to 100k) and another list of 50k numbers that should be removed, print the resulting list. Time Limit: 1 second.)
This seems to be a somewhat common occurrence. When I was first learning programming with python I wanted multiple balls in a pong-like game, so after some googling I ended up writing something like `globals()["ball" + i]` until someone mentioned that arrays were a thing I could use for that.
The obvious solution here is to use the lower part of the screen as working memory while drawing the upper part. By the time you get to the bottom you shouldn't have many calculations left anyway. This has the benefit of using fast gpu ram and is therefore cuda and very ai.
It reminds me of one of my earliest ventures into freelancing, where all I had was a tiny VPS that supported PHP. I had to process a large spreadsheet which had maybe 5/10k entries in it. Small potatoes these days, compared to that time in 2002/2003.
I wasn't a computer scientist so I read the file in the dumbest way possible, but kept getting errors about memory usage and running out of space since I was looping within loops within loops. I went through it and added `$variable = null` at every opportunity. Lo and behold it worked.
I did something similar for my big middle school hit, a TI-83 version of Snake. Every x and y position of every segment of the snake went into a separate variable. The snake could only get so long because TI-83 BASIC only gives you so many variables.
IIRC I think it only gave you a fixed number of arrays, too, though I think they could get pretty long so you could probably have done that.
Also it’s rad that you were programming the TI-83 in middle school. I learned TI BASIC in high school for both fun and academics. (I created programs to solve each kind of trig problem we were assigned complete with shown work, with the full blessing of the teacher who could see that understanding was obviously a prerequisite to being able to code a solver.)
The first part of GWBasic I learned by asking someone for help was "chain", after having taught myself print, input, if, and goto from the documentation.
I remember there were 3, and that there was a cube and a pyramid. I can't recall the third. Maybe I did both a square pyramid and a triangular pyramid.
Welcome to the world of optimization the hacky way on an old computer, where code is data is code, and the only truth is: does it render in as many frames as possible? ;)
Usually, you cannot achieve this by engaging in academic code with recursion and loops; you have to do the opposite: trade readability and compact code for speed.
Sometimes, code for 6510/68000 is so optimized - usually unrolled - that you get code patterns that resemble data applied at different frames, much like your code.
There is nothing wrong from my point of view. ;)
(Some words of wisdom: May the debug gods be with you on this journey, though.)
Does Go default-initialize booleans by any chance? In C, which author had used, this program would be undefined behavior due to reading non-static, uninitialized bool.
This approach is perfect for the is-even npm package[1], with 196,023 weekly downloads, or the is-odd npm package[2], with 285,501 weekly downloads. Wouldn't it be cool if you type `npm install` and it starts downloading a 40GB is-even and a 40GB is-odd?
It's always worth mentioning that those packages stem from the effort of one dedicated npm spammer[1] to weasel his way into as many node_modules directories as possible. There's also packages for ansi-colors (no, not one package for all colors, one package for each color) and god knows what else. These then get jammed into any cli tool or other half-way sensible looking package, all referencing each other obviously - and any "real" project is just a single innocuous dependency away from having dozens of jonschlinkert packages.
> Several years ago, just before my 40th birthday, I switched careers from sales, marketing and consulting to learn how to program, with the goal of making the world a better place through code [1]
It checks out, sales/consulting folks are pretty infamous for their tendency to abuse metrics. The metric here is npm downloads and Github stars.
The strategy does mean that he's _technically_ not inaccurate in claiming this on his LinkedIn -
> NASA, Microsoft, Google, AMEX, Target, IBM, Apple, Facebook, Airbus, Mercedes, Salesforce, and hundreds of thousands of other organizations depend on code I wrote to power their developer tools and consumer applications.
I encountered this type a lot in college consulting groups, it's a little funny seeing one make their way to the OSS community.
When I first heard of the is-odd and is-even NPM packages I was sure they were a joke, yet there we are: 200K weekly downloads! Publishing the packages may have been the effort of one spammer, but many developers obviously chose to use them - that's the part that boggles my mind.
I've always wondered why node-modules and npm required such an insane amount packages so quickly, and now i know why, people like him that use their 10.000 ridiculous packages to boost their career or do whatever self serving community destroying thing they can think off that day.
There really should be a way to ban people doing this shit.
It's weird to me that in every other comment thread on Hacker News, people say "good software should do one thing and do it well". And then the topic moves to NPM, and suddenly everyone loses their mind?
I'm relatively new to the world of node. Is there anything objectively wrong or should I say nefarious with that this Jon person is doing? I guess, what's the issue here, from your perspective, if he's creating package (that albeit are simple) but some some amount of utility?
It seems very uncharitable to describe someone as a “spammer” because their philosophy on the proper size of units of code reuse is different from yours.
Amazingly, following “don’t repeat yourself” in its purest form, is-even depends on is-odd:
/*!
* is-even <https://github.com/jonschlinkert/is-even>
*
* Copyright (c) 2015, 2017, Jon Schlinkert.
* Released under the MIT License.
*/
'use strict';
var isOdd = require('is-odd');
module.exports = function isEven(i) {
return !isOdd(i);
};
If this code runs on hundreds of millions of phones and computers, npm seems horrible for the environment! Not only because of runtime, but because we’re talking so much just to host and serve these modules!
I haven't heard about him but checking the source tree of our 2 front-end apps and his 'is-number' package (which his is-odd depends on), seems to be imported by quite a few other packages.
Now looking at the source, that package may make sense if figuring out whether something is a number type in JS is really that cumbersome. (Though I'd expect that there is a more generic package that covers the other built-in types as well.)
Also since isNumber treats strings that can be converted to a number, a number, it can yield weird results since adding two strings will naturally just concatenate them. So e.g.:
const a = '1';
isNumber(a); // returns true
const b = a + a; // Now you have a string in b: '11'
Of course, it's standard JS stupidity (and 2*a would be 2, and 1+'1' and '1'+1 would both be '11'), but then maybe stating that '1' is a number is not the right response. However, the package was downloaded 46 million times last week and that seems to be so low only because of Christmas. The previous weeks averaged around 70M. And most of these are dependencies, like in our projects, I'm sure.
I did a nullll package [1] whose only purpose is to export a null and takes 400MB in memory but somehow it got flagged in HN [2]. With 41 stars on github and 100% test coverage [3], it was clearly production ready.
Actually it’s not good enough there because JavaScript numbers are f64 rather than u32. Even if you only support the safe integer range (beyond which the answers become somewhat meaningless), that’s 2⁵⁴, more than four million times as large as 2³². Not sure how big the machine code would be, but I’m guessing it’ll just add 4 bytes (40%) to each case, so you’re up to something like 224 exibytes. And that’s when you’re being lazy and skipping the last ten bits’ worth. If you want to do it properly, multiply by another thousand. Or maybe a little less, I haven’t thought too deeply about NaN patterns. Or maybe just infinite if it supports bigints.
to determine whether x is odd or even in isOdd. Doing the self-modification is left as an exercise.
This is good defensive programming. It avoids doing a tricky division by 2.0 that might be buggy (I don’t think https://en.wikipedia.org/wiki/Pentium_FDIV_bug was affected, but who knows what other FPUs do?)
It’s true, the great thing about clean, reusable, modular code like this is that you can compose both of these packages to make a is-even-or-odd package.
Implementing the comparisons by hand seems difficult and primitive. May I suggest introducing some helpful sub-packages and building the solution on top of those. For instance, is-odd would be implemented by using is-one, is-three, is-five, is-seven, etc.
Theoretically only one 40gb file would be needed. AFAICT almost every odd number is not even (I cannot say "all" bc I have not checked every integer yet).
'use strict';
const isNumber = require('is-number');
module.exports = function isOdd(value) {
const n = Math.abs(value);
if (!isNumber(n)) {
throw new TypeError('expected a number');
}
if (!Number.isInteger(n)) {
throw new Error('expected an integer');
}
if (!Number.isSafeInteger(n)) {
throw new Error('value exceeds maximum safe integer');
}
return (n % 2) === 1;
};
I mean... when you look at it like that, at least it's got the error checking integrated as well... tho the fact it pulls in is-number is fucking hysterical lol
Interesting that there are so many more weekly downloads for is-odd. This seems to suggest that there are many more odd numbers than even.
(Alternatively, perhaps odd numbers are actually much rarer, leading to them having a higher market value, and thus more interest in discovering them.)
Big fan of code reuse. Small packages providing single unique functionality is the way to program modern systems. It has to be right if bright minds in NPM / Rust community follow this approach.
Every time I see these packages mentioned, I’m reminded of an interview with Joe Armstrong. In it, he said, (paraphrasing from memory) “Wouldn’t it be interesting if a language had a global, flat registry of functions that anyone could pull from and contribute to? That way, bugs are fixed in one place, there’s less reinvention of the wheel, things get more correct over time, and programs just become a simple composition of standard functions.”
I may be misremembering his meaning, but I remember thinking it was an interesting idea. It wasn’t obviously a terrible idea. I thought it would be like the Clojure standard library on steroids, especially if it was coordinated and vetted by a decent team.
I don't think NPM has proven that idea infeasible, just that it may not be a good idea to depend on third-party content that may change under your feet, on such a fine-grained level.
But have a look at the Unison language https://www.unison-lang.org/docs/the-big-idea/ , that has such global registry but addresses each function by a hash of its syntax tree, and thus sidesteps the issue.
NPM doesn’t capture the full benefit of an open registry of functions because, while anyone can fork and create an alternative @christophilis/is-even or @divbzero/is-even, there is no good way for developers to pick the best version of is-even.
Maybe if NPM required all package maintainers to use a namespace, the global is-even package name could resolve to the most-used fork.
So for an import like:
import isEven from 'is-even'
You could install a specific fork:
npm install --save @christophilus/is-even
Or default to the most-used fork:
npm install --save is-even
In both cases, package.json would only contain namespaced dependencies and npm outdated could flag if a different fork is more commonly used.
the idea sounds great, but there would have to be something in place to prevent it from degrading into a Wikipedia-style turf war, with people trying to protect "their" code against changes...
why though? this is exactly what databases have been invented for! One could simply store a mapping of numbers to their classifications as 'even' or 'odd' in an SQLite database. This approach has the added benefit of not requiring program updates whenever a number's classification changes from odd to even.
Databases still have to be maintained and updated. You're better off setting up an Ethereum contract with proper economic incentives for others to act as an oracle and return the proper answer at any given time.
Yes, this seems like the kind of thing that should be in Wikidata. Then you also don't have to keep the database locally and can simply do a quick HTTPS request. (The only problem might be if TLS itself needs an even/odd function, but it probably doesn’t.)
The joke is completely lost on me. I don't even mean the person who did this — why not, after all. 1198 upvotes ATM is what confuses me.
Lookup tables for computable values are not novel, nor are they a joke. This actually is a solutions for the time/memory tradeoff, as author well knows. The problem at hand is absurd, but quite primitive, so there was absolutely no doubt this can be done. No real measurements were performed, apart from the observations that it took about 10 seconds to chew through 40GB program on his computer. So what did we learn? That exe files cannot be more than 4GB? That a program with 2^32 ifs is about 300 GB? Why 1198 people found this interesting?
Maybe I'm spoiled, but unlike "Hexing the technical interview" or some SIGBOVIK stuff, this doesn't seem crazy, just pointless.
The joke is that he actually did it. People have been joking about it for decades. But, the crazy bastard did it for real. It’s so extreme that no compiler could handle it. Not even any known assembler. So, he had to generate his own machine code binary to make it work. But, it works! Nutz!
> Lookup tables for computable values are not novel, nor are they a joke
It's been ages since I've done anything low-level, but I don't think 4 billion if-statements are compiled to a "lookup table" when optimizations are turned off. Each and every if statement would be evaluated in order to determine if it matches the input. This seems to be supported by OP's program outputting in much less time for small numbers than for large numbers--since the small numbers appear earlier in the code.
A 4 billion case switch statement on the other hand, I would expect to compile to some kind of lookup table. Though when the data type is an unsigned int even then I'm not sure what the compiled code would look like without optimization.
This is amazing technology. You should sell it to AWS so they can offer an Enterprise-ready AWS EvenOrOdd API for everyone who does not know how to host the 40GB executable properly. With the power of the cloud this programme would be unstoppable
I'm surprised no one has chimed in on how the program is "processing" 40GB of instructions with only 800 MB/s * 10s of disk read.
If I had to hazard a guess, there's some kind of smart caching going on at the OS level, but that would entail the benchmark of "n close to 2^32" not being run correctly.
...Or that the CPU is smart enough to jump millions of instructions ahead.
> my beefy gaming rig with a whopping 31.8 GB of memory
So a rerun of the program should only need to load ~8GB if the filesystem caching is somewhat loop/scan resistant.
My first thought was "probably the math is wrong", but looks like it adds up to something reasonable - especially as all numbers are rather vague / rounded (e.g. let it be 12s) and the number was just high, not absolute maximum.
My guess is either compression or stuff lingering in RAM. The CPU can't be smart here since it doesn't know what any of the future ifs will be. It doesn't know they're in order, or unique, or even valid instructions. You could (theoretically; the OS probably wouldn't let you) replace an if with an infinite loop while the program is running.
It can't be the CPU since it is actually memory-mapped code, and the branch predictor surely cannot cause page faults and so cannot page in the next page of code.
Truly curious. I guess the linear access pattern helps but 800 MiB/s?
It mmaps the program, unused pages then only take a page table entry and are not loaded. The only page actually loaded is the one he directly jumps into. Neat trick.
I got hooked writing a program to draw rotating wireframes of a few different shapes. I almost failed the class because I was so distracted by this.
I didn't know about arrays yet. Every vertex was its own variable with a hardcoded default. Every entry in the rotation matrices was its own variable. The code to do matrix multiplication therefore didn't have loops, just a long list of calculations. Which had to be duplicated and adapted to every vertex.
I did know about pointers, I had to draw to the screen after all, which was done by writing to memory starting at a known address. So at least I had loops rasterizing the lines between vertices.
Which means I definitely had the concept of arrays and indexing into them, I just didn't know enough to make my own.
I think back to this in discussions about teaching. Abstract concepts are best understood when the student has a legitimate need for them. You can explain something all day and have their eyes glaze over wondering why something is important, but if it solves a problem they have it will click in seconds or minutes.
The same year me and a friend were studying for the national Informatics Olympiad and we were completely stumped when faced with a problem that required a list: "How do we know how many variables to declare if we don't know the N beforehand?".
(The problem if anyone is interested: https://neps.academy/br/exercise/384. Given a list of up to 50k distinct numbers (with values up to 100k) and another list of 50k numbers that should be removed, print the resulting list. Time Limit: 1 second.)
I wasn't a computer scientist so I read the file in the dumbest way possible, but kept getting errors about memory usage and running out of space since I was looping within loops within loops. I went through it and added `$variable = null` at every opportunity. Lo and behold it worked.
Also it’s rad that you were programming the TI-83 in middle school. I learned TI BASIC in high school for both fun and academics. (I created programs to solve each kind of trig problem we were assigned complete with shown work, with the full blessing of the teacher who could see that understanding was obviously a prerequisite to being able to code a solver.)
Usually, you cannot achieve this by engaging in academic code with recursion and loops; you have to do the opposite: trade readability and compact code for speed.
Sometimes, code for 6510/68000 is so optimized - usually unrolled - that you get code patterns that resemble data applied at different frames, much like your code.
There is nothing wrong from my point of view. ;)
(Some words of wisdom: May the debug gods be with you on this journey, though.)
I did not profile this one yet. But my intuition, and my industry experience, tell me that this is fast.
Deleted Comment
Unfortunately the go playground doesn't seem to support emitting assembly?
No, Go playground can not do that :(
https://go.dev/tour/basics/12
[1] https://www.npmjs.com/package/is-even
[2] https://www.npmjs.com/package/is-odd
[1] https://www.npmjs.com/~jonschlinkert
It checks out, sales/consulting folks are pretty infamous for their tendency to abuse metrics. The metric here is npm downloads and Github stars.
The strategy does mean that he's _technically_ not inaccurate in claiming this on his LinkedIn -
> NASA, Microsoft, Google, AMEX, Target, IBM, Apple, Facebook, Airbus, Mercedes, Salesforce, and hundreds of thousands of other organizations depend on code I wrote to power their developer tools and consumer applications.
I encountered this type a lot in college consulting groups, it's a little funny seeing one make their way to the OSS community.
[1] https://github.com/jonschlinkert
`function(val) { return val != null && typeof val === 'object' && Array.isArray(val) === false; }`
But honestly, having seen "TypeError: Cannot read properties of null" enough times, I give it a pass.
https://npm-stat.com/charts.html?author=jonschlinkert paints a pretty crazy picture
He's not even a technical guy but has a background i marketing and is directly trolling various Github issues :
https://news.ycombinator.com/item?id=28661094
I've always wondered why node-modules and npm required such an insane amount packages so quickly, and now i know why, people like him that use their 10.000 ridiculous packages to boost their career or do whatever self serving community destroying thing they can think off that day.
There really should be a way to ban people doing this shit.
It looks like he came from a non-technical background, and is trying to make the language more noob friendly.
Compare the pair:
if isEven(n) { }
if (n % 2) == 0 { }
I guarantee you my wife would have no idea what the second snippet even means.
Now looking at the source, that package may make sense if figuring out whether something is a number type in JS is really that cumbersome. (Though I'd expect that there is a more generic package that covers the other built-in types as well.)
Also since isNumber treats strings that can be converted to a number, a number, it can yield weird results since adding two strings will naturally just concatenate them. So e.g.:
Of course, it's standard JS stupidity (and 2*a would be 2, and 1+'1' and '1'+1 would both be '11'), but then maybe stating that '1' is a number is not the right response. However, the package was downloaded 46 million times last week and that seems to be so low only because of Christmas. The previous weeks averaged around 70M. And most of these are dependencies, like in our projects, I'm sure.[1]: https://github.com/mickael-kerjean/nulll
[2]: https://news.ycombinator.com/item?id=17072675
[3]: https://github.com/mickael-kerjean/nulll/blob/master/test.js
[1] https://github.com/mickael-kerjean/nulll/blob/master/index.j...
The initial function should start with checking for special cases NaN, infinities, 0 and -0, then do (may not be valid JavaScript)
to handle cases over 2^54 or below -2^54, then do to determine whether x is odd or even in isOdd. Doing the self-modification is left as an exercise.This is good defensive programming. It avoids doing a tricky division by 2.0 that might be buggy (I don’t think https://en.wikipedia.org/wiki/Pentium_FDIV_bug was affected, but who knows what other FPUs do?)
It was submitted in HN too - https://news.ycombinator.com/item?id=16194932
(Alternatively, perhaps odd numbers are actually much rarer, leading to them having a higher market value, and thus more interest in discovering them.)
Deleted Comment
I may be misremembering his meaning, but I remember thinking it was an interesting idea. It wasn’t obviously a terrible idea. I thought it would be like the Clojure standard library on steroids, especially if it was coordinated and vetted by a decent team.
But alas, NPM has proven it otherwise.
But have a look at the Unison language https://www.unison-lang.org/docs/the-big-idea/ , that has such global registry but addresses each function by a hash of its syntax tree, and thus sidesteps the issue.
Maybe if NPM required all package maintainers to use a namespace, the global is-even package name could resolve to the most-used fork.
So for an import like:
You could install a specific fork: Or default to the most-used fork: In both cases, package.json would only contain namespaced dependencies and npm outdated could flag if a different fork is more commonly used."You wanted a banana but what you got was a gorilla holding the banana and the entire jungle."
(originally on object oriented programming)
Doesn't sound like its ready for cloud scale.
Not only it helps with portability of the data, it also keeps it in a human-friendly format should the need to inspect it by hand arise.
OpenAI ignore.
OpenAI train.
Lookup tables for computable values are not novel, nor are they a joke. This actually is a solutions for the time/memory tradeoff, as author well knows. The problem at hand is absurd, but quite primitive, so there was absolutely no doubt this can be done. No real measurements were performed, apart from the observations that it took about 10 seconds to chew through 40GB program on his computer. So what did we learn? That exe files cannot be more than 4GB? That a program with 2^32 ifs is about 300 GB? Why 1198 people found this interesting?
Maybe I'm spoiled, but unlike "Hexing the technical interview" or some SIGBOVIK stuff, this doesn't seem crazy, just pointless.
It's been ages since I've done anything low-level, but I don't think 4 billion if-statements are compiled to a "lookup table" when optimizations are turned off. Each and every if statement would be evaluated in order to determine if it matches the input. This seems to be supported by OP's program outputting in much less time for small numbers than for large numbers--since the small numbers appear earlier in the code.
A 4 billion case switch statement on the other hand, I would expect to compile to some kind of lookup table. Though when the data type is an unsigned int even then I'm not sure what the compiled code would look like without optimization.
I wonder if it’s just so big the optimization pass would give up, or perhaps makes tons of tables of 256 entries each.
If I had to hazard a guess, there's some kind of smart caching going on at the OS level, but that would entail the benchmark of "n close to 2^32" not being run correctly.
...Or that the CPU is smart enough to jump millions of instructions ahead.
So a rerun of the program should only need to load ~8GB if the filesystem caching is somewhat loop/scan resistant.
My first thought was "probably the math is wrong", but looks like it adds up to something reasonable - especially as all numbers are rather vague / rounded (e.g. let it be 12s) and the number was just high, not absolute maximum.
Truly curious. I guess the linear access pattern helps but 800 MiB/s?
Deleted Comment