4 ms·
Most programs aren't about a return value. They are about global state, including IO and huge data structures. The functional programming hacks for IO are gross
by burfog 10y ago
Most programs aren't about a return value. They are about global state, including IO and huge data structures. The functional programming hacks for IO are gross. The issue of dealing with huge data structures is generally not solved; when you make a tiny change to a terabyte you have an awkward problem.
It's not standard C, but gcc will let you mark a function as being pure. Even without that, the compiler may notice.
I don't agree that expressing a partially applied function at runtime helps clarity and readability. I suppose tastes differ, and some people have bad taste. :-) The real trouble is that this is not something that is natively supported by the hardware. Your language either interprets a very unnatural machine, or it drags along a compiler.
Interpreting a very unnatural machine is unappealing. It distances you from reality, making it difficult to reason about how your code will perform on the hardware.
Dragging along a compiler is sometimes, rarely, justified. It's appropriate when the partially applied function must run for a long amount of time. Sometimes people even drag along a compiler for a different processor, such as a GPU. We find this to some extent with CUDA and OpenCL.
- lmm 10y ago> Most programs aren't about a return value. They are about global state, including IO and huge data structures. Not my experience at all. Most of the time if you're using a computer it's because you want to, well, compute. I/O happens but very often it's a simple matter that can be pushed to the edges, and the business logic that you actually need to be thinking about can be cleanly encapsulated. > The functional programming hacks for IO are gross. Only because I/O is gross. I find it's more a case of: functional style forces you to expose the ugliness that was always there underneath. Often you uncover things like the family of security vulnerabilities where an application creates a temporary file and then opens it and an attacker can replace it with a symlink in between. If you really want to say "do these I/O operations whenever, it'll all work out" then it's trivial to do that, even in Haskell (you just add "unsafePerformIO $" before all your I/O calls), but most people find it's actually worth explicitly figuring out which order things are supposed to happen in and doing that. > I don't agree that expressing a partially applied function at runtime helps clarity and readability. I suppose tastes differ, and some people have bad taste. :-) The real trouble is that this is not something that is natively supported by the hardware. Your language either interprets a very unnatural machine, or it drags along a compiler. Not at all. Every language with classes manages to do this (even Java can do it nowadays), C++ included. It's not hard to implement. What it does require is one more layer of indirection around function calls (if you're really bothered about the overhead you can make it opt-in i.e. "virtual" in C++). I could write my own code to do this in C, but a) I couldn't use the "function(arg1, arg2)" syntax because there's no way to override that (would have to do something like a macro, "VIRTUAL_CALL(function, arg1, arg2)") and b) it wouldn't interoperate with any of the rest of the ecosystem. You might not want to use it in every program, but this is the kind of thing that absolutely needs to be part of a language-wide standard for programs that do want to use it, so that libraries from different people can interoperate with each other. (FWIW I think the idea of corresponding to the hardware is a false idol - or at least, if there is a way to achieve it, C isn't that. GCC generates something very different from a hand translation into assembly. Even assembly no longer corresponds to what the actual processor will do (microops). Modern processors are inherently parallel and no remotely mainstream language expresses that accurately.)
- burfog 10y agoProbably you'd end up with this: foo->bar(foo,arg1,arg2) It's not as simple-looking, but that isn't all bad. It's very clear what is going on. The overhead isn't hidden. Things needing call-back functions need to support an extra void* of course, and sadly it is often left out. Understanding how compilers translate C into assembly is a skill that must be practiced. I suggest getting in the habit of looking at the binary. After a while, you will know what to expect. The optimizers are not magic. They do fairly predictable transformations on the code. You can get a feel for when hand-optimization matters, and when it is just pointless. An awareness of cache lines and TLB entries is helpful too.
- lmm 10y ago> Things needing call-back functions need to support an extra void* of course, and sadly it is often left out. Well when doing the right thing means writing more verbose code, it's no surprise that people do the quick-and-dirty thing (see http://www.haskellforall.com/2016/04/worst-practices-should-be-hard.html http://www.haskellforall.com/2016/04/worst-practices-should-... ). Since "everyone knows" that best practice for a callback is to take a function pointer and a void * , wouldn't it be better to have that idiom built into the language? (i.e. a concise syntax for the pair of callback-and-void * , and a concise syntax for "invoking" the pair, passing the extra void * along with the "ordinary" arguments).