5 ms·
> 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 H
by 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.
- jcelerier 8y ago> 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. Are you serious ? In standard Haskell you can't even add type annotations to bindings, that's how much type annotations are relevant to the language. They're only available in type & function definitions. I seriously doubt that you use {-# LANGUAGE ScopedTypeVariables #-} and that all of your code looks like main = let (x :: Integer) = 123 in print $ x That's literally what you expect C++ programmers to do when you ask them not to use auto, and I've absolutely never seen production Haskell looking like this - common Haskell is much closer to this : https://github.com/ivanperez-keera/haskanoid/blob/master/src/Game.hs https://github.com/ivanperez-keera/haskanoid/blob/master/src....
- mlthoughts2018 8y agoNah. ScopedTypeVariables is extremely commonly used, and Haskell as a language is one where frequent use of extensions is common generally. Saying “you can’t even add type annotations to let bindings” shows a misunderstanding of Haskell, because you can and the way the language accomplishes it is with ScopedTypeVariables. It’s not “special” or “extra” in Haskell, using extension pragmas is just a super common, mundane, every-day thing in Haskell. I should also point out that when you say > “They're only available in type & function definitions.” you are wrong. Firstly, you can (and should almost always) use type annotations for plain values, like pi :: Float pi = 3.14 and e.g. with more complex data constructors like in < https://en.m.wikibooks.org/wiki/Haskell/Type_declarations https://en.m.wikibooks.org/wiki/Haskell/Type_declarations > section on constructor functions. For value constructors that use complex type dynamism in Haskell, like the return value polymorphism in Haskell regular expressions, it’s critical to use type annotations on plain values, because the type annotation actually resolves which of the polymorphic return types will be chosen. Secondly, and more importantly, you can use type annotations on bindings natively in a where clause, and this is a very fundamentally important thing to do, especially for locally defined helper functions. Then you only need ScopedTypeVariables if your helper function needs to involve a generic type constraint from the polymorphism of the outer function declaration, which is vastly more rare. I’d also say the comparison with the types of local variables that tend to be unannotated in Haskell let or where clauses is very disingenuous and it’s actually not similar to the iterator boilerplate example you gave before at all. When all the arguments have well-annotated types, and you use a let or where variable to hold a temporary value, the type is inferred by a reader much more easily than when using auto in C++, and has a direct meaning in terms of the calculation at hand (e.g. it’s not done to reduce boilerplate like auto). For example, if you’re trying to calculate distance between two tuples (x0, y0) and (x1, y1), where the types of those tuple elements are given explicitly just a few lines up, then there is little harm in doing something like where dx = x1 - x0 though it’s still preferable to say e.g. where dx = x1 - x0 :: Float This is emphatically not what you’re doing with auto, because the types are not otherwise locally obvious, and even if they were, a judicious use of typedef / typename saves just as much syntax while being far more descriptive.
- fpoling 8y agoWith reasonable typedefs the first loop becomes: for(my_vector_t::const_iterator it = v.cbegin(), end = v.cend(); it != end; ) { ... } and if that looks long, then the problem is poor choice of type names in STL. I.e if the const_iterator would be citer, then with for(my_vector_t::citer it = v.cbegin(), end = v.cend(); it != end; ) { ... } the need for auto is greatly reduced. Besides, if one just uses indexes, the loop becomes: for(sized_t i = 0; i != v.size(); ) { if (v[i].second > 1 && i % rand() == 0) { v.erase(v.begin() + i); else ++i; } that does not have dangling reference bug from the original loop, easier to follow and with sane compiler one gets the same code. Surely the code only works with vectors, but when one deals with vector of pairs, it implies very specific problem that rather unlikely to change. It also show that the need for auto really comes from the design of STL that leads to very long types that auto mitigates.
- jcelerier 8y ago> Surely the code only works with vectors, but when one deals with vector of pairs, it implies very specific problem that rather unlikely to change. maybe, maybe not. I often have much deeper changes than this. > It also show that the need for auto really comes from the design of STL that leads to very long types that auto mitigates. the same problem exists with int32_t or uint32_t or even int or char. The point is, using auto makes your code much more resilient to changes and much less likely to introduce bugs or performance losses due to unwanted conversions.
- AstralStorm 8y agoIn fact, in templated code decltype(v)::const_iterator might be preferable to plain auto in some cases as it forces a type that does have one of these...