News for hackers. Stuff that matters.
Re:My Go Resolutions for 2017 by munificent on Wednesday January 18, 2017 @02:33PM (#13429551)

    > Part of the intended contract for error reporting in Go
    > is that functions include relevant available context,
    > including the operation being attempted (such as the
    > function name and its arguments).
I know the Go folks don't like exceptions, but this is an example of them learning the hard way that about one useful thing they lost by deciding to not do exceptions.

Exceptions give you stack traces automatically. All of that context (and more) is there without library authors having to manually weave it in at every level of calls.

    > Today, there are newer attempts to learn from as well,
    > including Dart, Midori, Rust, and Swift.
For what it's worth, we are making significant changes to Dart's generics story and type system in general [1]. We added generic methods, which probably should have been there the entire time.

Generics are still always covariant, which has some plusses but also some real minuses. It's not clear if the current behavior is sufficient.

Our ahead-of-time compilation story for generics is still not fully proven either. We don't do any specialization, so we may be sacrificing more performance than we'd like, though we don't have a lot of benchmark numbers yet to measure it. This also interacts with a lot of other language features in deep ways, like nullability, whether primitive types are objects, how lists are implemented, etc.

[1]: https://github.com/dart-lang/dev_compiler/blob/master/STRONG...

Re:My Go Resolutions for 2017 by stickfigure on Wednesday January 18, 2017 @05:03PM (#13430754)
Having just spent the last two months writing Go code, exceptions are the thing I miss most (well, besides the ternary operator and map/reduce operations). Not only are errors painful to debug without stacktraces, but every single method call is followed by three lines of "if err != null {". I am amazed that folks tolerate the sheer amount of repetitive typing required by the language.
Re:My Go Resolutions for 2017 by divan on Thursday January 19, 2017 @05:19AM (#13434596)
Changing the language is like changing the country residence. Some things take long time to get used to, unless you're highly mobile traveller :)

I couldn't imagine myself returning to languages with exceptions and ternary operators, for example.

Re:My Go Resolutions for 2017 by junke on Thursday January 19, 2017 @07:43AM (#13435283)
> I couldn't imagine ...

Reminds me of Rob Pike's post: https://commandcenter.blogspot.fr/2011/12/esmereldas-imagina...

Re:My Go Resolutions for 2017 by lmm on Thursday January 19, 2017 @09:34AM (#13436036)
Wow. What an unpleasant way to respond to criticism. Being a programmer does not require the ability to hobble your thinking. I can easily imagine programming in a language without generics - but I'd still be programming with generics, I'd just be expanding them in my head (or, if I was writing enough code, in my own implementation of generics) rather than using a built-in implementation.
Re:My Go Resolutions for 2017 by stymaar on Thursday January 19, 2017 @07:07AM (#13435116)
> I couldn't imagine myself returning to languages with exceptions and ternary operators, for example.

Why ? Is it because no language with those feature is good (let say you wouldn't like to return to Java for instance), or is it because you genuinely prefer this over ternary operator :

    if expr {
        n = trueVal
    } else {
        n = falseVal
    }
Re:My Go Resolutions for 2017 by divan on Thursday January 19, 2017 @07:24AM (#13435202)
Well, from my 15 years experience for each nice example of ternary operator being useful there are 10 more examples where it's being used improperly and makes code hardly readable.

I know that the typical answer is "it's just bad programmer", but that's where Go chooses different view. Many design decisions in Go were taken with awareness of the social context of programming.

If the feature incentivizes using it in a wrong way - it's not a bad programmer, it's a bad feature. Programming language is a language between humans in a first place and it should be readable as much as possible, it should deincentivize using obscure and easy-to-use-improperly things.

It so much better when you go to practically any Go repository and find that code is readable and clear. I've never had this experience with any other language before.

PS. Found one of the real examples of abusing ternary operators. That was one of the coolest I've seen. And I guess it's easy to understand that the same programmer wouldn't write the same with "if .. else .." blocks in Go. He would realize that it's too verbose and hard, hence probably wrong design, so he need to find better solution within the space of language features. http://imgur.com/a/hjKOe

Re:My Go Resolutions for 2017 by pif on Thursday January 19, 2017 @10:03AM (#13436285)
> If the feature incentivizes using it in a wrong way - it's not a bad programmer, it's a bad feature.

So there's nothing good about alcohol (it may cause liver damage), cars (they may cause accidents), prescribed drugs (they may have unpleasant collateral effects), ...

Hey, it's also true that programming incentivizes bugs, so programming is bad. Let's stop programming!

Re:My Go Resolutions for 2017 by nulltype on Friday January 20, 2017 @10:11PM (#13448551)
It's weird how people miss the conditional operator but not switch case fallthrough.
Re:My Go Resolutions for 2017 by junke on Thursday January 19, 2017 @08:59AM (#13435764)
> It so much better when you go to practically any Go repository and find that code is readable and clear. I've never had this experience with any other language before.

What languages did you have to endure before?

> Found one of the real examples of abusing ternary operators.

Yeah, the original code is silly, the binary search trees are not always balanced. They should be more like:

    if (type <= 10) ? (if (type <= 5) ? ... : ...) : if (type <= 7) ? ... : ...
Re:My Go Resolutions for 2017 by koblas on Thursday January 19, 2017 @08:44AM (#13435670)
It's worse that that, since you need to declare the variable as well. It's the "var n int" piece that bugs me, since sometimes you're moving code around and have a n := trueVal hidden in there.

    var    n int
    if expr {
        n = trueVal
    } else {
        n = falseVal
    }
Re:My Go Resolutions for 2017 by junke on Thursday January 19, 2017 @09:09AM (#13435827)
Go has a ternary if, but the syntax is weird ;-)

    n := map[bool]func() int{
            false: func() int { return falseExpr },
            true:  func() int { return trueExpr },
    }[expr]()

https://play.golang.org/p/TXIse32WD9

Now, let's see how to abstract it with "go generate"...

Re:My Go Resolutions for 2017 by lmm on Thursday January 19, 2017 @09:35AM (#13436050)
I genuinely prefer

    n = if expr trueVal else falseVal
(which is what e.g. Scala does) over meaningless-unless-memorized '?' and ':'
Re:My Go Resolutions for 2017 by lmb on Thursday January 19, 2017 @08:53AM (#13435721)
In most cases this will boil down to:

    n := defaultVal
    if expr {
      n := otherVal
    }
Which I think is easier to understand, and not that much more typing. YMMV of course.
Re:My Go Resolutions for 2017 by gkya on Thursday January 19, 2017 @10:38AM (#13436570)
If defaultVal is a constant, this is OK, but what if it requires computation?
Re:My Go Resolutions for 2017 by geoka9 on Thursday January 19, 2017 @12:49PM (#13437714)
I think typing is the easiest part of programming.

Reading, maintaining and improving legacy codebases are some of the hardest.

If that extra typing makes it much easier to do the above tasks, I don't mind doing this.

Go runtime panics include stack traces, complete with the offending statement's line number in the source code.

Re:My Go Resolutions for 2017 by morecoffee on Wednesday January 18, 2017 @10:01PM (#13432613)
> I know the Go folks don't like exceptions, but this is an example of them learning the hard way that about one useful thing they lost by deciding to not do exceptions.

The first thing I do in a Go project is reimplement exceptions it seems. It isn't even so much the stack trace, but that the _cause_ can be chained on. Often times one error causes another and the error interface in Go is too weak to capture it.

Rob Pike has his blog post about errors being values, but it's basically useless because the standard library hardly uses the more advanced error types. Pretty much every library returns error instead of MoreAdvancedError, which means you are doomed to speaking the lower common denominator.

(for the record, promising in the documentation an error will always be some type is pretty weak).

Re:My Go Resolutions for 2017 by saturn_vk on Wednesday January 18, 2017 @06:22PM (#13431463)
There was a proposal at some point to integrate github.com/pkg/errors into the stdlib, since it's pretty much a drop-in replacement to the current errors package, with extras. One of them is that errors contain stack traces that can be printed out if needed. Pretty useful, and still not an exception.
Re:My Go Resolutions for 2017 by heavenlyhash on Thursday January 19, 2017 @03:38AM (#13434132)
For errors with stacks in golang today, you could try the Meep [1] library.

It's a library for More Expressive Error Patterns. You can declare error types like this:

  type ErrFrobnozMalformed struct {
       Frob *Frobnoz
       meep.TraitTraceable
  }
... and any type you compose with a meep trait like that gets superpowers, like automatically attached stacks.

I'm the author. I don't think it's perfect -- in particular you really can't avoid a certain amount of boilerplate :( -- but with meep you get stacks, and you get custom error types, and that's worth a lot to me.

Whether or not you use this code, the idea that might be a useful takeaway is the fact that the stack-capturing behaviors (and others) are a trait that you can "mix in". Whether a stack is appropriate depends on situation. Some errors are fairly regular (e.g. certain kinds of IO halt) and putting a stack on them is not useful (and is CPU-costly). This doesn't necessarily follow any sort of direct inheritance tree. (Java has started doing something similar with adding Even More parameters to exception constructor for e.g. 'capturestack=false'.) I think this is an important point: errors often should have stacks, but not always.

I'm still hugely looking forward to seeing what the Go authors do in the future to make errors smarter. Doing the right thing should be easy, and it's almost impossible to strap something this essential on with a library: special syntax and compiler support for informative errors is warranted.

---

[1]: https://godoc.org/github.com/polydawn/meep

Re:My Go Resolutions for 2017 by spion on Wednesday January 18, 2017 @09:55PM (#13432584)
I think Swift got almost everything right, except for not having a GC maybe. Especially everything related to exceptions: the combination of "throws", "defer" and "try"/"try!" gets rid of most of the classic problems with exceptions.
Re:My Go Resolutions for 2017 by whateveracct on Wednesday January 18, 2017 @04:30PM (#13430464)
> Generics are still always covariant, which has some plusses but also some real minuses.

How is this even possible? Does the compiler just fail if you try to put a type parameter in contravariant position? Or does it allow it and then blow up at run time?

Re:My Go Resolutions for 2017 by inconclusive on Wednesday January 18, 2017 @08:37PM (#13432220)
Tangent: Is it a good thing for programming that I have to wrap my mind around this new term called contravariant?
Re:My Go Resolutions for 2017 by Groxx on Wednesday January 18, 2017 @10:49PM (#13432837)
I'd say "yes". But generally you don't have to (except in edge cases), library authors do. You benefit from stronger type guarantees.

It's a bit easier to think about it as ">=T" vs "<=T" though.

In one direction, you allow T and any subclass. This is probably the most common - all T subclasses should act like T, e.g. have method x() on them. You can assign a T subclass to a T variable or return it from a T-returning method. When you add something to a List<T>, it could be a T subclass, because it's "at least a T". (covariant)

In the other direction, you allow T and any superclass. My main way to think of this is for a callback, e.g. for declaring a `map` operation. If you're mapping over List<T>, you can't declare your callback as accepting a subclass of T because the list only contains "at least T". But you can accept a superclass (e.g. Object), because any T (or subclass) has that superclass. (contravariant)

---

If you don't have support for contravariance, you either give up flexibility (you can't make a reusable Object mapper) or safety (you can't guarantee the supplied callback is safe to call).

Re:My Go Resolutions for 2017 by inconclusive on Thursday January 19, 2017 @06:41AM (#13434990)
Why do I have to use OO? Why can't I just call the functions I need?
Re:My Go Resolutions for 2017 by Groxx on Thursday January 19, 2017 @01:37PM (#13438221)
If there's no type hierarchy whatsoever, then you don't get the benefits of polymorphism. Which is the case in some languages - for those, you typically get looser call semantics (no checks, or duck typing or something) or forcing you to use e.g. match statements everywhere to do the same thing in N branches when you have N types in a list.

With functions and a type hierarchy of some kind (or implicit conversions, or whatever) you have the same kind of issues. When you declare the type of your map function (explicitly or implicitly) you're still bounded by the type you're mapping over. If you make a "(float x, returns float) x + 1e10" function, you can't use it to map over a list of doubles, because they can't be safely reduced in precision. The reverse works though, because floats can be promoted to doubles safely. You essentially have "double" as a subclass of "float". Whether it's OO or not has nothing to do with type hierarchies, OO just embraces them with reckless abandon.

Good type inference systems can hide a lot of this from you, allowing you to drop types most of the time and let the compiler specialize it / make sure it's safe to do this particular thing. But they can fail. When they do, how do you ensure safety?

Re:My Go Resolutions for 2017 by inconclusive on Thursday January 19, 2017 @07:40PM (#13440828)
Can I just use macros? Passing the type as a parameter to a macro can let the compiler match types, no?
Re:My Go Resolutions for 2017 by lmm on Thursday January 19, 2017 @09:39AM (#13436086)
Learning the term before the concept is putting the cart before the horse. But once you find yourself dealing with the fact that e.g. one can use a Source[Float] as a Source[Number] and not vice versa, whereas one can use a Sink[Number] as a Sink[Float] but not vice versa; one can use a Function[Number, Float] as a Function[Float, Float] or a Function[Number, Number] and either of those as a Function[Float, Number], but not in reverse, then it's useful to have words for these relationships so that we can talk about them.
Re:My Go Resolutions for 2017 by whateveracct on Thursday January 19, 2017 @02:40PM (#13438819)
I'd say "yes" only because this concept will exist whether or not you have a name for it. And it's a concept you will bump into in any language with any sort of generics (even Go [1])

I wrote Scala for the last 2 years and was the resident type system "expert", so I had to explain variance to people pretty often. Luckily, it's a pretty quick, mathy definition:

Some notation first:

    A <: B means "A is a subtype of B"
    A >: B means "A is a supertype of B"
    F[_] refers to a unary type constructor.
Now for the definitions:

    If F[_] is "covariant", it means that if A <: B, then F[A] <: F[B]
    If F[_] is "contravariant", it means that if A >: B, then F[B] <: F[A]
Examples of covariant type constructors are List and Functions in their output. An example of a contravariant type constructor is Functions in their input type.

^ This is all that needs to be said! I can write it on a whiteboard in ~5 minutes. I'd say that's a reasonable thing to be expected to learn.

[1] https://www.reddit.com/r/golang/comments/3gtg3i/passing_slic...

Re:My Go Resolutions for 2017 by bshanks on Sunday January 22, 2017 @06:13AM (#13454696)
i think the way you defined it, covariant and contravariant are identical; in your contravariant definition, swap the names of "A" and "B" and you get:

  if B >: A, then F[A] <: F[B]
and B >: A means the same as A <: B, so this means:

  if A <: B, then F[A] <: F[B]
which is the same as your covariant definition.

Perhaps you meant:

    If F[_] is "contravariant", it means that if A <: B, then F[B] <: F[A]

?
Re:My Go Resolutions for 2017 by munificent on Thursday January 19, 2017 @08:00PM (#13440929)
It's only meaningful if you are using a language that has static types, subtyping, and generics. That covers Java, C#, and Dart, but omits Ruby (no static types), SML (no subtyping), and Go (no generics), for example.

If you are a language where it matters, it comes in handy, even though you usually aren't aware of it. You probably already have a correct intuition of it without realizing it.

Say you have a class like:

    class Enclosure<T> {
      void cage(T item) { ... }
    }
And assume some sort of class hierarchy like "Mammal is a subclass of Animal". If you have a method like:

   putKittyInCage(Enclosure<Mammal> enclosure, Mammal kitty) {
     enclosure.cage(kitty);
   }
Is this OK?

   putKittyInCage(new Enclosure<Animal>(), kitty);
The method expects an Enclosure<Mammal> and we're giving it an Enclosure<Animal>. Is that allowed? You probably intuitively see that it should be — an Enclosure<Animal> can hold any kind of animal and mammals are all animals. Your intuition is right.

"Contravariance" is the term to describe precisely what that intuition represents.

Re:My Go Resolutions for 2017 by munificent on Wednesday January 18, 2017 @05:43PM (#13431109)
No, you can use a type parameter in a covariant or contravariant position. What it means is that it treats all generic classes as covariant with respect to their type parameters, even when the type parameter is used in a contravariant position.

So, for example, List<T> is covariant—you can assign a List<int> to List<Object>—even though add() takes a T. This isn't statically safe, so the language inserts runtime checks to ensure you don't break soundness.

Re:My Go Resolutions for 2017 by chaotic-good on Thursday January 19, 2017 @02:18AM (#13433790)
> Exceptions give you stack traces automatically. All of that context (and more) is there without library authors having to manually weave it in at every level of calls.

I find exceptions really useful but also, I think that with exceptions people tend to loose very useful panic/error dichotomy. I saw projects without a single "panic" in the code. All those projects gravitated towards dumb error handling mechanisms aka "log all errors and continue".

Re:My Go Resolutions for 2017 by rogpeppe1 on Thursday January 19, 2017 @12:40PM (#13437615)
Exceptions don't always give you useful stack traces in a concurrent situation, because the current stack may only reflect a goroutine that's processing data on behalf of another. The real execution context may involve many more.