One thing that really annoyed me about D is that its documentation lists basically every function with an 'auto' return type. Auto should be completely banned from documentation, it's a complete hindrance. It's the single biggest contributor to why I stopped using D for personal projects - I was so tired of having to hunt down what functions are going to return, sometimes having to resort to just using it and looking at what the compiler complains about.
And that's a huge shame. Because in general I really liked using D.
The use of Auto is requires in some places because the standard library returns types that cannot be named in the context of the calling function. This happens for example with algortihms that return a custom Range implementation that is declared within the scope of the function implementing the algorithm.
I am not sure what to make of this pattern. At least the documentation should be more explicit about these Voldemort types. Documentation has other issues as well. The standard documentation generator doesn't cope well with version statements (conditional compilation), potentially skipping docs for stuff that wouldn't be compiled into a particular build variant.
fn map<U, T, F, I>(it: I) -> auto
where I: Iterator<Item=T>, U: From<T>
{
it.map(|t| From::from(t))
}
The type signature of the first one clearly tells me that the return type is an `Iterator<U>`, even though the actual type cannot be named because of the anonymous closure.
The second one leaves me guessing what the return type is.
If the actual type cannot be named, it is rarely the case that this is all there is to it. Usually, users are expected to use that type "somehow" (it is a `Range`?), and that means that there are some interfaces that these types implement.
This wouldn't work for D. D doesn't constrain return types to something less than what they are. A Range is not just an Iterator, it has optional pieces that depend completely on the given type.
For example, the return of map could provide indexing, or it could provide forward and backward iteration, or it might have methods that are completely unrelated to the type.
There is no good reasonable and non-confusing way to describe all the things map could return depending on the input. It's much better to just describe it conceptually in the human-readable docs, and let the person understand the result.
I'll note that just above the function map in D's source is the documentation. You just need a little more context, and it will describe what map returns in a much more (IMO) useful fashion than a return type that might be several lines long and consist of various static conditionals:
"The call map!(fun)(range) returns a range of which elements are obtained by applying fun(a) left to right for all elements a in range."
This is the difference between duck typing and generics.
To allow that you would essentially need to say that "if the input implements `Index`, the output implements `Index`":
fn map<U, T, F, I, O>(it: I) -> O
where I: Iterator<Item=T>, U: From<T>,
O: impl Iterator<Item=U>,
I:?Index<Target=T> -> O:?Index<Target=U>
{
it.map(|t| From::from(t))
}
The type system implementation already supports these types of constraints, but there isn't a language extension that exposes that. I don't see any fundamental reasons that make this impossible, but there are many trade-offs involved.
Notice that, for example, the output Range does not implement the same interfaces as the input range, e.g., the input Range implements an `Index` interface over a range of `T`s, but the output Range implements an `Index` interface over a range of `U`s. In D this is super implicit in the implementation details (body) of an equivalent `map` function, but in Rust it needs to be part of the type signature to avoid changes to a function body to silently cause API breaking changes. In D, you could change the body of map to map only from Range(T) -> Range(T), without changing its interface, and that would break all code using it to map a Range(T)->Range(U).
Though it doesn't work for D, it could work for the documentation of D (what is being discussed), in some usefully hand-wavy way.
If I'm working in a typed language, and are dealing with functions max(a, b, c) and list(a, b, c), I would expect the documentation to say that one returns T, whereas the other a list(T). If it says auto, then I'm guessing from the names.
Maybe the target audience is programmers familiar with dynamic languages, who don't care so much and are used to reading the descriptions of functions about what is returned.
What am I looking at there? Are those all traits on Vec that I then have to parse mentally so I can understand what I can do with it? Are all those pages basically to say "Vec works like an array of T"?
I've dealt with generics in other languages such as Swift and C#, and they were substandard to D's templates IMO. I remember in C#, I could not get a simple generic function that accepted both a string and Int to work, so I just gave up and wrote multiple functions without generics.
I'm sure some people find this documentation helpful, but it doesn't look as useful to me as map's simple one-liner.
> What am I looking at there? Are those all traits on Vec that I then have to parse mentally so I can understand what I can do with it? Are all those pages basically to say "Vec works like an array of T"?
The others are separate abstract operations which are available (implemented) on vecs e.g. AsRef/AsMut denote that you can trivially get a (mutable) reference to the parameter from a vec. The implementations are similarly trivial (https://doc.rust-lang.org/src/alloc/vec.rs.html#2348-2374).
> I'm sure some people find this documentation helpful, but it doesn't look as useful to me as map's simple one-liner.
I’ve not looked much into D, but I’ve really been enjoying Rust.
I think the main takeaway is that there are very different ways of approaching language design. In Rust there was a decision to make the function signature the single place which defines the guaranteed input and output types to a function, but that is a trade-off. It encourages a more complex type system, as the flexibility of functions is on a sense constrained by the type system. Personally I like that explicitness, since there is only one place to look. In the future features like const generics and GATs will make that more powerful.
But on the other hand, D appears to be able to support much more complex types (possibly dependent types?) by not requiring that the type system can express them directly. In a sense the whole language can be used to define types. That’s a cool thing to be able to do, even if it means having to inspect documentation and method bodies to work out what they do.
On the "auto auto", I'm pretty sure I filed a bug report on that. There are alternate D docs that don't produce the "auto auto" (and it goes without saying that it isn't that way in the code itself).
"Returns:
A range with each fun applied to all the elements. If there is more than one fun, the element type will be Tuple containing one element for each fun."
To get that degree of flexibility in Rust you’d have to turn to macros, then the return type will depend on the generated code. That could be pretty much anything, so you’d need to read the docs anyway. Perhaps the languages are not that different after all.
D’s syntax for the template body looks much more similar to normal D code, in Rust the pattern macro syntax is like a language to itself and procedural macros need a fair bit of boilerplate, including explicit “quote” blocks.
This looks insane, mostly because there must be some repeated code in all these impls.
The D way of solving this is to statically query the properties of the passed in type at compile time whenever a part of the template needs to be specialized. It can make for very concise code, but you can't name the exact input type with this approach.
That's the salient point of this thread though; Rust doesn't have type inference in any position that shows up in API documentation. (The closest thing Rust has is return types of `impl Trait`, but even that imposes a contract that the caller must adhere to and that the callee cannot see through.)
Which helps out the documentation side, but destroys code readability. Particularly since rust users appear to really like creating long method call chains, frequently with a closure or two sprinkled in. Take this "example" https://github.com/diwic/dbus-rs#server. For a beginning user of the library that is nearly impenetrable without breaking each of those calls apart. Even if your pretty familiar with rust you still have to break it apart and explicitly place the types in the "Let" statements to know the intermediate types in order to look up the method documentation.
This style of coding is so bad, that it turns out the example has a syntax error. Good luck finding it without the compiler or a quality editor though. Worse, the example doesn't actually work due to further bugs.
Anyway, rust by itself may be ok. Some of the core concepts are good, but the way people are using it is leading to inpenteratble messes of code. Code like the above combined with what seems excessive/unnecessary use of generics create problems for more advanced usage when it comes to learning and modifying a piece of code. Some people have blamed this on the language's learning curve, but I'm not sure that is appropriate. By itself the language is fairly straightforward, the difficulties occur when people are working around the language and pile in masses of write only code.
That particular code block IMHO is why rust is going to have a hard time truly gaining widespread usage. Even as someone somewhat familiar with rust, moving the example into a program, and modifying it in fairly trivial ways took me the better part of a day.
> Even if your pretty familiar with rust you still have to break it apart and explicitly place the types in the "Let" statements to know the intermediate types in order to look up the method documentation.
Maybe this is just me misreading your phrasing, but why would you actually have to break it apart into `let` statements? You can look up the types without modifying the program. Or are you talking about asking the compiler for the types with the `let _: () = ...` (or similar) trick? At that point you can just ask an IDE, also without modifying the program.
`auto` is just a keyword, that's used in D and C++ to implement many many different language features.
Rust does not have
fn foo() -> let { ... }
where
fn foo() -> let { 0_i32 }
let x: i32 = foo();
fn bar() -> let { 0_f32 }
let y: f32 = bar();
That is, you can't have an opaque function return type, that's both opaque, but simultaneously the user can name and use all interfaces from.
If you change the implementation of `bar` with such a feature to return `i32` instead, all calling code of `bar` would break. And that's precisely why Rust doesn't have D/C++'s `auto` in return position.
OP's point was about auto being littered in documentation and not in the code itself.
Having auto is a boon for certain design aspects. As system level programming language D offers everything in betterC mode. Of course it can offer more. But a small community can do only so much.
It's not "littered" in the documentation for anyone who understands the basics of D's ranges. auto is the correct choice here. Range functions are lazy, meaning they are almost always used in a chain of function calls that ends in something like `.array` to get a concrete type -- an array in this case. At no point in that process do you care about the actual return type of any of those functions.
Is Range a kind of interface? If yes, then wouldn't that be the appropriate return type?
edit: Looking at other answers, I think Range is probably not an interface like they exist in Java, but rather a pattern of behavior per templates in C++. Concepts are supposed to solve this problem in C++, but I don't know how well they actually do.
A Range is something that implements one or more interfaces depending on its properties and guarantees. So in order to name a Range that way you'd first have to create interfaces for all possible guarantees. That doesn't sound practical. It's analogous to C++ containers implementing common concepts without deriving from corresponding interfaces.
In D, you don't declare that a type X should have operations A, B, C. Instead, at the moment of template instantiation, you can verify if the provided type has operations A, B and C.
You did pick a particularly nasty example for that one. I do agree that it is not so easy to read these constraints. It can also be a bit frustrating when you need to chase down why exactly a particular line of code doesn't meet such a constraint. Tests like isInputRange are themselves fairly involved expressions and in the worst case, you end up staring at those after a template instantiation failed.
>so tired of having to hunt down what functions are going to return, sometimes having to resort to just using it
With highly generic functions, it's often not possible to know what they'll return without knowing what you'll call them with. Especially given that D functions like "map" and "reduce" tend to return special iterator types so that the compiler is able to smarty fuse them where possible. If D had concepts like C++20, you could probably describe them with something looking like:
But at least for me that doesn't seem like it would be much more helpful than just reading the documentation, which states what the function returns, if not necessarily the type.
>With highly generic functions, it's often not possible to know what they'll return without knowing what you'll call them with.
Of course it is. Map's type is "(a -> b) -> [a] -> [b]". D just absolutely and completely failed here, despite this being a solved problem 40 years ago.
Not in D, it isn't, for performance reasons. It takes an input range and returns a type that iterates through that range applying the callable (doesn't have to be a function!) to each element as requested.
>and returns a type that iterates through that range
Which should have a type. The entire point is that this is a solved problem, there is no excuse to simply throw up our hands and say "screw documentation we'll just say this function is a mystery".
Functor f => (a -> b) -> f a -> f b
And please don't miss the point and tell me D doesn't have Functor. The entire point is that D has something, and it doesn't tell us what that something is. It should. Documentation is good.
Or rather, D doesn't have concepts (of which 'Functor' is a special case); that is, the notion of a type that is characterized by having the ability to execute operations is not expressible in its typesystem.
Or rather, it is, but only with classes. You want something like "a return type; fulfilling the condition of being able to be used in this way." This is not something you can specify as a function attribute in D. Instead, ranges use a form of duck typing. The next step in the call chain can tell whether the previous step gave it something it can use using template inconditions, ie. `isInputRange!T`. But the previous step can't assert that it is returning a type that fulfills a constraint. In other words, there's type inconditions but not type outconditions.
What you're describing has names - structural types, refined types (a.k.a. contracts a.k.a. pre- and post-conditions)...
It's simply a failure of D the language/compiler (and a huge anti-pattern) to not expose internal types in a way that can be displayed to the programmer.
No such internal type exists. The range interface is purely a library feature. The problem is that D has no way to include a type constraint as a part of the function type, at present. Something like out template contracts would do it probably.
> > and returns a type that iterates through that range
> Which should have a type.
It does. But the name of that type depends on the type of the range and on the callable.
> Functor f => (a -> b) -> f a -> f b
This doesn't work because the return type isn't `f b`, it's `g b` where g depends on what f is. It also depends on the callable, because the first parameter isn't necessary a function. The closest is
Callable c, Range r0 => c a b -> r0 a -> r1 b
Where `r1` isn't even a concrete type but a type that depends on both `c` and `r0` and is made up on-the-fly (per instantiation).
> Documentation is good.
I agree. How would you suggest improving the signature of map given that D doesn't have typeclasses? Or with types that depend on other types in the template?
class Range g => FunctorTo f g | f -> g where
fmapto :: Callable c => c a b -> f a -> g b
is how you would define that class of types in Haskell. It says "If g is a range, then a pair of types f and g satisfy the FunctorTo interface[1] when knowing f determines g, and there's an implementation of fmapto with this type".
Maybe D needs typeclasses. This thread has certainly put me off of D, because being able to write down types is really quite important to me.
[1] The Haskell class keyword is defines something closer to an interface than an OO class.
> because being able to write down types is really quite important to me.
The issue arises only with generic heavily templated functions. Nothing in D forbids you to write your programs with all types explicitly written down. That's btw how I mostly write my code.
> That should not be a problem. It is a problem in D because of a lacking in D.
It is not a problem, the compiler copes with it. The problem comes from the fact that such a type is absolutely not interesting to know how it is written. The unmangled type is unreadable.
Yes, the type is very interesting to know. That's why other languages make it known. Ignoring the entire discussion to reply twice with "nu uh!" is not very productive.
There's usually no reason to know the return type of any range-based function in D. It's not like auto is applied as a return type willy nilly. And anyone who understands D's ranges and how they are used should have no problem seeing a function declaration that returns auto.
Not quite. I'm saying that in this case it doesn't matter because of the way the API is used. Is it confusing for people who don't understand D ranges? Yes, it certainly can be. It was for me when ranges first came along. But once you understand how D ranges are intended to be used, then you realize you rarely ever care what the return type is. D is not Rust, or C++, or (insert language here).
When the return type actually matters, auto should be avoided unless there's no way around it. But that's why we have "Returns:" in Ddoc. The function signature itself is not the complete documentation. I mean, you're acting like all D functions are documented to return auto. They aren't. It's used where it needs to be.
> The language needs fixed so it can express its own types.
The language expresses its types just fine (it's in the mangled name in the object file). The issue is that there is no point in the human readable form of these types.
It gives us much more information than "something". It tells us it is a list of the type of the second argument to the function we passed it. Or in the generic version a "something you can iterate over" of things of the type of the second argument to the function we passed.
I have seen few videos by Walter Bright, Andrei Alexandrescu on ranges and I have no problem understanding D`s documentation. Maybe you should learn the language before using it.
If something is unclear, you can ask for clarification. I repeated the statement to three people because three people repeated the same argument to me. This is how conversations work. I have very limited exposure to haskell, am not detached from reality, and am taking nothing personal. Of course the discussion is serious, why would I spend time engaging in a frivolous and meaningless discussion?
You don't have to wait for the compiler to complain. Using:
pragma(msg, T);
where T is any type will print the type to the screen during compilation. pragma(msg) will print all kinds of things, making it a very handy tool to visualize what is happening while compiling. I use it all the time.
It's often because these functions have unnamed types. Chain of lazy computations in D often return unnamed types (so called "Voldemort" types) because finding good names for those inner structs is a challenge, they have a single use (which is to have a particular signature).
> Chain of lazy computations in D often return unnamed types (so called "Voldemort" types) because finding good names for those inner structs is a challenge
There is something so absurd about having "unnamed types" as an antipattern!
Not really - the concrete type isn't important, but what you can do with it is. One could argue that instead we'd use a concept in place of `auto`, and Bjarne has argued exactly that for C++.
> Not really - the concrete type isn't important, but what you can do with it is.
Then surely that's what should be shown? Rust uses `impl <Trait>` for that, the actual return type is opaque but you know it implements the specified trait.
Not everything is worth naming, if there isn't an obvious good name for somethink (like say, a Java Anonymous Classes) then why not allow it to have no name?
Java's objects aren't types even though they're mixed up with its type system. Objects are supposed to represent units of computation so anonymous classes aren't absurd.
But the reason why it seems that types without names are absurd is that types are only real for the interpreter or compiler. At runtime they aren't used anymore. So it's absurd that a construct made for humans to understand and describe code starts to become something opaque to human understanding because they're impossible to be named.
I've felt this as well, been using D for a couple years now, and this is the kind of thing that just makes me have to context switch more than I'd like. With the current implementation of the language it's hard to avoid, and function's return type can be quite complex, so writing it down can be hard.
Another reason is the (ironically) dynamic nature of a return type. E.g.
auto whatDoesItReturn(int i)() {
static if (i == 0) { return int.init; }
else { return string.init; }
}
Template code can do that quite easily and then you don't have a choice but to write auto as the return value.
What would be fantastic if the documentation could be given access to the the compiler's return type inference, so that it could document the auto returns with a little more info.
Another way useful approach would be to implement protocols like in swift, or traits like in scala/rust/others, signatures in ml, etc. Then you would be able to define the interface of what a function returns.
> auto whatDoesItReturn(int i)() { static if (i == 0) { return int.init; } else { return string.init; } }
I agree with your point, but for the sake of the audience who doesn't know D I think this example is misleading, as one could take the "int i" parameter as a runtime one, while it's actually a compile time one (the equivalent of C++ non-type template parameter). If you instantiate the function with 0 as a compile-time parameter, it is a function that returns int; otherwise it's a function that returns string. It is never a function that can return int or string.
Yes, this is the problem for returns, but it's going to be difficult for the compiler to put something useful. The best it can do is point you at the code that returns, and let you figure it out.
I still think the best option is let the author describe it in ddoc, as the semantic meaning can be much easier to convey that way.
Yes, it supports interfaces and unions. One thing not being stated enough in this thread also is that the docs are not just a regurgitated version of the prototypes -- there's actual hand-written text that tells you what the things return.
In the standard library, it's primarily in the range-based functions that you see this, where the type doesn't generally need to be known. And where you do see it, the documentation will describe what the function returns more accurately than any convoluted made-up type the function signature could show you.
Template declarations in D take 2 parameter lists. The first is the template parameters the second the runtime parameter:
in
auto whatDoesItReturn(int i)() { static if (i == 0) { return int.init; } else { return string.init; } }
we have (int i) as template parameter and () as an empty runtime parameter. In C++ syntax whatDoesItReturn<int i>()
at instanciation the syntax is different:
whatDoesItReturn!0() will instantiate a function returning an int
whatDoesItReturn!42() will instantiate a function returning a string.
I missed this the first time, but there are two sets of parentheses in the example. Apparently the first set are like template parameters, and the second are the actual arguments to the function.
Personally I dislike var/auto in languages because I like having types explicitly written. But in case of languages like Java or Kotlin you can move the cursor over the variable name and you will see the type, also you can right-click and select "replace with explicit type" and it will work. In D, IDEs struggle with templates and can rarely index templated code (no wonder, because most of the code doesn't exist until build time).
Most people will tell you, "oh just use auto, it makes the code more generic". That's sweet, except as soon as I want to pass it to another function, I need to have the concrete type. Like you, I usually just copy-paste the full type from the error message and move on.
In Java, `var` comes in useful at times. For example:
for (Map.Entry<SomeLongType, AnotherLongType> x : someMap) {
final SomeLongType key = x.getKey();
final AnotherLongType value = x.getValue();
...
}
In the above code snippet, `var x` would have been very useful because the actual type just repeats information that can be found in the next two lines. Also, usually, I'll use more speaking names instead of `key` and `value`.
But if the body of the loop just refers to `x.getKey()` and `x.getValue()`, without extracting them into local variables, then it makes sense to put the exact `Map.Entry` type into the loop header.
I prefer to put "var" into the loop header because the combination of two types is hard to read. It's easier to read (for me!) when the type of the key and the type of the value are separated, like they are on the first two lines of the loop body.
for (var x : someMap) {
final SomeLongType key = x.getKey();
final AnotherLongType value = x.getValue();
...
}
Personally I dislike var/auto in languages because I like having types explicitly written. But in case of languages like Java or Kotlin you can move the cursor over the variable name and you will see the type, also you can right-click and select "replace with explicit type" and it will work. In D, IDEs struggle with templates and can rarely index templated code (no wonder, because most of the code doesn't exist until build time).
It's 2020. Why couldn't things work like this, where one can open a window for a concrete type using templates, and it shows the code?
I agree. I will paraphrase this as - a well designed language should be usable, at minimum, from a pure text editor, and should not put unreasonable burden on an IDE.
The cynic in me wants to say that such a language doesn’t lend itself to static analysis. As soon as you can do great things with static analysis you can build those features into an IDE, therefore causing the “pure text editor” to feel crippled giving rise to the idea that this language is “unusable from a pure text editor”.
Interactivity is a base attribute of an ide, and not the first reason people use it; the context here is that using an ide doesn't define a good programmer. So being able to clicky click is what makes good programmers to you?
They said "A" good dev uses an ide, and a snappy 'in 2020' retort. It's plainly clear their intention is to imply only good devs use ide's in the modern era.
No, that's not how simple if statements work. I don't know what else I can say.
If I say "A good CPU has more than two cores in 2020." then I'm just saying it's a requirement, not sufficient all by itself. I'm not calling a twelve-year-old phenom X3 a good CPU. The "in 2020" is just to emphasize that anyone failing this standard is falling behind the times.
Templates don't exist until compile time, until you build the code, the IDE plugin doesn't have the full data on what types exactly are there. Java/C# generics are more limited in functionality, but it's a tradeoff in exchange for better ahead of time knowledge of types.
is not DRY. It also makes it practical to use complicated structures out of generics/templates without killing the developer With<Deeply<Nested,Template>, Declarations>.
Hard disagree. If you are assigning a value that's the result of an expression you might have somewhat complicated logic. Being able to say what you expect returned is very useful.
var accountsToDelete = accountService.GetAccountsToDelete();
deleterService.Delete(accountsToDelete);
So as much type information is already encoded into names so that all references to this object are clear in what we're handling, and so the type declarations at the point where the variable is declared is just redundant noise.
If your variables aren't informative when I'm reading the code, I'll be confused 5 lines later anyways. So make them informative at the start. And given that, doesn't that mean the List<Account> is a bit redundant?
var/auto is a trade-off which requires good tooling support, as you said. Once you're able to see the types as you see fit in your IDE, I feel that you're mostly better off, for all the reasons already mentioned in the thread.
Of course good tooling is still a big requirement, but I still think it's the best decision in the long-term: it's way easier to improve and change tools like IDEs (especially with LSP?), rather than the language itself.
Regarding explicit types in functions, you also don't always need them in languages such as OCaml. I feel that the answer to your criticism could be to just have "auto" also for function arguments, especially when you're just prototyping.
In places where you need to know the exact type auto probably isn't the right choice.
There are many cases where the type is clear however or irrelevant or in generic code is hard to express (thus depending on documentation/comments unless obvious) which would also be hard to read.
I've been using D since 2009 both personally and professionally. 'auto' return did bother me in a few places but it never came close to being a deal breaker.
It's been raised many times before. By me and numerous others. The standard library is generic, which isn't the end of the world, but you can't tell from the documentation how you can work with the output. It's common for someone to ask a question and be told "add .array to the output". They'd never know that after reading the documentation.
I had this issue last year when I started learning D. I eventually got used to it, but it was a stumbling block at first. The #d IRC channel helped me out.
I've never programmed in D so I don't know, but from curiosity I wanted to check if what you write is true. However, I can't find any function that is declared as auto.
Could you please paste some example of a function that has a return value which is declared as auto?
So, what's the type of pieces? The D standard library is written to be generic. And sure enough, that line of code will run. Where it turns into a problem is when you try to do something with it. If pieces is a range, there are certain things you can't do with it. Or maybe you can. Who knows. You'll never learn it from reading the documentation. I've been using D since 2013 and I still struggle with this at times. It's a valid complaint. (D's a great language, but is short on manpower to fix rough edges like this.)
> You'll never learn it from reading the documentation.
Did you not see the Returns section from that link?
"Returns: A std.typecons.Tuple of the three resulting ranges. These ranges are slices of the original range."
Further note: If you just saw `std.typecons.Tuple!(typeof(Range.init[0 .. $]), typeof(Range.init[0 .. $]), typeof(Range.init[0 .. $]))` which is what would have to be written there instead of auto, would that make you feel better? Do you not have to read the documentation to figure out what the function does or what actually goes into those tuples?
The major examples are parts of the stdlib which offer higher-level pipelines (some answers here indicate the need for such things to be very flexible in their return values to allow this, but this is not a priori obvious - Java manages similar functionality with just the Stream<T> class after all).
D's classes and interfaces are much like Java's, so it's possible and easy to write functions that express return types like that. It's also quite restrictive for generic programming. D's metaprogramming features are much more powerful. That means a template can return different types that do not conform to a single, easily-expressed interface. The std.algorithm package is built on that concept.
Maybe it was fixed? I would imagine that saying in documentation that return type is auto is equivalent to not mentioning return type at all, so feels like something not intentional.
Having each of the generic range functions return a <FN>Result type that all implement some variations of a random access range is absurd. Having "auto map(); Returns an input range" is much denser and helpful than "MapResult map(); struct MapResult { @property bool isEmpty(); ...}".
A more explicit "impl(InputRange) map()" may be better until you consider that map is generic on the kind of range you give it, so that just turns into "impl(MapResult!R) map(R)()". A more roundabout, pointless way of saying "auto".
> A type is much denser, than a textual description, and in most >cases sufficient.
In feeble languages with simpleton typesystem may be, but in highly generic templated language like D it is not the case.
The type is not dense at all.
What's funny is that in general people complain that compiler errors in D are unreadable. You know why they are unreadable?
Because they print out the types of the functions in which the error occurs and that is nothing more than word salad for generic functions.
Types with hundreds of characters are very common.
Depends the type, it can be quite long and complicated. I always have a lot of issues trying to read return types from templated functions in C++ because they look really messy.
Sometimes I feel like auto is definitely overused. In a recent fix, I changed something that returned a boolean (no templates involved) from returning auto to returning bool.
But sometimes auto is the best tool for the job, especially when writing wrapping types. In that case, yes, you have to read the documentation (and I mean what is written in the ddoc comments). But in many cases, you don't have to, because you recognize the pattern, or it's simply a wrap of the underlying type's function.
+1, I cringed when Herb Sutter released the 'Almost Always Auto' C++ presentation on YouTube. Sure, auto has its place, and I personally use it, but I just knew that less experience devs would go nuts with it, and it'd only make their lives easier for a short time.
Whether that's really what you want and whether that is the best approach to solve the problem at hand is a matter of preference and the problem space.
It is also allows for a "gradual typing" approach that Dart 1 had.
I think it’s really interesting that as dynamically-typed languages increasingly encourage explicit type hints, statically-typed languages are recommending “almost always auto”.
In dynamically typed languages, adding types can stop things blowing up at runtime. In compiled languages, all the type-inference is still done at compile time, so if it compiles than you're not going to get a crash from accidentally adding a string to an integer at runtime.
I don't know D, but after reading more comments looks like auto is also compensating for poor type system. For example functions that accept arguments of many types apparently need to be declared that are returning auto.
Scala has a strong type system and when working with it on a daily basis I was not programming, I was thinking about types and fighting with the compiler. And that is the language which has a pretty decent IDE plugin. When I moved to D, it felt like a breathe of fresh air to me. Therefore, the whole talk above about auto and types looks like subjective nitpicking.
In practice though, D codes fast and runs fast, as promised on the official site.
I vehemently disagree. Auto/var is a tool that may be used judiciously by your userbase. This new philosophy of blocking your users from using dangerous tools because you know better than them just invites workarounds and kludges. Give the user the tools and warn them of the ways it can go wrong. There's a reason Rust is losing the war to C++.
Anyways, var/auto is critical in some cases. C#'s LINQ, for example, would be very difficult to develop with if you had to manually figure out the type you were returning with long queries every time you wanted to restructure your query.
I disagree; you don't seem to be addressing what the parent is talking about (documentation). Whatever reason Rust is losing a war against C++ (it's really not), this isn't it.
More a failure of documentation/tools? We've been content for a decade to just name the arguments and return without any context. Like the old joke "Where am I?" answered by "In a hot air balloon!". Correct but useless.
I wonder if the document could describe (in some regular way) how those auto types are constructed...from what input, with what operations?
Im annoyed that you have to spell out auto all over the place. It should be implicit. The compiler should automatically add auto if you have not specified something else.
Another reason for C/C++ is that they are very permissive with implicit lossy conversions. If you specified explicit type, chances are the compiler helpfully made a lossy conversion for you.
Sutter made that point under Maintainability and robustness.
Like Sutter's answer, this point doesn't answer the complaint. People on the anti-auto side say it seriously harms readability, as locals' types are no longer clear at a glance. They aren't asking for a list of reasons why some people favour auto, they're asking for an answer to their readability problem.
Perhaps IDEs could infer types and display them as a superscript. That would keep just about everyone happy. (Perhaps not Vim users.)
Herb didn't say it well enough. It's a tradeoff between correctness and readability. If it wasn't for C++'s weak type system you wouldn't be required to make this tradeoff and could improve readability while still staying correct.
Perfect, just what I was thinking. Does something like that exist for C++? Visual Studio 2019 doesn't seem to have it, but it's able to show a local's type when I hover over the local's identifier.
If you use only explicit constructors and ban cast operators except for the most basic of types, then you have a sane language and explicitness in your code.
Type inference systems generally have crumby error messages, and are harder to reason about all around. The best type inference systems are those that allow inference within a function definition, but the function signature requires explicit annotation. This keeps the inference local, which is easier for humans to read about. Good tooling can make either system easier to work with, but tooling is much less important in the type annotation case because the cost of adding annotations is trivial (contrary to your "torment" vocabulary) compared to reasoning about (non-local) type inference.
In general, this reduces to the principle that concrete is easier to reason about than abstract. The type signature of an unannotated function in a type inference system is maximally abstract, while (especially in practice) the signatures for functions that are manually annotated are more concrete if not fully concrete. There are still problems in non-type-inferred systems with programmers who try to be egregiously abstract, but these are fewer and farther between.
And that's a huge shame. Because in general I really liked using D.