[HN Gopher] Linus Torvalds' good taste argument for linked lists... ___________________________________________________________________ Linus Torvalds' good taste argument for linked lists, explained Author : mkirchner Score : 131 points Date : 2020-12-06 20:57 UTC (2 hours ago) (HTM) web link (github.com) (TXT) w3m dump (github.com) | smitty1e wrote: | This seems like the classic argument of whether approaches like | Duff's Device[1] are a good implementation idea. | | I would offer that there is no shame in doing something a bit | more advanced, as long as there are test cases and documentation | proportional to the advanced nature of the technique available. | | [1] https://en.m.wikipedia.org/wiki/Duff's_device | m4lvin wrote: | The second version seems more elegant but will scare non-C people | away with all those pointers ;-) | | What I do not understand is why one should use an "IntList" | struct in the first place? As the explanation of the second | method suggests, a List is the same thing as a pointer to its | first element, so why not do this?: | | typedef struct IntListItem* IntList; | | Also, could it be that both methods fail terribly (infinite | loops?) when they are given wrong input such as elements not in | the list at all? | MauranKilom wrote: | > Also, could it be that both methods fail terribly (infinite | loops?) when they are given wrong input such as elements not in | the list at all? | | You could just say "has undefined behavior unless target is an | element of the list". | | Whatever your choice though - this is slide code. It should be | obvious that it's not production ready. | baby wrote: | This is great, I often run into these type of things in Rust | where an unwrap() is used when there doesn't need to be if you | refactor the code correctly. | | EDIT: interestingly, I see a lot of comments here focusing on the | reduction of lines of code (LOC). But IMO, the solution would | still have been deemed "better code "if it had increase the LOC | instead of reducing it. The idea is about eliminating edge-cases, | not about reducing the number of LOC. | danielg0 wrote: | Computerphile has a video showing a nice visual explanation of | this technique, albeit for inserting values into a linked list | rather than deleting them. | | https://youtu.be/0ZEX_l0DFK0 | abhinav22 wrote: | Thank you, very well written. | userbinator wrote: | Linus isn't the first nor only person to discover this | simplification, although I've usually encountered it as a | "virtual head" using only a single indirection: | | https://news.ycombinator.com/item?id=18997420 | | It's interesting to see how divisive the opinions are. I see it | as the difference between the "growth mindset" and not. | wanderer2323 wrote: | Let's draw a list IntListItem -> IntListItem -> | IntListItem | | and then draw the position of the list head: | IntList -> IntListItem -> IntListItem -> IntListItem | | You can see that the if-branch in the cs101 answer comes because | there is a ('virtual') element of the list (the IntList head) | that is different from the other elements of the list. | | If we were to make them the same (C# code): | interface IListItem { IntListItem Next {get;set;} | } class IntListItem : IListItem { int | Value {get; set;} IntListItem Next {get; set;} } | class IntList : IListItem { IntListItem Head {get; | set;} IntListItem Next { get { | return Head; } set { Head = value; } } | } | | then our cs101 code simplifies itself, folding into a prettier | algorithm: void remove_cs102(IntList l, | IntListItem target) { IListItem p = (IListItem) l; | while (p.Next != target) { p = (IListItem) | p.Next; } p.Next = p.Next.Next; } | | the use of indirect pointers was masking the real issue: some | algorithms _look_ better if you add a virtual head (or a virtual | tail) to your linked list. Emphasis on _look_ though -- they work | almost the same. | whack wrote: | This sounds like a classic case of _" simplicity is in the eye of | the beholder"_. | | If you're someone who has experience with, or can easily grok | concepts such as "pointer to a pointer", then the second snippet | seems obviously simpler. After all, there are fewer branches to | consider. | | Unfortunately, as someone who stopped working with pointers a | long time ago, my mind has to work in overdrive to understand any | implementation that relies on a "pointer to a pointer". Hence why | the first implementation is far easier for me to understand. | | I'm sure we'll see many debates around which solution is simpler, | but these debates will never reach a resolution. Depending on the | skills and experiences you bring to the table, different people | will objectively benefit from very different implementations. | | https://software.rajivprab.com/2019/08/29/abstractions-are-i... | maweki wrote: | I think what's missing in the mental model of the elegant | solution is, that the IntList-pointer does not point to a | complete list or an element of the list, but to a tail of some | list (that might be the complete list). This explains why the | head is not really a special case, as it points to the tail that | is complete. And you can just exchange a tail. | | So I think the image with the blue boxes is misleading and it | should be | | ->[4->[12->[3->[6->[2->[]]]]]] | | It is now obvious that you can always point -> to a different | [...] | | And as it turns out, that is basically the Cons/Nil view of a | list from functional programming or lisp, if you're so inclined. | And in those languages you would pattern match on the constructor | once and do the tail-recursive call. | mrloba wrote: | Maybe slightly off topic, but.. | | I don't think this is really about taste. It's about "common | ground" between programmers. If you've seen and used the second | pattern many times before, it becomes part of your vocabulary. | You're then able to express yourself more succinctly. It is then | obviously a better solution to you. | | If you see another programmer use the same pattern, it is common | ground between you, and you like it. As long as every or most | programmers on the team share the same common ground, you are | more effective for using it. If you don't share it, you're less | effective. | | Everyone here commenting that they like the first solution better | probably doesn't share the necessary common ground with Linus. | | The big question is: what common grounds should you expect when | writing your code? | xorcist wrote: | Also it's the other way around! Using common idioms builds | culture how we structure our code. Then that common culture | help make the idioms part of the shared vocabulary. | | Working with such a large piece of code as the kernel, it's | invaluable to have a certain sense of shared ideals how the | code should read, and it's probably a good thing to have | maintainers that nitpicks about such details. | | People sometimes say things like "just a matter of taste" as if | that somehow made it less important, but taste is probably the | most important trait we can share when programming. | galoisgirl wrote: | The same argument was made recently about the reduce function: | it's a known pattern for some and a hard to understand trick | for others. | convolvatron wrote: | this is the big point. when coding alone I strongly prefer | Linus's version. | | but it seems to cause friction and get called out whenever I | use it in a group environment. so I don't. or I change it. | | the code itself isn't important. its about the functioning of | the group, the velocity, and the ownership. | priyanshuraj wrote: | Good post. Not a "show hn" though, is it? | dang wrote: | Correct--reading material doesn't qualify for Show HN. | Otherwise every submission could have "Show HN" on it. We've | taken that out of the title now. | | Submitters: before putting Show HN on a title, please read the | rules: https://news.ycombinator.com/showhn.html. | mkirchner wrote: | Author here. Thanks for fixing the title, I should have seen | that! | int_19h wrote: | Which one required writing an article about to explain? | seanwilson wrote: | I used to ask candidates to just describe what a linked list was | during interviews and less than half the candidates for | mid/senior positions could. I think it's a really revealing | question. | jraph wrote: | I've seen this presentation, and if I remember correctly, this | example was more an attempt to convey what good taste can relate | to, more than a definitive answer on whether the second is | actually better than the first. | | Many comments here are arguing that the first answer is actually | better because it is clearer. I think it feels clearer to many of | us not because it is actually simpler, but because it is the one | we learnt at school / are used to see and therefore know by | heart. | | Linus is probably aware of this and may have meant to surprise | the public with the second solution. | | Why the second solution is better? Not because it does less | branching and is more efficient. This misses the point. Not | because there are fewer lines of codes and is terser. This also | misses the point. | | Fewer special cases means less ways to screw up, and also easier | to follow. In the general case. Not only in this specific linked | list example. And also clearer code. | | It's just that in this specific case, we are used to the first | solution that we are able to recognize at a glance (we "pattern- | match" it). | | Do you remember when you had to grasp this first solution the | first time you encountered it or tried to write it? Many of us | probably screwed it up and wasted time making it work. We might | have forgotten the exact edge case Linus Torvalds was pointing | out in this presentation. At least for me, I remember it was | hard. I probably would have had easier time understanding the | second solution by the way. The difficulty is a pointer | indirection, but you better really understand pointers correctly | when you are manipulating linked lists in C anyway. | | Comments here also speak about leaving maintainable code for | future developers on the project and avoid clever solutions to | make their life easier, but it is the whole point the second | solution: let them not have to think about edge cases as much as | possible. | | Don't stop on this linked list example. We are all used to the | first solution and Linus Torvalds probably picked this example | because many people know linked lists. The message is: fewer edge | cases is better. Actually hard to argue against. | | Also see the original code with comments, which is way more | readable: https://news.ycombinator.com/item?id=25327066 | loopz wrote: | > [...] I don't want you to understand why it doesn't have the if | statement. But I want you to understand that sometimes you can | see a problem in a different way and rewrite it so that a special | case goes away and becomes the normal case, and that's good code. | [...] -- L. Torvalds | | I think the idea of this extends much beyond a linked-list | implementation, into software design and architecture. | | Sometimes, you find more elegant solutions to something, that | inherently do away with edge cases. I think this is the original | intent, to show that you can find beauty, much as chessplayers do | in chess. These solutions may be harder to understand completely, | but you can actually encapsulate them in a function or use them | as patterns! | | A point is also made, there's often a _rewrite_ involved. You | usually don 't need to find this stuff on first try. | carstenhag wrote: | I don't disagree with it being written well. I don't feel like | using C and pointers is helpful for getting the point across. | | After reading for a minute I realized it's all about pointer and | C specific stuff, I am not going to revisit that just for an | article... | tigerlily wrote: | When I interview prospective employees I sometimes ask them to | explain the rudiments of linked lists in C. | gjulianm wrote: | Although I liked the 'elegant' code after reading it, in general | I would try to shy away from 'elegant' solutions that are | actually harder to understand. Unless you are working with high- | performance programs, most of the time you don't care about one | or two extra branches. It's usually more beneficial to write code | that can be easily understood by a future-you or by another | person in your team: less time invested in understanding code, | more time dedicated to actually fixing issues that matter. If | performance becomes an issue, you'll catch that in profiling | easily (because everybody here is profiling before trying to | improve the performance of a piece of code, right?). | eptcyka wrote: | The more succint version of that code is easier to read and | review and it makes the code surrounding it easier to read and | review. The extra conditional blocks are superfluous and take | up a lot of lines. This is most definitely not about | performance but about coding style. | kbenson wrote: | There's usually a trade off between easiness of understanding | and elegance, because elegance usually means "works very well | given advanced understanding of the domain, task and tools". | You can usually shift the problem a bit and get a bit more of | both elegance and easy understanding with a good comment giving | the general idea. That isn't to say things shouldn't be laid | out and named sanely to make thing obvious where possible, but | any time you make assumptions about what someone else looking | at your code know or is thinking, you're opening up the future | for more bugs. It makes sense to attempt to limit that in some | respect. | | The fact that we have all looked at code and not known WTF is | going on and stepped away and come back a little later and it | was obvious should be all the evidence needed not to to assume | _too much_ about what some other programmer will understand | just by looking the code itself, especially _elegant_ code. | foobarian wrote: | I think this particular case has two reasons the optimization | makes sense; first, it's some core kernel code that possibly | gets called a ton - so high performance code. And then second, | profiling this kind of code is not easy; I don't know what | tooling there is nowadays but when I was playing with Linux in | the 90s each iteration would involve a reboot, and staring at | printk output. So there is a tendency to write code the author | thinks is faster - which may or may not be true but is probably | likely for experienced developers. | macspoofing wrote: | I understand the general point (and value) of reframing the | problem or the solution in a way that removes special cases ... | but in this case I would actually prefer the first solution over | the second. The second solutions reminds me of the old-school | perl culture, and JavaScript culture, where 'cleverness' (which | always manifests itself as terseness as if lines of code were | expensive), takes precedence over maintainability and | understandability. Your code is going to be potentially | maintained years in the future by developers of all levels - make | it easy on them by practicing the principle of least surprise. In | this case, this means using a traditional implementation of a | linked list that most developers would be familiar with. | andy_ppp wrote: | I totally agree, it's about proving you're clever rather than | communicating ideas. Maybe for something hard like Linux kernel | development this is a good thing but in most cases it just | leads to messy code that people can't understand or follow. | m_mueller wrote: | While there certainly are such cases, I think here it's just | about being proficient in a language. Pointers, referencing | and dereferencing are the bread and butter of any C code, and | applying them in a way to reduce complexity is certainly | something to strive for - if this isn't readable then I'd | argue the reader shouldn't be touching the codebase anyways. | andy_ppp wrote: | Fortunately or unfortunately you don't always get to decide | who touches the code, so unless performance is a concern as | it is in kernel development optimising like this in the day | job will eventually lead people making mistakes. Basically | I say be as explicit and clear as you dare, even at the | cost of some CPU cycles. | R0b0t1 wrote: | I was only ever taught the second, more concise one, so this | discussion is kind of confusing. How is it not shorter and | more explicit? | tent3n3 wrote: | What if someone only ever learns what looks to be too clever | to you? | | You make it sound as if some universal utilitarianism exists. | | Everyone's personal event log is going to look just different | enough to make that unreasonable. | commandlinefan wrote: | I've written code like the first example many times, and I | always felt a little "dirty" for doing it - I could tell | there _had_ to be some way to avoid treating head as a | special case, but I was too lazy to actually work out how. | The author of the second example may be "clever", but he | also didn't give up. | zem wrote: | i think a lot of the reason it looks more "clever" than elegant | is because c's syntax makes the "get the address of this field | in a struct" operation so hard to read at a glance. | xorcist wrote: | Fever lines aren't automatically better, but fewer lines means | less code to maintain. More often than not, fewer lines is | easier to read too. | | The example here is a good one. The shorter version reads much | better and is more obviously right. | qualityonly wrote: | I have one to bring up "for loops". | | I think they are infinitely worse than while loops. (I ran | into this specifically with Pandas dataframes/series) | | You save 1 or 2 lines and have ambiguity on wherever "each" | is. | | Maybe static typing solves this, but I've decided I hate | syntax sugar and dynamic typing. | baby wrote: | I think your problem is with C as a language, which can look | really ugly to the untrained (or trained) eyes, and is way too | permissible in terms of what you can with pointers and memory | in general. | | But I see this as a paradigm that you can apply in more | programming languages: you should aim at writing code that | removes edge-cases. This is not about code reduction in my | opinion, I would have been equally happy if the code had been | longer. It's about more secure code: because there is no edge- | cases to think about. | joe_the_user wrote: | I generally agree about avoiding cleverness in code. | | But this particular finesse isn't necessarily about extra | cleverness. Here the situation is that all the linked-list | nodes look the same and can be/should be treated the same. The | reason the first bit of code has to treat "head" differently is | that head is the only node that the rest of the program keeps a | pointer to. But that should be incidental, by doing the | operation indirectly, you do things uniformly, _you should do | things uniformly, since the linked list has a uniform | structure_. | | Here, it's not much about combining cases overly cleverly, not | leveraging "while (x=y){...}"-type stuff but removing what is | an unnecessary kludge; that you have to treat head differently | 'cause it's the value you happen to have and you need to update | it while preserving it. | eloff wrote: | Looking at the code there are not just fewer lines, but fewer | conditionals too. It's much simpler to read and understand. I | got it at a glance, whereas I skimmed the typical approach and | would still need to go over it more carefully to be sure it is | correct. | | I think Linus is correct on this one. | jodrellblank wrote: | > " _which always manifests itself as terseness as if lines of | code were expensive)_ " | | They are expensive! Your code maintained years in the future, | every developer has to potentially read every damn line. | | The alternative to "clever" code isn't Java and | FactoryFactoryFactories, it's short clear code, which is | different from short codegolfed code. The main difference is | nicely designed libraries and abstractions. | jcranmer wrote: | There's arguments to be made on both sides, but I think the | problem with Linus's solution here is that it doesn't quite | clearly establish the assumption being made, which gives it a | bit too much of the 'cleverness' flavor that you allude to. A | better implementation would be one that does establish why the | use of pointers make sense: void | remove_entry(node_t *entry) { // curr_ref is the | address of the link pointing to curr node_t **curr_ref | = &head; node_t *curr = *curr_ref; while | (curr != NULL && curr != entry) { // Advance | curr_ref and curr curr_ref = &curr->next; | curr = *curr_ref; } if (curr) { *curr_ref = | curr->next; } } | | Choosing names somewhat more rationally, and making it clear | that "curr" always points to the current node, and that | "curr_ref" is the address of whatever pointer we followed to | arrive at curr, makes it easier to establish the invariant that | updating curr_ref is sufficient to insert or remove curr into a | list, no matter if it's referring to the head link of a list or | the next link of the prior node. | fsloth wrote: | I have to disagree. This code is simply concise and elegant. | | Generally less code is better, since there are fever lines that | can have a bug. | | While pathological terse code can be obscure - to prove that | the author is clever - and then it really is a pathology - | simple terse code is just beautiful. | erik_seaberg wrote: | Lines of code are expensive, because we expect human beings to | reread them with almost no help from tools. Concise code is a | temporary problem for novices, until they grow into experts. | Bulky code is an ongoing problem for _everyone_ and we don't | have a solution. | Taniwha wrote: | While I mostly agree with you, I think that use of double | indirect pointers is rare enough that in effect each use of | one probably counts as a "line of code" when trying to | understand what's going on. | | The one Linus doesn't like likely runs faster (at a | microarchitectural level), it's also the thing I've done for | 40 years now, it's what comes out of my fingers when I code | linked lists, for me at least it's more understandable | because I already understand it | varjag wrote: | It's very unlikely it is faster. | 0xdeadbeefbabe wrote: | I completely disagree. Lines of code, long variable names, and | meaningless hierarchies are some tasteless taxes. Some people | like each of them(!), and I don't get it. | tcoff91 wrote: | Why are long variable names a tax when there are such good | auto completion tools? I get short variable names in | algorithm code but in business logic long variable names can | make code vastly more self-documenting. | deanCommie wrote: | I started reading the article agreeing with you. Hearing Linus | talk about "taste" made me expect something obscure and | esoteric, with a marginal performance benefit, and a | subjective/objective debate about whether the value of micro- | optimizations at scale throughout the linux kernel adds up to | meaningful benefits, and if they are traded-off with less | readable code. | | Surprisingly, I ended up in a different spot. | | I actually think this is a low-level C-version example of the | best practice of "use idiomatic language concepts". | | When C# first got LINQ, when Java first got Streams, and when | both got inline anonymous Lambda functions a lot of old-school | developers resisted both. | | "The syntax is confusing!". "The behaviour is hidden!". "What's | wrong with a good ol'd for/while loop?" | | I know because that was my first instinct too. | | But I quickly embarced these ideas because they allowed me to | write more terse declarative code and spend a larger percentage | of my TLOC on business logic. There was a small learning curve | to new developers to understand the libraries, but after that | everyone was more performant. | | I feel the same way about C and pointers and address | manipulation magic. No, it has no place in Java or C#. But for | C developers, these are their idiomatic principles and | concepts. Pretending otherwise, and not leveraging all the | capabilities possible with direct pointer and address | manipulation is not utilizing the benefits of C to it's full | potential. | | NOTE: I am not a C developer. This is just how this comes off | to me. I'd love to hear from actual C developers if they would | say that this is the equivalent of running around with a loaded | shotgun and languages like Rust have been designed with solving | these things in mind (or something). | petergeoghegan wrote: | It seems to me that what is at issue here is the difference | between easy to understand and simple. They're not always the | same thing. Simpler code tends to be easier to audit for bugs | (as a practical matter). | andy_threos_io wrote: | Pointless and bad article. | | I compiled the presented code out curiosity on arm gcc 8.2 on | godbolt.org with -O3 op and the results are: | | (the original remove is even faster then the so called elegant or | the elegant with inline) | | remove_cs101:</br> ldr r2, [r0] cmp r2, r1 bne .L3 b .L9 .L6: mov | r2, r3 .L3: ldr r3, [r2, #4] cmp r1, r3 bne .L6 ldr r3, [r1, #4] | str r3, [r2, #4] bx lr .L9: ldr r3, [r2, #4] str r3, [r0] bx lr | | remove_elegant: ldr r2, [r0] cmp r1, r2 bne .L12 b .L13 .L14: mov | r2, r3 .L12: ldr r3, [r2, #4] cmp r1, r3 bne .L14 add r0, r2, #4 | .L13: ldr r3, [r1, #4] str r3, [r0] bx lr | | remove_elegant_with_inline: ldr r2, [r0] cmp r1, r2 cmpne r2, #0 | bne .L17 b .L18 .L19: mov r2, r3 .L17: ldr r3, [r2, #4] cmp r1, | r3 cmpne r3, #0 bne .L19 add r0, r2, #4 .L18: ldr r3, [r1, #4] | str r3, [r0] bx lr | runawaybottle wrote: | How does an interviewer measure good taste? You the interviewer | could have been the result of a variety of metrics, none of which | are good taste related. Bad taste is endemic in corporate and | corporate startups(if you enter the millions in funding budget) | for the simple reason that adequate taste is more reliable. | | Edit: Within 30 seconds this got downvoted by cowards with no | response. Enough with lurker culture. Say something. | axegon_ wrote: | This is actually really easy imo. | | 1. For people who have worked on open source - just look | through their code, their commit and you'll see how they think | and operate. | | 2. If 1 isn't applicable, give them homework, not a test. Give | them a very simple but very well documented task. Something | along the lines of an authentication system, with password | reset which is time restricted, basic encryption and security | and that's it. This can be easily achieved in just about any | language in several hundred lines of code. But given the | adequate amount of time to think it through and develop it, | you'll see if they come up with clever solutions to simple | problems or a pile of duct tape hacks. | dvt wrote: | > A particularly beautiful outcome is that the implementation has | consistent semantics for the edge cases | | Not sure if this is actually a benefit or not. Edge cases are | notoriously hard to debug, so it's sometimes actually _nice_ to | have a branch that specifically handles edge cases. Conceptually, | it 's also much more difficult to wrap one's head around. I would | be interested to see how much of the cs101 solution is compiled | away and if there are any tangible benefits of being clever here. | | PS: If the linked list is stored in contiguous memory (if you're | using a slab allocator, for example), you can actually be even | _more_ clever (I 'll leave that as an exercise to the reader). | cjaybo wrote: | > PS: If the linked list is stored in contiguous memory (if | you're using a slab allocator, for example), you can actually | be even more clever (I'll leave that as an exercise to the | reader). | | This might be a dumb question, but if list elements are stored | contiguously, is there any advantage to using a linked list | instead of a data structure that is designed for contiguous | storage (something like C++'s std::vector)? | axegon_ wrote: | I gave a lot of thought on that example when I first watched the | video many many years ago. And I think it all comes down to | experience and it has far less to do with good taste. To a junior | developer the first solution is perfectly valid and easy to read | for everyone. And it is true to a certain degree. But it takes | some experience and sooner or later you start getting this | feeling that doing something like this probably has a simpler and | easier solution, for after all, this is a common thing. Once | people wrap their head around that concept(the "surely I'm not | the only one that's faced that" thought), they start finding it | very easy to jump between languages and learn new ones with ease. | setr wrote: | Another aspect is that as a codebase develops, memes inevitably | appear; patterns and strategies that replicate themselves | throughout the codebase. | | The elegant utility may be difficult to deal with standing | alone, but when you see it 2,3,4 different places (by same | author, or mimics) it becomes normalized, and once normal | there's really no reason _not_ to use it. | | The main problem with clever code is when it stands alone. | Which is also the context in which these debates occur. | geophile wrote: | I agree that the if-less solution is more elegant, but I was | surprised at the way it was achieved. I was expecting to see head | implemented as an IntListItem. I.e., an empty list would be just | the head IntListItem, with next = NULL, and value undefined. | | I believe that this approach has the advantage of being clearer. | | Admittedly, one drawback of this approach is that it uses more | space, which could be an issue in an application where you have | many lists, nearly all empty. | xiphias2 wrote: | There shouldn't even be an IntListItem type, just IntList, or | it could be typedef-ed to make the API clearer. In that case | there no need to be for special case, and NULL is the empty | list. | crazygringo wrote: | Like others here, I prefer the first. | | The first one -- I read it, I know what it does, it seems | intuitive to understand and expect it to be bug-free. If someone | else has to modify it later, I'm not particularly worried they'll | mess it up. | | The second one -- it took me about 4x longer to understand what | it does. It works too, but it doesn't match how my brain | naturally thinks about it, so it leaves me with the uneasy | feeling "what if there's a bug?" and I'd be more worried someone | else would introduce a bug if they had to modify it later. | | I don't want "elegance" or "good taste" in code. Unless it has a | good reason to be hand-tuned for performance, I want code that is | written the way you'd expect an average programmer to write it. | Nothing clever, just a straightforward translation of ideas into | code. Not "transforming" them into something more "elegant". | toast0 wrote: | > so it leaves me with the uneasy feeling "what if there's a | bug?" | | The second solution takes me a bit longer to wrap my head | around, but once I do, I like it more, because there's fewer | conditionals, and so there's less chance that the function | breaks on unusual conditions. | | Using fewer local variables is also a nice plus. | foobarian wrote: | In case of the optimized version, I would have liked to see | comments explaining how much faster it was vs. the obvious one | in a profiling experiment, and see it accompanied by a unit | test to take care of the "what if there is a bug" concern. | warmfuzzykitten wrote: | The trick employed by the "good taste" solution is to ignore the | type of the object containing the pointer, which is different for | the head, and focus only on the type of the object pointed to, | which is the same for all the pointers. This frame of reference | shift makes the solution look tricky to people unaccustomed to | working with pointers, or languages in which pointers are not | explicitly represented. It is also not quite true, as the pointer | in the last element in the list does not point to a list element. | nemetroid wrote: | I'm not sure why the article removed comments from the code and | replaced variable names like "indirect" with "p". Here are the | two code samples verbatim from Linus's presentation: | remove_list_entry(entry) { prev = NULL; | walk = head; // Walk the list | while (walk != entry) { prev = walk; | walk = walk->next; } // Remove the | entry by updating the // head or the previous entry | if (!prev) head = entry->next; else | prev->next = entry->next; } | remove_list_entry(entry) { // The "indirect" | pointer points to the // *address* of the thing we'll | update indirect = &head; // | Walk the list, looking for the thing that // points to | the entry we want to remove while ((*indirect) | != entry) indirect = &(*indirect)->next; | // .. and just remove it *indirect = entry->next; | } | forrestthewoods wrote: | Perfect. I almost commented to complain that the "elegant" | version is harder to read and understand. This is much much | better. Comments are important! | brainwipe wrote: | Thank you, I found that much easier to read than the article | posted. | [deleted] | btilly wrote: | Most people prefer the first over the second. But I think that | Linus really should prefer the second over the first. Let me try | to explain why. | | There is a well-known saying attributed to David Wheeler, "All | problems in computer science can be solved by another level of | indirection." Except the problem of having too many layers of | indirection. Also both quotes are often seeing with "abstraction" | instead of "indirection". | | There is a not well-known saying that you can attribute to me, | "Any method of abstraction that you have internalized becomes | conceptually free to you." (So for the sake of others, choose | wisely which you will expect maintenance programmers to have | internalized!) | | The key to the elegant solution is understanding how to | manipulate data through pointers. | | That makes the elegant solution inappropriate to use in CS 101. | It involves a method of indirection/abstraction that is | emphatically NOT free to beginning programmers. | | It also makes the elegant solution inappropriate for most people | on HN. We do not directly deal with manipulating things through | pointers very much. Therefore most of us have not internalized | how to do that, and the technique is very much not free to us. | | However Linus is a kernel developer. Anyone maintaining his code | will also be a kernel developer. Kernel developers have to | internalize how to handle manipulating data through pointers | because their APIs require it. For example look at | https://www.kernel.org/doc/html/v4.14/filesystems/index.html and | see that pretty much every function gets a pointer to a data | structure, and then manipulates that data structure through the | pointer. | | Therefore every kernel developer should internalize how to | manipulate data through pointers. And the elegant solution | therefore becomes not just less code, it _becomes conceptually | simpler!_ And yes, any time you can replace a block of code with | less code that is conceptually simpler, this shows good taste. | | BUT, and this is very important, it is only conceptually simpler | if you've already internalized concepts around manipulating data | through the indirection of a pointer. Which makes it conceptually | simpler for kernel developers like Linus, but not for most | programmers in other languages. ___________________________________________________________________ (page generated 2020-12-06 23:00 UTC)