Other contenders I find myself sharing and re-reading:
- https://swtch.com/~rsc/regexp/regexp1.html
- https://swtch.com/~rsc/regexp/regexp4.html
Hope he gives us more in the future
thanks rsc
At the time, the community seemed to have settled on dep - a different, more npm-like way of locking dependencies.
rsc said "nope this doesn't work" and made his own, better version. And there was some wailing and gnashing of teeth, but also a lot of rejoicing.
That makes me a bit sad that rsc is leaving.
On the other hand, I don't really like the recent iterator changes, so maybe it's all good.
Btw if you reading this rsc, thanks a lot for everything, go really changed my life (for the better).
It's not that hard to understand what OP means.
- You need to be able maintaining code with multiple ways to iterate over various collections in standard Go packages.
- You need to spend time on deciding which approach for iteration to use when you write new code.
The benefit of Go’s generator implementation is a huge simplificación of the language semantics compared to other approaches. The generator function has no special semantics at all, and when used with ‘range’, all that occurs is a very simple conversion to an explicit loop repeatedly calling the function. Other popular approaches require either special runtime support for coroutines of some form, or a much more elaborate translation step within the compiler.
I’ve wasted so much time dealing with “module hell” in go, that I never dealt with in the prior years of go usage. I think it has some major flaws for external (outside Google) usage.
Ah, I still remember this thread:
https://groups.google.com/g/golang-nuts/c/rvGTZSFU8sY/m/R7El...
An integer range is a very basic type, too, conceptually, but many languages don't support them in the type system. You get an unsigned int type if you're lucky.
Not really, its semantics get hairy almost instantly. Eg does it incrementing it produce a new range?
Or do you give up on answering that and simply prevent adding strings and integers? When one wants to add them they can first manually apply an appropriate type conversion.
That is certainly a valid way to address your question – i.e. don't allow incrementing said type. Force converting it to a type that supports incrementing, and then from that the developer can, if they so choose, convert it back to an appropriate range type, including the original range type if suitable.
Of course, different languages will have different opinions about what is the "right" answer to these questions.
The original statement was about a range type, that is something like an integer that is statically constrained to a range of, say, 1..4 (1, 2, 3, 4).
To work with this as a type you need to have type level operations, such as adding two ranges (which can yield a disjoint range!), adding elements to the range, and so on, which produce new types. These all have to work on types, not on values. If 1..4 + 5..8 = 1..8 this has to happen at the type level, or, in other words, at compile-time.
Range types are very complicated types, compared to the types most people deal with.
Converting a string to an int is very simple to type (String => Int if you ignore errors) and adding integers is also simple to type ((Int, Int) => Int)
The quoted part above is an argument for dependent types. The conversion back to a range type creates a type that depends on a value, which is the essence of dependent typing.
But such a thing would be useful mostly for fields in data structures, and the runtime checks would add overhead. (Though, perhaps it would replace an array bounds check somewhere else?)
Just signed vs unsigned makes this a complex topic.
Separating the concept of pointers and nullable types is one of the things that I think go having from the beginning would have made it a much better language. Generics and sum types are a couple of others.
All reference types should be able to take a null value.
It's impossible to write complex and performant programs without null.
It's impossible to write complex and performant programs without pointers.
References always hold a memory address in a linear address space. (Not even true in C!)
Every type is comparable.
Every type is printable.
Every type should derive from the same common type.
All primitive types should support all kind of arithmetic the language has operators for.
The only way to extend an existing type is to inherit from it.
What else?
Well, clearly there is a need for a special value that is not part of the set of legal values. Things like std::optional etc. are of course less performant.
If I can dream, all of this would be solved by 72-bit CPUs, which would be the same as 64-bit CPUs, but the upper 8 bits can be used for garbage collection tags, sentinel values, option types etc.
https://www.cl.cam.ac.uk/research/security/ctsrd/cheri/cheri...
Address space is 64bit, pointers are 128bit, and encode the region the pointer is allowed to dereference. And there's a secret 129th bit that doesn't live in the address space that gets flipped if the pointer is overwritten (unless it's an explicit instruction for changing a pointer)
There's a neat trick available here: If you make zero an illegal value for the pointer itself, you can use zero as your "special value" for the std::optional wrapper, and the performance overhead goes away.
This is exactly what Rust does, and as a result, Option<&T>, Option<Box<T>>, etc are guaranteed to have zero overhead: https://doc.rust-lang.org/std/option/index.html#representati...
Well, I'd rather not copy around a multi-hundred-megabyte (or gigabyte) 3D object around to be able to poke its parts at will.
I'll also rather not copy its parts millions of times a second.
While not having pointers doesn't make impossible, it makes writing certain kinds of problems hard and cumbersome.
Even programming languages which do not have pointers (cough Java cough), carry pointers transparently prevent copying and performance hits.
The operations written in a program must literally represent the operations the computer will execute.
This one stops being true on high-level languages at the level of x86 assembly.
"Assembly is not a low level language" is my favorite take, and with microcode and all the magic inside the CPU, it becomes higher level at every iteration.
And in some languages, where you only operate on values, and never worry about where something is stored, allocation is just an implementation detail.
I assume you mean by only allocating on the stack? Those are still allocations. It's just someone else doing it for you.
> And in some languages, where you only operate on values, and never worry about where something is stored, allocation is just an implementation detail.
Again, that's someone else deciding what to allocate where and how to handle the pointers etc. Don't get me wrong, I very much appreciate FP, as long as I do information processing, but alot of programming doesn't deal in abstract values but in actual memory, for example functional programming language compilers.
Example in C:
void fun(void) {
int a[16];
for (int i = 0; i < sizeof(a); i++)
{
a[i] = 1;
}
}I have allocated and referred to memory without pointers here.
Here's something to try at home .. exactly your code save for this change:
i[a] = 1;
... guess what, still compiles, still works !!WTF ??? you ask, well, you see, X[Y] is just syntactic sugar for X+Y - it's a pointer operation disguised to look like a rose (but it smells just the same).
It’s 2024, every type is jsonable!
And the dismissive tone of some people including Ian. But to be fair before Rust there was definitely this widespread myth in the dev hivemind that nullable pointers is just the cost of performance and low level control. What’s fascinating is how easy and hindsight-obvious it was to rid code of them. I’ve never had to use pointers in Rust and I’ve worked on quite advanced stuff.
Go needs a null-like thing because the language forces every type to have a zero value. To remove the concept of zero value from Go would be a major change.
To begin with, zero values were never a great idea. It sounds better than what C does (undefined behavior), but zero values can also hide subtle bugs. The correct approach is to force values to always be initialized on declaration or make use-before-initialization an error.
Having said that, it was probably too late to fix zero values by 2009, when Go was released to the public, and this is not what the thread's OP suggested. He referred to Eiffel, which is an old language from the 1990s (at least?) that didn't initially have null-safety (or "void-safety" in Eiffel's case), but released a mechanism to do just that in 2009, shortly after Tony Hoare's talk at QCon London 2009 (no idea if they were influenced by the talk, but they did mention the "Billion Dollar Mistake" in the release notes).
Eiffel's added nullability and non-nullability markers to types (called "detachable" and "attached"), but it's also using flow-sensitive typing[1] to prevent null-dereferencing (which is the main cause for bugs).
The thread OP didn't ask to eliminate zero values or nullable types, but rather requested to have a non-nullable pointer type, and flow-sensitive typing.
If structs need to be zero-initialized, a non-nullable pointer could be forbidden in structs, or alternatively Go could make explicit initialization mandatory for structs that have non-nullable pointers. At the very least, Go could support non-nullable pointers as local stack values, and use flow-sensitive typing to prevent null dereference.
Yeah, that’s the entire point. Type safety.
You'd have to start by constructing replacements for all those mechanisms, then migrate all Go source code in the world over to the new APIs, just to enable Go to have types without zero values.
Ouch, who were they asking? There are so many problems from even the most simple CRUD apps where "lack of a value" must be modelled, but where the zero-value is a valid value and therefore an unsuitable substitute. This is probably my single biggest pain point with Go.
Using pointers to model nullability, or other "hacks" like using a map where keys may not be set, feel completely at odds with Go's stated goal of high code clarity and its general disdain for trickery.
I know with generics it's now trivially easy to implement your own Optional wrappers, but the fact that it's not part of the language or even the standard library means you're never going to have a universal way of modelling this incredibly basic and common requirement across projects. It also means you're never going to have any compile-time guarantees against not accidentally using an invalid value—though that's also the case with the ubiquitous (value, error) pattern and so is evidently not something the language is concerned with.
Iterators are very nice addition, even with typical Go fashion of quite ugly syntax.
I didn't study the reason why Go chose this way over others. I do know they've considered other ways of doing it and concluded this one is best, based on complex criteria.
People who make value judgements like this typically ignore those complex consideration, of which playing well with all the past Go design decisions is the most important.
Frankly, you didn't even bother to say which language does it better or provide a concrete example of the supposedly non-ugly alternative.
Go had both generics and iterators from the get go. Just not user defined ones.
Thus it is obvious that the creators of the language always saw their need for a simple and productive language
There is close to zero practical need in user-defined generic types and generic functions. Go 1.18 opened Pandora box of unnecessary complexity of Go specification and Go type system because of generics. Users started writing overcomplicated generic code instead of writing simple code solving the given concrete task.
Generics also allow to write some data structures that would be useful e.g. to speed up AST parsing: writing a custom allocator that would allocate a large chunk of structs of a certain type previously required to copy this code for each type of AST node, which is a nightmare.
This sounds like a good application for Go interfaces (non-empty interfaces). The majority of generics Go code I've seen could be simplified by using non-empty interfaces without the need of generics.
But what can we do against of this? This what I think: - stuck to use Go 1.16 - fork Go 1.16 and continue develop lang from there - learn OCaml... - give up and consume what these people decide to add to lang next and everytime feel disgust
And I think there are many similar people like me.
There are few languages that are safer and easier to maintain, imho. The typesafety is superb.
Because of this change, Go 1.22 is actually the first Go version which seriously breaks Go 1 compatibility, even if the Go official doesn't admit the fact.
I always assumed that it was considered faulty to do so.
I'm just also watching your YT video on testing and enjoying it very much!
And there are a large quantity of private Go code in the world.
The authors of the change did try to prove such cases don't happen in practice, but their proving process is totally breaking.
It is my prediction that multiple instances of broken cases will be uncovered in coming years, in addition to the new foot-gun issues created by the altered semantics of transitional 'for' loops.
I think you are assuming more guarantees than are actually guaranteed.
You have a well-documented history of making incorrect claims about Go compiler and runtime behaviors, so this isn't surprising.
> since Go 1.22, you should try to specify a Go language version for every Go source file
What on Earth?? Absolutely 100% not.
It looks you don't understand the change at all.
The statement "since Go 1.22, you should try to specify a Go language version for every Go source file" is made officially, not by me.
> You have a well-documented history of making incorrect claims about Go compiler and runtime behaviors, so this isn't surprising.
The claim is totally baseless.
All my opinions and articles are based on facts. If you have found ones which are incorrect or which are not based on facts, please let me know: https://x.com/zigo_101.
Please provide a link to documentation on golang.org. Note: not a comment in a GitHub issue, not a blog article -- official stuff only.
> baseless
It should be evident by the consistent responses to your GitHub issues that nobody takes you seriously. Which is unsurprising, when you make recommendations like
> Anyway, since Go 1.22, you should try to specify a Go language version for every Go source file, in any of the above introduced ways, to avoid compiler version dependent behaviors. This is the minimum standard to be a professional Go programmer in the Go 1.22+ era.
Other than that, I agree with your comment.