8 ms·
What is the intended use of auto in these cases in a statically typed language? It seems counter-productive because in C++ writing type annotations is an aide t
by mlthoughts2018 8y ago
What is the intended use of auto in these cases in a statically typed language? It seems counter-productive because in C++ writing type annotations is an aide to the code reader to understand the code author’s intent. Making the code reader also infer types of constituent variables, and remember those types in their head as they read the surrounding code where some e.g. structured binding was used to assign to some variables — this seems like a very bad thing to do to code readers and you’re gaining hardly anything in terms of terser syntax.
Compared with Python, say, where you always know that the burden of understanding how a variable is used is part of the reader’s responsibility, maybe with conventional help in docstrings or optional type hints, this C++ feature seems like the worst of both worlds. You still have to add some annotation (“auto”) and keep in mental cache an understanding of the types, instead of just being nice and explicit about it and declaring things with the types.
So you don’t get the freedom of duck typing on the auto’d variables, and you trade for slightly reduced syntax instead of explicit annotations for the reader.
It seems like it would immediately become a C++ anti-pattern to use structures binding. It’s cute instead of pragmatic.
- tzahola 8y agoYou assume that the point of using types is readability, which is false. Types let the compiler reject a large set of invalid programs, which would otherwise produce a runtime error in a dynamic language. for (auto foo : foos) { foo.bar() } I don't care about the exact type of `foo`, so I let the compiler deduce it behind the scenes. However, I still want it to break my build if `bar()` doesn't exist!
- mlthoughts2018 8y agoYour example does not convince me. As a code reader, I would care very much why does foo have a bar() method. Is it because foo is a Widget or a subclass thereof? Then I can backtrack to understand the implementation being inherited. Or maybe it’s some other class that uses bar() for a totally different purpose. Who cares if the compiler can deduce it. That’s not helpful to me at all if I am scanning this section of code because I have to add more method calls inside the loop to calculate new results, and I don’t know what base type foo even is to get started with what would be required. > “You assume that the point of using types is readability, which is false.” This just seems wrong to me. Communicating your designed intention with type annotations is the number one reason to use them, whether you’re writing C++ or Haskell. Getting the compiler’s help to prove correctness or avoid bugs is a secondary reason. Sometimes it becomes the primary reason in isolated cases. But usually it’s a secondary consideration nowhere near as important as using types to communicate the design of the program.
- jcelerier 8y ago> This just seems wrong to me. Communicating your designed intention with type annotations is the number one reason to use them, whether you’re writing C++ or Haskell. sure, but most haskell programs leave type annotations out, it's only some specific parts where it enhances clarity that it helps ; most of the time it would be unneeded noise, just like in C++. I really don't want to go back to the dark days of for(std::vector<std::pair<float, int>>::const_iterator it = v.cbegin(), end = v.cend(); it != end; ) { if(it->second > 1 && std::distance(v.begin(), it) % rand() == 0) it = v.erase(it); else ++it; } versus for(auto it = v.cbegin(), end = v.cend(); it != end; ) { if(it->second > 1 && std::distance(v.begin(), it) % rand() == 0) it = v.erase(it); else ++it; } In a lot of cases, it can even cause bugs : for instance, the iterator type of std::map<int, std::string> is std::pair<const int, std::string>. If you ever happened to write : std::map<int, std::string> map; // ... const std::pair<int, std::string>& value = *map.begin(); then congrats, you have unneeded memory allocations because the pair will be copied in order to match the explicit type. whereas here : const auto& value = *map.begin(); you know that you get exactly the correct type - maybe at some point you will replace with a boost::flat_map or another which has a different iterator type, and when you do this, you won't need to change any other code anywhere else - it will be automatically correct.
- mlthoughts2018 8y ago> “sure, but most haskell programs leave type annotations out, it's only some specific parts where it enhances clarity ” This is very false, I can tell you from having written Haskell in production for several years. It’s widely regarded as an anti-pattern in Haskell to leave out type annotations, even for constant values, except in the most trivial of cases. Type annotations are a design feature of Haskell. You usually use the “type” keyword to define custom type names for primitive types just to give them descriptive, application-specific names, like type PhoneBook = [(Name, Number)] just so you can add more explicit annotations and communicate the flow of your program very explicitly in the type annotations. You definitely do not look at it like minimally using type annotations “only where it adds clarity” because the whole point is that as the code author you don’t know where clarity is needed. That is for the code reader who has to change your work later on. Incidentally, I actually very much think that your first example of that for loop at the bottom is a lot clearer than the one using auto. Just make a descriptive typedef / typename of that long const_iterator type. Make something readable so that we know why there is a vector of pairs of (float, int). If you want to cut down the syntax there, auto is not the right way. A descriptive typedef is the right way using type alias names that communicate your intended design.
- beojan 8y agoIt's to avoid having to write out complex types that the compiler already knows about. It's particularly useful with generic code that makes heavy use of templates, or code that uses iterators. In these cases, it just isn't that useful for the reader to know the exact type.
- EliRivers 8y agoauto can be very handy in writing template code; situations in which the type depends on the templated type(s).
- sclangdon 8y agoI think it was introduced to help with things like lambdas, or other structures that the coder may not know the type of, or it would be difficult to work out the type. Then the usage of auto was expanded to help when writing generic code. I think both of these cases are valid uses of auto, but people seem to have run away with it and will literally use it for statements like auto i = 0; As a professional C++ coder, I personally don't have a problem with auto (or var in C# for that matter) but I'd prefer people use it where it's useful rather than for every little thing.
- jzwinck 8y agoI agree, but TFA said: > modern C++ style using auto almost whenever possible. Using auto everywhere, to me, is bad code. It's hard to understand what types we're dealing with, at a glance.
- dingo_bat 8y agoIf you use an IDE, it is very easy to look at the type inferred by auto. So no worrying about types unless you want to.
- mlthoughts2018 8y agoWhat if you’re trying to read the code in a GitHub PR diff? Now I can’t review it because I actually have to go waste time getting the code into some IDE that offers me hovering type info? And what if you use only a console-based editor? Now you gotta abandon the developer tools that work for you because of some language feature? It sounds crazy to me to hear someone say that it’s OK to tolerate the downsides of a language feature because some other tool like an IDE might mitigate them in some cases. Why are we contorting our tools to overcome language problems and acting like it’s OK, instead of designing better languages in the first place that don’t require tooling gymnastics to make them readable?
- Game_Ender 8y agoThis a matter of debate in the C++ community. The “almost always auto” camp has the position that it makes the code more flexible and easier to refactor. The fewer times you write a type name the easier it is to change. It also has its uses in template heavy code which is popular on modern C++. There is a group such as your self that value the extra context the type provides. I think the counter to is that modern tooling can provide this information to you. To split the difference you can use auto just where the type is redundant, like with make_shared, or verbose and unneeded, like with iterators.
- mlthoughts2018 8y agoIt might be easier to change types behind the scenes when using auto, but why would someone feel that is going to be a common case? When are you completely changing types all the time? More often, the types don’t change all that much, but their behaviors get extended and new features have to be calculated in some block of code. If you can see the type explicitly, then you know what operations it supports, etc. If I come across a structured binding where x and y are auto assigned as the unpacking of struct fields whose types might be complicated, now I have local variables x and y that I cannot reason about until I backtrack manually to figure out what type they will be deduced to having (and then keep that in memory any time I’m tinkering in that section of code). I guess for the cases when big structural overhauls are happening behind the scenes to radically change underlying types and designs, auto would reduce a bunch of rework on type annotations. However, I just can’t believe anyone would allow that to be a common enough use case for it to matter more than explicit type annotations.
- susam 8y ago> If I come across a structured binding where x and y are auto assigned as the unpacking of struct fields whose types might be complicated, now I have local variables x and y that I cannot reason about ... One is free to not use the auto keyword in a scenario like you mention in order to make the types explicit regardless of what the "Almost Always Auto" camp says. However there are scenarios where leaving the type out does not degrade readability and sometimes improves readability. Often cited examples of situations where it may improve readability usually look somewhat like this: void print_vector_of_maps(std::vector<std::map<std::string, std::string>> v) { for (const std::map<std::string, std::string>& item: v) { for (const std::pair<std::string, std::string>& entry: item) { std::cout << entry.first << ": " << entry.second << '\n'; } std::cout << "---\n"; } } With the auto keyword, it looks like this: void print_vector_of_maps(std::vector<std::map<std::string, std::string>> v) { for (const auto& item: v) { for (const auto& entry: item) { std::cout << entry.first << ": " << entry.second << '\n'; } std::cout << "---\n"; } } The types are still known from the surrounding context and the readability has arguably improved.
- jcelerier 8y ago`auto` does not make your code any less statically typed : http://webm.land/media/hJeQ.webm http://webm.land/media/hJeQ.webm .
- mlthoughts2018 8y agoWho said that it did? I never said anything like that: only that auto removes useful type annotations.
- roel_v 8y agoThere are cases (when writing template libraries) where you don't know the types you'll be using yet, and you need auto to express what you want. Apart from those (admittedly) rare cases, after a few years using auto, I'm not as opposed as I was. Generally I use auto by default, and replace it with the actual type when I re-visit that code and have to actually think what the type would be. I'd guesstimate that I do that in, maybe 10% of the cases where I initially used auto. Makes code much more readable (as in, 'reading over'), too. Turns out it's only when you try to reason an algorithm through line by line that you actually need the type, and in the other cases (where you're just roughly following along, which I find happens more often than the other case) having 'auto' abstracts away some of the mental overhead of having type declarations.
- mlthoughts2018 8y ago> “Turns out it's only when you try to reason an algorithm through line by line that you actually need the type,” Yes, but for other people reading your code, this would be way more often than 10% of the time. So you save a tiny bit of effort in 90% of cases for you, while other people have to exert more effort in probably 90% of cases for them. It seems like the minute the number of people needing to read and understand that section of code exceeds 2 (you & one other), then this type of use of auto is actually increasing the amount of overall work required linearly for every additional person added in.
- roel_v 8y agoI don't really agree. I don't think there is much difference between reading code you wrote a few weeks ago and code someone else wrote - at least not in this context.
- mlthoughts2018 8y agoWe might just have to agree to disagree, but I would disagree with your claim here in the strongest possible terms. When someone new comes into a project, you can’t assume what skill level they’re at, how they think about code, what design patterns have been most strongly used in the teams they were previously on. Code you wrote a few weeks ago is still going to be hugely similar to the types of code you often write, the ways you think about design patterns, etc., especially if you’re on a team for a long time. Code someone else wrote very frequently looks completely alien, especially for new people, or someone from a different team that has to collaborate on one small cross-cutting problem, or people who look at your code long after you left the company. Using things like auto because it has some benefit as you write the code, in your mind, is deceptive for exactly this reason. Those criteria are hardly ever still relevant when it is someone else reading the code.
- SloopJon 8y agoType inference (or type deduction, as it's called in C++) is the number one reason for the resurgence of static typing these days. I for one would find C# and C++ much less pleasant both to write and read without it; so much so that I would probably use a dynamically typed language instead. Your perspective is that type annotations are there for the reader. I agree with tzahola that type safety itself is valuable in its own right without forcing the user to type little reminders to himself everywhere. Thanks to language servers, I can hover an expression in a text editor and remove all doubt. I also agree with the other responses that if any language benefits from type deduction, it's C++. No way would the code be more readable if you were forced to be explicit about some of the dependent types in the STL. That said, you are obviously not alone in your perspective. I've heard the exact same arguments from experienced C# programmers.
- mlthoughts2018 8y agoIt’s funny because for me, I spent well over a decade as a Python engineer. And the thing I find most valuable about statically typed languages is the act of writing type annotations, creating typedefs, etc., as part of the overall design of a program. Type safety and compilation have never really mattered to me — it was always extremely easy to write safe programs in Python, without crazy indecipherable language grammars or long compile times. I would say having written and maintained large programs in C++ and Haskell, it is no easier to write safe programs in those languages than in Python. The compiler doesn’t save you time by catching bugs, that stuff is all just myth. But what it does offer you is a way to communicate the design of your program through efficient type annotation, and give a lot of clarity into how things are intended to flow, and exactly at what points certain types of extensibility can be implemented. I’m sure this can vary by project and personnel, but it has been really true for me. It is so, so easy to write safe, huge programs in Python, and the fear of hitting an uncaught runtime problem after lots of wasted time is misguided. Meanwhile, the benefit of static typing in Haskell, for example, is that I can create stub functions for everything while working on the right type-level flow of operations. Then I have a nice end to end skeleton code with expressive application-specific type descriptions as I start to fill in the implementation for the stubs. And at the end, people can understand the code from reading the flow of type annotations.