It's worth noting that the original FizzBuzz problem is not "given a number n, print..." but rather "for the first 100 natural numbers, print...". In this formulation, you don't need the modulo operator: you just initialize two counters at 3 and 5, respectively, decrement them each time, and react when either or both reach 0.
No idea what prices you're referring. In Europe, the most popular car repairs franchise offers tire swap at less than 2 euro (per wheel) more than wheel swap. What steel wheel can you buy for 6 euro?
From a cursory read of the readme and design documents, it looks to me the key point about Evo is a different use of existing concepts, like branches, merges, etc. rather than new concepts. I guess if you want ephemeral branches, nobody stops you from using git in that way, too. A wrapper around git would solve the remaining propositions, that is a better command syntax.
I struggled to understand git until I tried "gitless"[1], a wrapper around git that lets you focus on your workflow ignoring git's own weirdness. For example, switching to another branch automatically checks out the active commit of that branch. This is what you want most of the time: you don't switch branch and then don't even look at the code in that branch, do you?
I was intrigued but... What does this even mean? "switching to another branch" is the same thing as "checking out the active commit of that branch" in git.
> For example, switching to another branch automatically checks out the active commit of that branch. This is what you want most of the time: you don't switch branch and then don't even look at the code in that branch, do you?
You are right. What gitless does differently is it saves (git stash) the uncommitted files before switching: branches are well isolated, it doesn't let uncommited files be "copied over", if you understand what I mean.
> the tool (or system) is too complicated (or annoying) to use from scratch.
Or boring: some systems require boilerplate with no added value. It's normal to copy & paste from previous works.
Makefiles are a good example. Every makefile author must write their own functionally identical "clean" target. Shouldn't there be an implicit default?
C is not immune, either. How many bits of interesting information do you spot in the following excerpt?
The printf alone is the real payload, the rest conveys no information. (Suggestion for compiler authors: since the programs that include stdio.h outnumber those that don't, wouldn't it be saner for a compiler to automatically do it for us, and accept a flag to not do it in those rare cases where we want to deviate?)
> since the programs that include stdio.h outnumber those that don't
I don't think that is true. There is a lot of embedded systems C out there, plus there are a lot of files in most projects, and include is per file not per project. The project might use stdio in a few files, and not use it in many others.
> Makefiles are a good example. Every makefile author must write their own functionally identical "clean" target. Shouldn't there be an implicit default?
At some point you have to give the system something to go on, and the part where it starts deleting files seems like a good one where not to guess.
It's plenty implicit in other places. You can for example, without a Makefile even, just do `make foo` and it will do its best to figure out how to do that. If there's a foo.c you'll get a `foo` executable from that with the default settings.
> The printf alone is the real payload, the rest conveys no information.
What are you talking about? Every line is important.
#include <stdio.h>
This means you need IO in your program. C is a general purpose language , it shouldn't include that unless asked for. You could claim it should include stuff by default, but that would go completely against what C stands for. Code shouldn't have to depend on knowing which flags you need to use to compile successfully (at least not in general like this).
int main(int argc, char** argv)
Every program requires a main function. Scripting languages pretend they don't, but they just wrap all top-level code in one. Having that be explicit, again, is important for a low level language like C. By the way, the C standard lets you declare it in a simplified manner:
int main(void)
Let's ignore the braces as you could just place them on the same line.
printf("Hello\n");
You could just use `puts` here, but apart from that, yeah that's the main payload, cool.
return 0;
The C standard actually makes this line optional. Funny but I guess it addresses your complaint that "common stuff" perhaps should not be spelled out all the time?
So, here is the actual minimalist Hello world:
#include <stdio.h>
int main(void) {
puts("Hello world\n");
}
Thank you, but this thread was not about writing good code, but rather how often one ends up acritically copying existing "legacy" parts without even attempting to understand it.
I probably used the wrong words: "conveys no information" was meant as "is less meaningful than the printf". Just like switching on the PC every morning is essential, but if you ask me what my job's about, I wouldn't mention it.
In the same vein, I'm convinced that the printf is the part that expresses the goal of the program. The rest, the #include, the main(), even with the optimizations that you suggested, is just boilerplate, the part that is usually copied and pasted, not because it's not useful and not because it's too difficult to get right, as the original article claims, but because it's boring.
What I find weird is that the chunk's size is fixed, while the caller must specify how many chunks a pool contains at pool creation. I would do exactly the dual: the chunk's size should be defined at pool creation, so that you can create multiple pools, each dedicated to one specific kind of object. (You need a third field in struct Pool, though.) Instead, the number of chunks per pool should not something the user needs to care about: if they knew in advance how many objects are required, they would simply allocate an array big enough! The library implementation should define a sensible number and stick to it.
Similarly, I don't like exposing pool_expand(): too much burden on the user. Expansion should be automatically triggered by pool_alloc() whenever the current pool is exhausted.
This would also allow a much simpler pool_new() that just initializes its pointers to NULL, leaving it to the first invocation of pool_alloc() to actually do the first allocation. This would avoid the duplication of code between pool_new() and pool_expand().
Another benefit of fixing the number of chunks is a possible simplification of ArrayStart: this could include a proper array instead of a pointer, avoiding a malloc(). Something like this:
> I would do exactly the dual: the chunk's size should be defined at pool creation, so that you can create multiple pools, each dedicated to one specific kind of object.
This is supported in my 'libpool' project. I thought I mentioned it in the article, but now I am not so sure.
> Similarly, I don't like exposing pool_expand(): too much burden on the user. Expansion should be automatically triggered by pool_alloc() whenever the current pool is exhausted.
I feel like this shouldn't be too hard to do, but I actually did write a function for this in another project I am working on.
> This would also allow a much simpler pool_new() that just initializes its pointers to NULL, leaving it to the first invocation of pool_alloc() to actually do the first allocation.
I didn't think of this, but I rather keep the two functions separate, specially for readability in the article.
In some way it's the dual of break, in that you want to jump into the middle of the loop, while break is to jump out of it.
Let's rewrite the loop this way, with 'break' expanded to 'goto':
while (true) {
prepare...
if (!cond) goto exitpoint;
process...
}
exitpoint:
The dual would be:
goto entrypoint;
do {
process...
entrypoint:
prepare...
} while(cond);
Both constructs need two points: where the jump begins and where it lands. The 'break' is syntactic sugar that removes the need to specify the label 'exitpoint'. In fact with 'break' the starting point is explicit, it's where the 'break' is, and the landing point is implicit, after the closing '}'.
If we want to add the same kind of syntactic sugar for the jump-in case, the landing point must be explicit (no way for the compiler to guess it), so the only one we can make implicit is the starting point, that is where the 'do' is.
So we need: a new statement, let's call it 'entry', that is the dual of 'break' and a new semantic of 'do' to not start the loop at the opening '{' but at 'entry'.
do {
process...
entry;
prepare...
} while (cond);
Is it more readable than today's syntax? I don't know...
Is there any particular reason why this straightforward implementation of a well-known PRNG (published by Marsaglia 20 years ago) is worth being linked from HN's first page?
reply