News for hackers. Stuff that matters.
Re:Go 2 Draft Designs by JelteF on Tuesday August 28, 2018 @01:11PM (#17860618)
Overall really great that Go is addressing it's current biggest issues. I do think the argument for the check keyword instead of a ? operator like in Rust is quite weak. Mainly because a big advantage of the ? operator (apart from the brevity) is that it can be used in method calling chains. With the check keyword this is not possible. AFAICT the check keyword could be replaced for the ? operator in the current proposal to get this advantage, even while keeping the handle keyword.

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:

    if result.err != nil {
	    return result.err
    }
    use(result.value)

Instead it does the following:

    if result.err != nil {
	    return ResultErrorType.from(result.err)
    }
    use(result.value)
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.
Re:Go 2 Draft Designs by munificent on Tuesday August 28, 2018 @03:20PM (#17861994)
> I do think the argument for the check keyword instead of a ? operator like in Rust is quite weak. Mainly because a big advantage of the ? operator (apart from the brevity) is that it can be used in method calling chains.

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.
Re:Go 2 Draft Designs by reggieband on Tuesday August 28, 2018 @06:38PM (#17863566)
Maybe I'm crazy but I'd much prefer that as:

   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.
Re:Go 2 Draft Designs by munificent on Tuesday August 28, 2018 @07:14PM (#17863756)
Yeah, the guidance is usually to hoist the subexpressions out to variables like you do here. But I encounter it often enough that it feels like we are trying to paper over a poor language syntax.

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

Re:Go 2 Draft Designs by skybrian on Tuesday August 28, 2018 @04:45PM (#17862715)
Would it be better to use statements more? After all, this is a sequence of steps and it's doing a context switch between each step. That seems better written vertically, one per line.
Re:Go 2 Draft Designs by btilly on Tuesday August 28, 2018 @07:55PM (#17863985)
Deeply chained method calls are a code smell on their own. But would it really be that much better if you wrote it as something like:

    foo..bar..baz;
Re:Go 2 Draft Designs by dom96 on Tuesday August 28, 2018 @05:47PM (#17863206)
I'm not sure about Dart, but in my experience it's rare that you await a function which returns an object with another awaitable in it.

Never mind something that nests these 2 levels deep.

Re:Go 2 Draft Designs by RodericDay on Tuesday August 28, 2018 @07:19PM (#17863797)
fetch() + response.text() is usually two. add a user-defined third and you're there.
Re:Go 2 Draft Designs by rsc on Tuesday August 28, 2018 @01:37PM (#17860897)
You're right, I oversimplified that. But having a "ResultErrorType" is not really context, not by itself. The interesting context would be additional fields recorded in that type.
Re:Go 2 Draft Designs by bcantrill on Tuesday August 28, 2018 @01:57PM (#17861107)
That being the case, you should change the doc, because it's an important distinction. Speaking personally, I have found in my own Rust that the presence of the propagation operator does not, in fact, come with the "likely omission of proper handling"; to the contrary, it has allowed me to (properly) propagate errors that I can't meaningfully add context to, and to (properly) handle those errors that I can handle -- all with very readable code that doesn't involve error-prone boilerplate.
Re:Go 2 Draft Designs by stouset on Tuesday August 28, 2018 @02:57PM (#17861776)
Yeah, the underhanded comment "the convenience of the ? operator comes with the likely omission of proper handling" is not only unnecessary but completely wrong in my experience. It strikes me as if the author hasn't actually ever used or investigated the language in any kind of depth, and took a wild guess that "making propagating errors easy means people don't actually deal with errors" or something.

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.

Re:Go 2 Draft Designs by lclarkmichalek on Tuesday August 28, 2018 @02:22PM (#17861388)
This is something I don't enjoy about Rust's error handling. The context is added on a package level (or at least, the level at which the Error type is defined, which seems to be usually package level), and often quite far away from where the error was emitted. For all its ills, `errors.Wrap(err, "...")` puts the adding of context incredibly close to the place where the error is relevant.
Re:Go 2 Draft Designs by edflsafoiewq on Tuesday August 28, 2018 @04:17PM (#17862476)
x? will convert the error type for x, E1, to the error type for the calling function, E2, only when a conversion from E1 to E2 is defined. If its not possible to do this conversion without some contextual information, then this bare conversion can't be defined and x? will not compile. In that case, callers will have to do something like

    x.context(...)?
    x.map_err(|e| ...)?
    ...etc...
so they have to provide contextual information.
Re:Go 2 Draft Designs by IshKebab on Tuesday August 28, 2018 @04:56PM (#17862799)
You can use map_err or the failure crate's with_context to add context.
Re:Go 2 Draft Designs by cpuguy83 on Tuesday August 28, 2018 @04:05PM (#17862379)
What do you think are the ills of errors.Wrap?
Re:Go 2 Draft Designs by epage on Tuesday August 28, 2018 @01:27PM (#17860777)
> But Rust has no equivalent of handle: the convenience of the ? operator comes with the likely omission of proper handling.

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

Re:Go 2 Draft Designs by marcus_holmes on Wednesday August 29, 2018 @05:31AM (#17866927)
Go doesn't chain things much. I like that it doesn't.

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.

Re:Go 2 Draft Designs by kyrra on Tuesday August 28, 2018 @01:31PM (#17860815)
I get the feeling that the core Go team is rather against 1-character operators. Also, the Go community doesn't tend to do a lot of call chaining from a general stylistic standpoint.
Re:Go 2 Draft Designs by zlynx on Tuesday August 28, 2018 @01:49PM (#17861022)
I've written a lot of Go in the last few years. I stopped chaining calls after I realized how much of a pain it is to debug.

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.

Re:Go 2 Draft Designs by mike_hearn on Tuesday August 28, 2018 @02:24PM (#17861403)
I'd note that's only an issue if you don't have a good IDE.

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.

Re:Go 2 Draft Designs by kcmp on Tuesday August 28, 2018 @02:52PM (#17861716)
I think needing a heavy IDE to comfortably work in a language/paradigm is a negative
Re:Go 2 Draft Designs by pjmlp on Tuesday August 28, 2018 @05:41PM (#17863150)
Given what Lisp, Smalltalk, Mesa/Cedar, Oberon, Delphi, VB introduced as productivity tooling, I see as negative programming as if my computer was still stuck in 80x25 green phosphor terminal.
Re:Go 2 Draft Designs by simias on Wednesday August 29, 2018 @03:53AM (#17866503)
That's a silly caricature. I could retort "I see as a negative if my development environment takes 40GB of RAM and half an hour to boot up" and it would be on the same level.

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.

Re:Go 2 Draft Designs by marcus_holmes on Wednesday August 29, 2018 @05:34AM (#17866941)
also, if you write a ton of log statements because that's how you debug, then when your production system blows up and you can't fire up the debugger, you have something to work on.
Re:Go 2 Draft Designs by pjmlp on Wednesday August 29, 2018 @06:51AM (#17867210)
Or use modern instrumentation data that debuggers can work with like IntelliTrace, JMX, Flight Recorder, JTAG,....
Re:Go 2 Draft Designs by simias on Wednesday August 29, 2018 @07:02AM (#17867253)
I don't understand why it has to be an exclusive OR. Both printf-style logging and debuggers have their use. As I mentioned in my previous comment deploying some tracing solutions can be a complicated and expensive process, writing to an UART is however very cheap. Breaking or slowing down the execution of tasks interacting with other threads and resources can be impractical or simply hide the bug altogether if it's race-condition related, sometimes toggling a GPIO and putting an oscilloscope on the PIN beats any trace framework in terms of practicality. It will also work regardless of programing language, developing environment software and hardware used.

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.

Re:Go 2 Draft Designs by scroot on Tuesday August 28, 2018 @09:25PM (#17864515)
Amen
Re:Go 2 Draft Designs by mike_hearn on Tuesday August 28, 2018 @02:57PM (#17861779)
You don't need an IDE but it solves problems like "I don't want to chain my methods because my weak debugger can't handle it well".
Re:Go 2 Draft Designs by sagichmal on Tuesday August 28, 2018 @05:51PM (#17863242)
Chained method calls being difficult to debug does not necessarily mean using a debugger. It can mean difficult to insert clarifying code in the middle of the chain: print statements, or additional value checks, and so on.
Re:Go 2 Draft Designs by bmurphy1976 on Tuesday August 28, 2018 @07:18PM (#17863792)
Chained methods also don't diff as clearly (the one liners anyway).
Re:Go 2 Draft Designs by pjmlp on Wednesday August 29, 2018 @01:06AM (#17865806)
They do on GUI diff tools by using different colors.
Re:Go 2 Draft Designs by Cthulhu_ on Wednesday August 29, 2018 @03:33AM (#17866404)
It's still not as clear as just splitting across lines, plus in most tools you need to explicitly indicate you want to highlight word diffs.

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?

Re:Go 2 Draft Designs by marcus_holmes on Wednesday August 29, 2018 @05:33AM (#17866933)
Generally speaking, Gophers don't interactively debug. There is some support for it, but it's historically been difficult and we have tended to adjust to logging everything and working out the problem from the logs.
Re:Go 2 Draft Designs by lucio on Thursday August 30, 2018 @05:09PM (#17880803)
That's OK but when using "logs" it takes more time and tries to find a complex bug than to search for it using an interactive debug tool.
Re:Go 2 Draft Designs by ordu on Tuesday August 28, 2018 @09:24PM (#17864508)
I don't know about Go, but if I faced such an issue with rust, I would make a trait with one method:

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.

Re:Go 2 Draft Designs by gnuvince on Tuesday August 28, 2018 @10:19PM (#17864903)
I'm not sure why you would prefer to (1) create a new trait, (2) create a custom derive for that trait, (3) derive that trait for all the types you want to debug over assigning to a variable and printing. It sounds like an over-engineered solution to a non-problem to me.
Re:Go 2 Draft Designs by Dylan16807 on Tuesday August 28, 2018 @11:47PM (#17865436)
Because that's a tiny amount of work that saves you from making bloated junk variables all over.
Re:Go 2 Draft Designs by zlynx on Wednesday August 29, 2018 @01:44AM (#17865958)
The compiler removes them, and the naming of intermediates often makes the code more understandable.
Re:Go 2 Draft Designs by Dylan16807 on Wednesday August 29, 2018 @11:59AM (#17869225)
By bloat I mean in the source code.

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.

Re:Go 2 Draft Designs by ordu on Wednesday August 29, 2018 @08:15AM (#17867612)
(1), (2) and (3) needs to be one once per lifetime. Or even less, if it was published on cargo.io. It is not an issue. And it doesn't seem as over-engineering for me, it would be like 10 lines of library code, with clear purpose. If it was 50 lines of code, with several methods for different cases, and with a lot of corner-cases, then it might be called over-engineering. But 10 lines of straight self-describing code with two line comment about suggested uses is not.

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.

Re:Go 2 Draft Designs by acjohnson55 on Tuesday August 28, 2018 @11:23PM (#17865300)
In Scala, I use an implicit class to add a `tee` function. I kind of wish it were in the standard library so I wouldn't have to import it.
Re:Go 2 Draft Designs by jacques_chester on Tuesday August 28, 2018 @09:37PM (#17864606)
> Also, the Go community doesn't tend to do a lot of call chaining from a general stylistic standpoint.

There's also the small matter of multiple returns breaking the chain.

Re:Go 2 Draft Designs by orblivion on Tuesday August 28, 2018 @01:48PM (#17861017)
> chaining

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.

Re:Go 2 Draft Designs by JulianMorrison on Wednesday August 29, 2018 @10:19AM (#17868373)
It looks like check can be used in calling chains.

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.
Re:Go 2 Draft Designs by barsonme on Thursday August 30, 2018 @12:33AM (#17874102)
I hope this doesn't make it in. I'd hate to see the inevitable

check (check A(check B(check C( ... ))))

Re:Go 2 Draft Designs by JulianMorrison on Thursday August 30, 2018 @04:57AM (#17875134)
I wouldn't. It's very explicitly saying, this is a series of calls that can fail.