Expectations for generics in Go 1.18
groups.google.com
groups.google.com
[0]: https://www.reddit.com/r/rust/comments/5penft/parallelizing_...
type ImmutableTreeListᐸElementTᐳ struct
"If you look closely, those aren't angle brackets, they're characters from the Canadian Aboriginal Syllabics block, which are allowed in Go identifiers. From Go's perspective, that's just one long identifier."Simultaneously amusing and disturbing. Is there an award for which one might nominate this person?
Maybe the Unicode Consortium should change it, but for now, Canadian Aboriginal is the correct term.
Just do a find and replace, refactoring is easy and trying to argue on the internet about it is tiring.
> trying to argue on the internet about it is tiring.
… exactly.
At the very least they could have used the cute Japanese 「quotation marks」 to avoid confusion.
Definitely the correct response.
Also,
> c++ allows 0-width spaces in variable names. Where's your god now?
Nowhere. We have strayed maximal distance from god's light and are currently in orbit around Satan.
Is that true though ?
> A valid identifier must begin with a non-digit character (Latin letter, underscore, or Unicode character of class XID_Start) and may contain non-digit characters, digits, and Unicode characters of class XID_Continue in non-initial positions. Identifiers are case-sensitive (lowercase and uppercase letters are distinct), and every character is significant. Every identifier must conform Normalization Form C.
This seems to be the only limitation for that to be true.
So the question is does 0-width space (U+200B) survive canonical decomposition, followed by canonical composition?
It feels like it does to me.
Clang logs a warning about potentially invisible characters every time you use them. g++ just compiles the code without warning, even with -Wall and -Wpedantic.
What clang _doesn't_ warn for, is the use of the left-to-right override character. This can be used to confuse the victims of your code even more.
Upon further investigation(I think IntelljIDEA reported Unicode value) I've found out that the 'semicolon' I copied is a Greek character that looks exactly like semicolon.
I wonder how people on vim/emacs deal with situation like this.
And in my case, even if compiler / interpreter reported about an invalid syntax - I would not think of checking the unicode codepoint as it the character appeared to look exactly like semicolon.
I guess that's a lot of ifs/ands but it's interesting that one could have future proofed their code if their bet on syntax paid off
It might be useful for editors and compilers to check for tricky Unicode (lookalikes for common characters, atypical changes in direction, invisible spaces or other formatting control codes, etc.) in this era of copy/paste coding.
[0] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p194...
Oh gosh oh gosh zaglo variables please no
But Go having actual generics, and accordingly removing some need for codegen, is not a bad thing at all.
I for one won't be using more advanced functional coding though, it's a lot more difficult to read / parse and the language is not designed with functional programming in mind. I'm sure people are already working on a functional programming library so you can do map / reduce / filter and the like, but readability and performance will be dreadful.
Next thing you know they will try to force me into structured programming (I'm sure there are people already working on do...while). They can pry goto from my dead cold hands.
def traverseImpl[F[_], A, B](fa: Option[A])(f: A => F[B])(implicit F: Applicative[F]) =
fa map (a => F.map(f(a))(Some(_): Option[B])) getOrElse F.point(None)That's the type. The expression makes little sense to me, but I suspect you removed a bunch of dots and maybe some parens, and it should actually look like this:
fa.map (a => F.map(f(a))(Some(_): Option[B])).getOrElse(F.point(None))
So this function essentially just operates on an option value inside of some functorial context. This seems like a building block function that you might use often, but probably not write very frequently.(If you didn't remove anything, then I suppose this language has infix 'map' and 'getOrElse' operators. I think that's bad, but it's not related to the type system.)
Idea, I think, is to make the repl look more like a CLI, but it also may be a fight against punctuation.
If you are interested in generics in Go 1.18 please read https://go.googlesource.com/proposal/+/refs/heads/master/des...
Personally, I'm super excited for the potential generics has to make error handling in Go less noisy, so I'll be attempting to use it ASAP.
The previous try() proposal added new control flow. That's part of the reason it was criticized so heavily -- it hid a control flow construct in something that looked like a function.
At the very least, I'll appreciate having some more sugar over loops that are essentially just map/filter.
Before Rust 1.12, Rust has a macro, named try! which does a similar thing to its argument although the built-in operator has some fancier features.
It's both. Adding only concise flow control, such as with the ? operator, works poorly if all you can return is multiple non-generic values; various common permutations get awkward in that case. And as you point out Result<> doesn't achieve much without the concise operators. Rust has very successfully shown the benefit of the combination.
I like there's a language that has such a strong vision, but dealing in absolutes has drawbacks, a little sytactic sugar would make error handling a lot nicer.
I do appreciate rusts match syntax though.
Absolutely false. When you write the really groundbreaking and important software "feels right" is absolutely not what you should be experiencing.
The correct mode you should be in is "holy fuck how did I get here, and can I come out in one piece"?
(A: no, you can't, but the new you will be a better you.)
From what I know of zig’s error handling, I really don’t think it’s better than result types.
It is less work, especially when using error unions (though I think that makes it easier to miss changes to error sets as they’re implicit), but since errors are literally just u16 they’re also deeply lacking in richness and composability.
It’s definitely an improvement over C’s error style, and thus perfectly understandable given zig’s goal, but I very much disagree that it’s “better than Result”.
It’s also extremely magical in that it needs special language-level support throughout in ways Result is not, but obviously that’s more of a matter of taste.
That's a peculiar observation, it's like saying Rust's borrow checker is magical because it needs language-level support. I mean, that's the point.
In any case re: zig error or rust's Result, I don't think either of us are discussing features and pros/cons, but just describing what we like the best. Not very objective an argument.
> but since errors are literally just u16 they’re also deeply lacking in richness and composability
Zig errors are composable: https://ziglang.org/documentation/master/#Merging-Error-Sets and later sections.
You can’t do borrow checking without language support (I think, at least not without a significantly more expressive type system) whereas Result is pretty much just a type, the features it uses are mostly non-exclusive, and those which are, are very much intended not to remain so (e.g. the `Try` trait for the `?` operator).
> Zig errors are composable: https://ziglang.org/documentation/master/#Merging-Error-Sets and later sections.
Not really? That’s just merging error sets, but you can’t say that you’ve got an error which originally comes from an other error, except by creating its dual and documenting it as such. Essentially your choices are full transparency or full type erasure.
What you just described are error traces. They're generated by Zig auomatically for you, thanks also to the fact that errors are a special construct in the language.
Relevant langref passage:
https://ziglang.org/documentation/master/#Error-Return-Trace...
Sum types are a powerful language feature, but they’re also a general language feature, they don’t exist for the sole purpose of creating a Result type (and indeed a number of languages have had the former and lacked the latter).
And affine types are not here for borrow checking, it’s closer to the opposite. Affine types are a formalisation of RAII, borrow checking is a way to make references memory-safe without runtime overhead which mostly avoids unnecessary (and sometimes impossible requiring allocations & refcounting) copies of affine types.
I mean there's nothing stopping you in zig from creating a rich "error" struct and returning a union of the struct with whatever you would have done otherwise. You only lose the error return trace.
Thus Rust's std::result::Result<T, E> where the E in Err(E) is anything that implements the std::result::Error trait.
Rust got this right. It satisfies most use cases with the least possible noise and accommodates the weird ones easily, and does it in a std:: codified manner where no one has to wonder whether or not there is anything stopping them.
If there is a problem with how it's done in zig, it's a community one: maybe the pattern needs a megaphone beside it, like examples in the main docs or tuts in the various zig tut sites that have cropped up
No, and I'm not arguing that zig hasn't done well. I do argue that Go has not; you're only non-weird choice is to fetter code with miles of "if err ==/!=" gymnastics.
Traces are frequently very useful; precise traces have saved me a lot of pain in life. Lack of traces precludes nothing for me, but I have enjoyed the benefit of them enough times to acknowledge their great value.
Yes, it was the second proposal by the Go team for shorter error handling: https://github.com/golang/proposal/blob/master/design/32437-...
> I'm really curious what happened to the proposals for shorter Go error handling
The community didn't like it and it was declined: https://github.com/golang/go/issues/32437#issuecomment-51203...
People can now create Try or Option monads instead of returning the error.
This was the one of the reasons anti generics people were anti generics
I can't see it myself, but happy to educate myself.
But more verbose? I'm not so sure.
go: don't change the libraries in 1.18
Yes: https://go.googlesource.com/proposal/+/refs/heads/master/des...
let (a,b) = w::<x,y>(z);So for C# it's resolved "by examining the token after the closing >: If it is one of (, ), ], :, ;, ,, ., ?, == or !=, the expression is parsed as a generic method call."
I assume this works reasonably well; however, it's not hard to see how this might be a problem in some cases where the parser gets it wrong.
For ambiguities with angle brackets consider the assignment
a, b = w < x, y > (z)
Without type information, it is impossible to decide whether the right-hand side of the assignment is a pair of expressions:
(w < x), (y > (z))
or whether it is a generic function invocation that returns two result values:
(w<x, y>)(z)
In Go, type information is not available at compile time. For instance, in this case, any of the identifiers may be declared in another file that has not even been parsed yet.
> also Julia at least is prior art
Julia doesn't use curly braces for blocks and structs.
ᐸ & ᐳ
They thought about generics for 12 years. It's unlikely that someone who thought about it for 3 minutes on Hacker News has some epiphany that wasn't taken into consideration.
Also, I think it is fair to challenge the decisions here seeing as how there have been a few things chosen in Go's history that ignores prior art from other programming languages.
Not a compiler expert, but I got confused when reading this, when is it available?
There is a longer and more precise description of the problem as part of this FAQ of the generics proposal:
https://go.googlesource.com/proposal/+/refs/heads/master/des...
…which includes an example and the statement “It is a key design decision of Go that parsing be possible without type information”.
In Go, you can do this fairly easily with the go/ast package.
Then you take the AST and do something with it. That something can be compiling the code, but also writing some sort of tooling like a linter, or identifier rename tool, or generate documentation from it, or whatnot. When compiling the code you need the type information, for a lot of other purposes you don't really care.
It's pretty valuable to keep the parsing as simple as possible; it makes it easier to detect errors, improves the quality of the error messages, and makes it easier to write tooling. It also keeps the code a lot simpler, easier to understand and modify, etc.
Lexing/tokenization doesn't produce an AST; it produces a token stream. Parsing (which may or may not be preceded by lexing) produces an AST.
Well, you could... take a look at languages created these past twenty years that support generics, learn lessons from that, and see how these lessons could apply to Go.
But well, the Go team is not exactly known for paying much attention to the state of programming language theory, so I guess that's out.
Milner, R., Morris, L., Newey, M. "A Logic for Computable Functions with reflexive and polymorphic types", Proc. Conference on Proving and Improving Programs, Arc-et-Senans (1975)
(And of coures we've been able to write generic code in dynamic languages for a long time as this is a static typing concept)
Reasoning by analogy in a sneering way doesn't make it work better.
In fact, why stop at generics? Why not go back and question structured programming? Or strong typing? Because structured programming and strong typing has proven to be extremely useful. Same as generics in statically typed languages.
How much you should rely on others' experience, and when you should consider it a positive example versus something to learn from by avoiding, is going to be a judgement call.
Compile time? With template expansion like D and C++?
The upside is there is no runtime cost to using generics, the downside is there is significant compile time and link time cost to generics.
Are we talking statistically significant or productivity significant compile time cost? Go has a fairly fast compiler, it would be a shame if generics made that a less stand-out feature.
The downside is you can't do something like have a function pointer to the generic itself or specify generic members of a dyn trait/interface or otherwise treat the generic as a first class value (although you can treat specific instantiations of it as first class). The generic is basically syntactic sugar for a macro like substitution system. C++ and other languages that take the monomorphization route all have this restriction, whereas languages with more "first-class" support for generics like C# don't and hence do allow the moral equivalent of a virtual template.
Even in C#, generics are sort-of second class:
interface IMyLovelyADT { R Accept<R>(IMyLovelyADTVisitor<R> visitor); }
is not isomorphic to Func<IMyLovelyADTVisitor<R>, R>
The interface is strictly more flexible because it's non-generic, see my comment at [0] for an actual code example (it's quite long) if you're interested, but the gist is "you need a non-generic wrapper interface IMyLovelyADT: it essentially does the same job as Haskell's forall, since generic types are not quite proper types in C#'s type system. Interestingly enough, upcoming Go 2's generics will not support this scenario: a method inside an interface will be able to use only the generic parameters that the interface itself declares."[0] https://blog.ploeh.dk/2021/08/03/the-tennis-kata-revisited/
It will be Go 1.18, not Go 2.
And weren't they supposed to be in Go 1.17 but then got delayed?
Generics have the potential to impact a decent amount of the standard library. Although I’m not surprised that they are not doing a major version bump given the isolation of the feature at this stage, it will be interesting to observe its uptake in the community and whether fragmentation occurs over what is supported and what isn’t given their recommendation of isolating generics-related code in third party libraries.
With the addition of generics to Go, they've been careful to ensure it's fully backwards compatible, so all existing code will still work as is. Major version bumps are for incompatible changes (like Python 2.x to 3.x).
What they're recommending with isolating generic code in existing libraries is so users of older versions of Go (1.17 and prior) can still use those libraries, just not any new functions/types the libraries have added that use generics, and hence require Go 1.18.
https://golang.org/doc/go1compat
The fact that they can release generics and NOT bump to 2.0 is a triumph of the language designers and implementers, a testament to how much effort has been put into keeping this commitment.
I have a feeling this won't happen.
My plan is to not use generics for quite a while, as it's pointless to have two separate implementations, one for 1.18 and one for < 1.18, which is difficult, because I really like the feature.
I hope that the publicity caused by generics doesn't taint this release causing people to unnecessary procrastinate a toolchains update they would have otherwise done if it wasn't for generics.
Hacking on some projects or experiments is one thing, but say you're providing code for the automotive industry or the payment card industry; you're in for a world of regulatory hurt.
Explicit error handling can be tedious, but I’m never confused about what some code will do.
Compared to Java where an exception may bubble up 10 layers to a catchall try catch statement, I actually know where the error is coming from.
Would be nice to have this abstracted away with tooling though. I would imagine go generate could be used to great effect in this arena.
I prefer returning errors as a part of the function result myself, as you would in any decent language encouraging functional programming principles.
If anything, it shows that including a feature in a language (exceptions) will not lead to people misusing it if that misuse is not encouraged.
Meanwhile, as a non Java programmer, I've never seen a Scala app. I know about R, but nothing on my PC is written in it.
Agreed that PHP is massive in comparison to all of them.
One widely known Scala application is Kafka. Some parts of it are in Java, but actual core is still mainly Scala afaik.
You could say the same thing about excel macros. They are both tools for wrangling data, tools that will typically not be distributed to end users.
I guess it is a thing, after all Go folks tend to ignore the experience from other programming communities.