top | item 23005436

(no title)

F-0X | 5 years ago

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.

discuss

order

gmueckl|5 years ago

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.

fluffything|5 years ago

I'm glad that Rust has no `auto`. I find this:

    fn map<U, T, F, I>(it: I) -> impl Iterator<Item=U> 
        where I: Iterator<Item=T>, U: From<T>
    {
        it.map(|t| From::from(t))
    }
infinitely more readable than

    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.

scott_s|5 years ago

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.

logicchains|5 years ago

>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:

    template<class R>
    
      concept __SimpleView =                         //     exposition only
        ranges::view<R> && ranges::range<const R> &&
        std::same_as<std::ranges::iterator_t<R>, std::ranges::iterator_t<const R>> &&
        std::same_as<std::ranges::sentinel_t<R>, std::ranges::sentinel_t<const R>>;
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.

rowanG077|5 years ago

So you still can see that it depends on some input. That is hugely more useful then auto.

asdkjh345fd|5 years ago

>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.

WalterBright|5 years ago

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.

slezyr|5 years ago

Print-debug you program before it's even compiled and even have a bug is a bag idea.

p0nce|5 years ago

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).

Konohamaru|5 years ago

> 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!

lumberjackstian|5 years ago

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.

lultimouomo|5 years ago

> 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.

schveiguy|5 years ago

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.

GordonS|5 years ago

I haven't looked at D yet, but... yuck! `var` for return types in a method signature is seriously unhelpful!

If the docs are filled with this, then D is certainly coming off my list of langs to look at.

For functions that can return different types, I think interfaces or union types would be more helpful (not sure if D supports either though).

RandallBrown|5 years ago

What would be the return type of that? Does D have an Either type?

skocznymroczny|5 years ago

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.

hibbelig|5 years ago

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.

stcredzero|5 years ago

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?

Pxtl|5 years ago

Hard disagree.

   MyClass myVar = new MyClass()
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>.

hiq|5 years ago

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.

acehreli|5 years ago

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.

WalterBright|5 years ago

It's helpful to bring up issues you are encountering in the D forums. This is the first I've heard of this particular one.

bachmeier|5 years ago

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.

anaphor|5 years ago

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.

self_awareness|5 years ago

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?

bachmeier|5 years ago

Here's one example:

https://dlang.org/phobos/std_algorithm_sorting.html#partitio...

The important line is

auto pieces = partition3(a, 4);

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.)

F-0X|5 years ago

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).

In this (and the sibling pages in algorithm) nearly every entry is listed as either "template" or "auto" relhttps://dlang.org/phobos/std_algorithm_iteration.html

takeda|5 years ago

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.

schveiguy|5 years ago

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.

dirtydroog|5 years ago

+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.

Koshkin|5 years ago

Plus, seeing the word 'auto' all over the place leaves a weird impression.

pansa2|5 years ago

I think it’s really interesting that as dynamically-typed languages increasingly encourage explicit type hints, statically-typed languages are recommending “almost always auto”.

logicchains|5 years ago

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.

arunc|5 years ago

Vim vs emacs, auto inference vs explicit, I don't think it will ever end. :)

takeda|5 years ago

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.

At least that's what I understood.

jbverschoor|5 years ago

Aren't there any type synthesizers which modify your code based on inferenced types?

lostmsu|5 years ago

It already ended. VSCode and auto inference.

jimbob45|5 years ago

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.

ynx|5 years ago

But they were talking about _docs_.

TwoBit|5 years ago

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.

JoeAltmaier|5 years ago

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?

z3t4|5 years ago

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.

underthensun|5 years ago

I really hate the auto keyword but as I like D so much, I kinda get used to it.

stevefan1999|5 years ago

Well, not everyone likes automatic type deduction -- some people just like to torment him/herself by repeating information that is unneeded. Why?

throwaway894345|5 years ago

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.

Koshkin|5 years ago

Sometimes when reading a long passage in a novel you need a reminder who "he" is.