6 ms·
I think we would be very unhappy working on the same project. I use functional style in C when it fits... which is rare because most problems are ill-suited to
by burfog 10y ago
I think we would be very unhappy working on the same project. I use functional style in C when it fits... which is rare because most problems are ill-suited to functional programming. The loop on pointer increment and test is completely natural for this problem, as is the pointer math to get the result. This is exactly what the assembly code would do, possibly moving the -1 up to the top and switching from post-increment to pre-increment. (that alternate layout is a C violation that most compilers would accept)
You mentioned that C is missing "generics, anonymous functions, multiple return or tail call elimination". Well, not really.
generics: use the _Generic keyword
anonymous functions: you can autogenerate names via token pasting
multiple return: you can return a struct (living w/o auto-unboxing) or pass a pointer for an extra return value
tail call elimination: there might not be a language requirement for any particular case, but gcc at least is very good about eliminating tail calls -- that said, code which benefits from this optimization is usually hard for humans to deal with
In case you wonder about the C way to count list items...
if(!node)
return 0;
unsigned n = 0;
do n++;
while(node=node->next);
return n;
Like that loop? :-) Curly braces are not needed, and an assignment (plain '=') produces an rvalue that can be tested.
- lmm 10y ago> The loop on pointer increment and test is completely natural for this problem, as is the pointer math to get the result. Disagree. "Move the pointer forwards until the value it points to is zero, then measure the distance between where it points and where it started" is a very unnatural way for a human to think about computing the length of a string. The pointer is doing double-duty as a pointer and a counter. It's efficient for the computer, but it's exposing implementation details that don't make a lot of conceptual sense (and don't even match the real implementation these days - modern hardware has to do a lot of work with cache invalidation and the like to maintain the illusion of a unified flat memory space). > This is exactly what the assembly code would do Absolutely - but assembly is optimized for the computer, not the human. > generics: use the _Generic keyword I don't need to do branching based on a finite set of possible types. I want a generic version of result (where value is of the generic type), and I want indexed_cata to accept an operate that returns whichever generic version of result, and return the same type (I made a mistake in my code - it should return size_t rather than int). Can I do that in C? > anonymous functions: you can autogenerate names via token pasting Not good enough I think. I need to be able to create a function at runtime. (I wanted to pass the continuation into operate and then operate can either return directly or call the continuation and return the result of that, rather than have operate return a flag that the caller that indexed_cata checks and loops) > multiple return: you can return a struct (living w/o auto-unboxing) or pass a pointer for an extra return value I passed a pointer to result and returned via that for exactly that reason. Could I have had index_of_first_null_u return a result_size_t instead of accepting a result_size_t* and it would all work? > Like that loop? :-) Curly braces are not needed, and an assignment (plain '=') produces an rvalue that can be tested. I don't. "=" in a loop test is horrible for readability (it looks like an error (and often is), and has very different semantics from mathematical "=" - pascal-style "while(node := node->next)" would be a much clearer syntax), and returning values that are usually discarded is bad practice (I would like discarding values to be a rare event, probably something that can be a compiler warning - ideally I'd like something like a linear type system). The "do" and "while" look like unrelated statements. The if at the top as the special case is ugly and shouldn't be necessary - when a loop is expressed elegantly the zero case should just fall out naturally.
- burfog 10y agoHaving the pointer "doing double-duty as a pointer and a counter" reduces the potential for bugs. If you do otherwise, then you have more variables to think about and they could get out of sync. It does match the hardware. There is no cache invalidation. C programmers commonly think in terms of assembly. They might not envision a particular kind of CPU, but low-level operations are in their mind. The better programmers actually check the assembly sometimes. They know that a "do" loop is normally smaller than a "while" loop because the "while" loop gets converted into a "do" loop inside an "if" that handles the case of no iteration. The _Generic keyword is resolved by the compiler, so there is no branching. One can also use a macro. Macros can be the normal function-like type of course, but there is so much more. Macros can be used to define functions. Macros can be used with repeated inclusion of the same header file, allowing the creation of many different versions of code. This lets you generate indexed_cata__size_t and indexed_cata__uint64_t and so on, which you would then hook up via a macro called indexed_cata that uses _Generic to dispatch (with zero run-time overhead) to the correct implementation. I probably don't really get what you're looking for with the continuation stuff. Wikipedia compares it to setjmp/longjmp and goto, which doesn't sound too nice. This is all completely off-the-wall alien to a C programmer. It's hard to get excited about learning to understand something that is associated with really slow programming languages. Being "able to create a function at runtime" is normally incompatible with having a small high-performance runtime. In C you have a choice: fake it, or do things the awesome way. You can fake it with macros, possibly including ones that expand out to define functions. The awesome way is to JIT, and yes I've done this: allocate a chunk of memory, write out some binary instruction code, deal with any permissions/cache/pipeline issues, and then call it as a function or use a bit of inline assembly to reach it. Oh, there is another way that might be more your style: create C code on-the-fly, feed it into the compiler to generate a library, dlopen the library, and call your new function. Yes, you can return a struct. What you don't yet get is any syntax to automatically split that apart in the caller. The caller will end up with a struct. Hopefully the next C revision will improve this. Expressing my loop in an alternate way is likely to generate code that is bigger and slower. This particular case is trivial enough that gcc 4.8.4 can optimize it down to the exact same bytes, and a newer gcc probably just substitutes a call to strcpy unless you pass -ffreestanding to stop that optimization. Normally it makes a difference.