7 ms·
How to avoid the assignment statement
- cperciva 16y agoA C programmer's reparsing: "Initialize right away" --> "Make it hard to figure out what your variables are by mixing up code with their declarations." "Construct new values" --> "Manually do what the first pass of the compiler already does. Except don't use the const keyword." "Make functions, not procedures" --> "Don't check for errors. Don't even make it possible to check for errors." "Make your objects immutable" --> "Let's see how much time we can spend in malloc!" "Use purely functional data structures" --> "Arrays and hash tables are often the optimally performant data structures... but you shouldn't use them." "When you can't help it" --> "If all your other attempts to sabotage your code fail to provide the required performance degradation, you can always slow your program down by copying data around unnecessarily." In all seriousness, just like the famous "GOTO considered harmful", the author's "assignment statement is harmful" is an assertion which is valid in some situations -- quite possibly most or all of the situations he personally has to deal with -- but most definitely not valid in all circumstances.
- btmorex 16y agoI agree with most of what you said, but: "Initialize right away" --> "Make it hard to figure out what your variables are by mixing up code with their declarations." No. Putting all declarations at the top of a function makes it hard to figure out. If you can use C99, you should keep declarations and initializations together and hopefully as close to where they are actually used as possible. One thing I'd add about the article: naming a function "move" and then having it return the sum of two vectors is bizarre and worse than the original implementation. At least, move as part of the Point class actually moved the point.
- tkahn6 16y agoAgree. When I was going through K&R I was always thinking to myself, "did they just know all of the variables they would need before hand?".
- mfukar 16y agoMaybe not. Probably not, in many cases. Hey, have you seen function prologues in assembly? How does it know all the amount of stack space it's going to need beforehand?
- loup-vaillant 16y ago> naming a function "move" and then having it return the sum of two vectors is bizarre and worse than the original implementation. I kept the name for the sake of consistency. I agree that's a mistake. The problem is that a `Point` should be moved with a `Vector`, and not another point. But introducing a `Vector` class would have lengthen my tutorial. Maybe I should use two `floats` instead of a `Point`.
- th 16y agoThat example bothered me as well mostly because when the move function is placed outside of the class it is now performing a different action. The new function no longer really moves a point so much as it adds two points (regardless of the point/vector issue). I think many of the naming conventions commonly used in OOP for mutable objects are not as appropriate for their immutable counterparts.
- mahmud 16y agoOne thing I'd add about the article: naming a function "move" and then having it return the sum of two vectors is bizarre and worse than the original implementation. At least, move as part of the Point class actually moved the point. This makes sense in MVC where the controller is responsible for updating point coordinates and the view is responsible the actual rendering of the points on the display. Also, in most graphic systems, the screen refresh is done by a single call made from within the interaction loop or by a designated callback. That's because all display updates are made to a back buffer, which then gets written to the framebuffer at a regular clock-driven interval.
- cperciva 16y agoIn a 50 line function with 5 variables, I find it much easier to look at 5 lines of variable declarations to figure out how I declared a variable than to scan through up to 50 lines of code.
- loup-vaillant 16y agoSo, declaring variables at the top of the function makes it easy to find them. Now how do you find the point of initialization? declare x | ... | ... initialize x | declare & initialize x ... | ... use x | use x In both cases, you will have to scan for the whole function, or hit the "search" hotkey. Or maybe sometimes you just need to know when `x` were declared, but not what value it holds?
- Qz 16y agoStop writing 50 line functions.
- loup-vaillant 16y agoThank you for your feedback, I will amend my entry. > "Make functions, not procedures" --> "Don't check for errors. Don't even make it possible to check for errors." I will make my tutorial clearer on this point: errors can be embeded in results. Option types can do wonders. Of course, this is not easy to do in C. My opinion is that programming in C means either dealing with legacy code, seeking utmost performance, or being nuts. I think that leaves plenty of room for reasonable programming. I never said that my assertion is valid in all circumstances. Actually I agree with you here. But I would be extremely surprised if my advice couldn't be followed by most programmers, most of the time.
- mfukar 16y ago"Of course, this is not easy to do in C." Actually, it's quite simple. The issue is that debugging becomes a bitch, and error codes are hidden even from the developer inside nested function calls - ie. you can't stop/recover from errors right away.
- philwelch 16y agoError checking can be done in a function-composing style if your functions check for error on their inputs, recognize error-indicating values on their parameters, and consistently output error-indicating values. There is definitely waste involved in this--when composing functions this way, you have extra function calls and error-code checking on your parameters rather than checking data along the way and bailing out if there's a problem. Your functions have to check the arguments rather than having their preconditions enforced outside of them, which makes the composed functions less elegant. I don't know how practical it is in the end, but pushing most of the error-checking complexity into lower level functions and writing your upper level code in a compositional style sure seemed elegant every time I tried it.
- cperciva 16y agoError checking can be done in a function-composing style if your functions check for error on their inputs, recognize error-indicating values on their parameters, and consistently output error-indicating values. True -- but this is only possible if you accept layering violations. To take an extreme example, how is printf() supposed to know that if a string parameter is NULL then it should error out without printing anything?
- loup-vaillant 16y agoYour example is wrong. Functions parameters should be guaranteed to be error-free in the first place. If you decided that a NULL string was an error condition, you should test for that before calling printf. You should test return values, not parameters.
- cperciva 16y agoYou're disagreeing with the wrong person here.
- loup-vaillant 16y agoI disagreed with your "extreme example", on the grounds that checking arguments instead of return values is a bad idea.
- mahmud 16y agoIn his book "Zen of Assembly Language", Michael Abrash has a chapter named "Billy, don't be a compiler" and tells the story of a man who coded Assembly like a C compiler. I apologize in advance, but I can't help but extend to you the same advice, cperciva. "Make it hard to figure out what your variables are by mixing up code with their declarations." In the right language, binding of variables is usually done by a LET construct, and within its scope where the variable is visible, the code is indented in ward. When you nest LETS you can see the liveness of a variable. "Construct new values" --> Manually do what the first pass of the compiler already does. Except don't use the const keyword He misspoke. He meant construct new variables. I am sure you would agree with him that a 'promiscuous tmp' is a royal pain in the ass when you're stepping through bad C code. "Make functions, not procedures" --> "Don't check for errors. Don't even make it possible to check for errors." Again, you're thinking at an abnormally low level of abstraction. Results are not for error handling, that's what the condition system is for (some erroneously call it an exception handling system) "Make your objects immutable" --> "Let's see how much time we can spend in malloc!" That's what the GC is for. When you make objects immutable you're buying yourself the piece of mind that comes with referential transparency. "Use purely functional data structures" --> "Arrays and hash tables are often the optimally performant data structures... but you shouldn't use them." There are ways to implement functional arrays and hashtables, but none too apparent without getting into academic wankery. I agree on this point.
- deleted 16y ago[deleted]
- loup-vaillant 16y agoLanguage specific? Could you specify the languages you have in mind? I used a C/C++/Java syntax, but I meant any language. Or at least the ones with garbage collection.
- mfukar 16y agoI will actually make a blog post of it. There are a few more things to be said, and too little space here.
- ajuc 16y ago> Make functions, not procedures I can't agree - you don't need results of functions for checking errors - in many higher level languages you can return multiple values, you can use exceptions, even in C++ without exceptions you can do this by in-out parameters, for example: http://www.nopaste.pl/rtt http://www.nopaste.pl/rtt I think it's cleaner than making nested if then else-s over every invocation of your functions.
- 10ren 16y agoA useful summary (eg. I found my experimental code from last year a lot easier to understand when it used functions rather than procedures) - though I couldn't help laughing when he started saying "mistakes", "correct" and "incorrect".
- loup-vaillant 16y agoAh… Do you think I should change my wording? Because if it made you laugh, it could also discredit my point in the eyes of those who don't agree with me in the first place. I'd like to correct that, if possible.
- 10ren 16y agoThe wording comes across as dogmatic; yet the article isn't actually dogmatic - it sincerely acknowledges cases where the functional approach isn't suitable (even including the really cool algorithm of quicksort - BTW you might mention games as another one where mutable state is convenient). Showing respect for alternatives (where due) makes an article seem objective, impartial, intellectual and truth-seeking (rather than pushing an agenda; or trying to sell something). It would probably be beneficial if the wording reflected your intent. I did find it distracting, because (to be frank) people don't like to be told what they should be doing; laughter is a way to defuse the affront. Another approach is to appeal to those (few) people who are keen to learn about techniques that will help them. And I really think that that's what you were going for. Changing the wording would help achieve that aim, in my opinion. So... yes. btw: I really liked your explanation of linked lists being cheap to modify; and the article in general is very well done and communicates very effectively - I'd say it's of textbook standard.
- zweinz 16y agoSeems to me like you should just be coding in a functional language. Coding in Scheme might be easier than turning C into Scheme.
- loup-vaillant 16y agoI agree. By the few code that I write[1] outside of my day job is not in C. Also, I didn't meant C, but any language with garbage collection. I used C syntax mainly to reach mainstream imperative programers. [1]: http://www.loup-vaillant.fr/projects/ussm/ http://www.loup-vaillant.fr/projects/ussm/