In Dart (as in a couple of other languages) we have an `await` keyword for asynchrony. It has the same problem you describe and it really is very painful. Code like this is not that uncommon:
await (await (await foo).bar).baz);
It sucks. I really wish the language team had gone with something postfix. For error-handling, I wouldn't be surprised if chaining like this was even more common. const temp = await foo;
const bar = await temp.bar;
const baz = await bar.baz;
But then again it looks like in this example we are returning promises from getters on objects which is something I would avoid.> we are returning promises from getters on objects which is something I would avoid.
Getters are very common in Dart and it's idiomatic to use them even for properties that are asynchronous. (It's not like making it a method with an extra `()` really solves anything.) It's not as common to have nested chains of asynchronous properties, but they do happen, especially in tests.
foo..bar..baz;Never mind something that nests these 2 levels deep.
In practice, writing `?` is no more or less automatic than `if foo, err := someFunc(bar); err != nil { return nil, err }`, but the difference is that it's immediately obvious when special error-handling logic has been added, by the simple fact of its existence.
x.context(...)?
x.map_err(|e| ...)?
...etc...
so they have to provide contextual information.Additionally, Rust programs heavily use "RAII", avoiding the need for `handle`.
But there is talk of adding support for `catch`.
See https://internals.rust-lang.org/t/pre-rfc-catching-functions...
Verbosity can be a pain. But needlessly dense code is (imho) less readable. Having a single character that changes the entire context of a statement is not fun (I have the same problem with !). Reading a set of a dozen chained functions, some with ? and some without... that's not fun for anyone.
When you need to print / log a value from the middle of your call chain you have to take the chain apart. So just write it out to start with. It's not any less efficient.
In IntelliJ you can breakpoint on an expression that uses chaining, hold down the alt key and then click on the expression you want to evaluate. It turns into a hyperlink and can be viewed easily.
Debuggers are great but they're not always practical, for instance for debugging remote targets with limited connectivity. Actually in some situations you may end up having to buy expensive licenses for closed source software to run a debugger on certain hardware (although that might not be a concern for Go).
I'm also of the opinion that firing the debugger as soon as something unexpected happens instead of taking 10 seconds to reason about your code might lead you to miss a more general problem with your architecture as you focus solely on the symptoms, but that's of course a lot more subjective and your experience may vary. Personally I generally tend to use debuggers as a last recourse when I really can't make sense of what's going on through "static" analysis.
Saying that debuggers are for noobs and that they shouldn't be used is idiotic but arguing that not using them is doing it wrong and being stuck at the age of 80x25 terminals isn't much better. Wisdom is to use the right tool for the right job.
But more abstractly speaking: are chained calls, from the POV of a human that didn't write it, or wrote it two months ago, more readable than a number of statements on multiple lines?
fn print(self) -> Self { println!("{:?}", self); self }
You can do it nicely, so all you need is to add one `derive` to a type declaration, and then you will be able to insert .print() into the chain.
It is like print in lisps, might be really handy for the debug printing.
Naming intermediates can sometimes make things clearer, but also it can make things less clear. Often there is no good name, or the good name is just as long as the code that made it.
As to derive for each type, I already use `#[derive(Debug)]` for most of my types, to be able to print them with {:?} in format/println!. Though you are right in the sense, that it would be great to make such a method a part of Debug trait. But I personally have never needed it, so maybe complications of Debug trait would be the over-engineering you speak about.
From other hand I do not like variables. Variables are complications of code, and complications like this make it impossible to grasp code on the first sight. There are a lot of cases when you need a variable, and it is not apparent on the first sight which case is it. You need to stop and think. I do not like to think just to find that there was no reason to think. Intelligence is a constrained resource, you need to use it wisely.
There's also the small matter of multiple returns breaking the chain.
Could you do this?
check (check func1()).func2()
Looks ugly but maybe they can clean it up further. In fact maybe they could just introduce something that looks like ? but works like check.
That is, if F is string to string,error, and G is empty to string,error then
s := check F(check G())
either assigns a string to s, or calls the handler.check (check A(check B(check C( ... ))))
Furthermore the following statement about rust error handling is simply not true:
> But Rust has no equivalent of handle: the convenience of the ? operator comes with the likely omission of proper handling.
That's because the ? operator does a min more than the code snippet in the draft design shows:
Instead it does the following: This means that a very common way to handle errors in Rust is to define your own Error type consumes other errors and adds context to them.