So they either say they are done, or keep adding type theory stuff until it implodes, I fear.
Actually I am looking forward to type annotations in JavaScript now in the roadmap, being good enough for general use cases.
https://github.com/tc39/proposal-type-annotations/issues/184
But I heard an interesting argument: It's not TypeScript vs. vanilla JS; it is TypeScript vs. whatever else full-blown linting/IDE comfort you can get by still writing vanilla JS with no transpile step.
I also hope but doubt that they will do something few other languages dare: remove features.
Go moves at a pretty slow pace, adding only minor new features (and thus minor complications) in most releases. Even in 1.22, they are previewing a new feature, range-over-functions, which seem to be basically C#/Python's iterator functions - a feature which will, of course, complicate the language - but make certain programs simpler.
As a general rule, the more features a language has, the shorter program that implements a particular algorithm can be, but the harder it is to learn, and the bigger the chance that it will be misunderstood. There are exceptions where certain features make languages more verbose (e.g. access modifiers), but typically only in minor ways.
Yes, something people coming to Go would have assumed worked before looking at all range cases, but didn't.
s := []string{"hello", "world"}
for i, x := range slices.Backward(s) {
fmt.Println(i, x)
}
func Backward[E any](s []E) func(func(int, E) bool) {
return func(yield func(int, E) bool) {
for i := len(s)-1; i >= 0; i-- {
if !yield(i, s[i]) {
return
}
}
}
}
I don't think anyone expects this to work in Go as it is today, it's just a new feature that will make the language more complex, but it will make certain kinds of programs simpler to write.I should also note that the official name is "range-over-function iterators", I called it by a wrong name earlier.
I see this as more like the `yield return` functions of C#, which I definitely wasn't expecting. That can of course also be used to implement an iterator for a collection, but it seems much more general.
Given that before generics Go had exactly 3 types of collections, and that those were all iterable with range, I guess I never thought about this thing missing from the for loop.
~15 (?) years ago I wrote a "famous" blog post on how to use Iterator as a poor man Maybe/Option.
for (String s: m Option<String>) {
// Executed on Some
}My understanding about the use of advanced/more expressive TR features is that it's OK if you don't use them, and don't bother wasting time for most products. Bot if you are writing a library of framework in TS, go ahead especially since they are meant to improve the experience of consumers.
What made me stay in go is its amazingly unified build toolchain. The things you can do with go:embed and go:generate blow my mind in every other project.
The golang.org/x package is also another thing, where there is pretty much every internet RFC related implementation available, ready to use.
You can use whatever frontend framework you want, and just embed the html/css/js/asset files inside the binary with go:embed. In case of dynamic set of files, you can also write a Go utility to generate the embeddings with go:generate.
In addition to the ease of distribution (no more assets/ directory - just a single binary executable!), it also increases speed of the application, as it no longer has to perform file system reads in order to serve a webpage.
https://github.com/pocketbase/pocketbase/blob/master/ui/embe...
//go:generate protoc --go_out=src/generated/ protos/service.proto
Our CI pipeline is a dockerfile that looks vaguely like this: FROM golang:1.21 as build
go generate
go build
go test
FROM scratch
COPY --from=build ...
The CI steps are:
docker build <XXXX> && docker push <XXXX>We have a goland project template that has options for generate, build, test that matches what we do in CI rather than having that _one_ edge case that is the difference between `make build` and `go build`. That difference has caused more than one outage in my career.
And I've used go:embed to include .sql files for database migrations and querying. I should really spend some time on this POC I made (sqlite, goose for migrations, and an implementation of temporal tables) and publish it as a demo.
Till that day comes, you could use the "lo" library (inspired from lodash). It's my goto Swiss army knife for golang projects.
`context` also helps solve a bunch of the channel related use cases in a more elegant (IMO) way.
There are only a handful of things in that package I wish were included, such as "Keys()" on a map.
They were included in the experimental packages on google.com/x.
At least in my job(s over the years), turning a flat list of db records into a more complex, nested (potentially on multiple levels) data structure before handing it off to the front-end is a very common. I've seen it done with "simple, fast code" (although not in go specifically, but in other languages), but it very quickly turned into huge messes of of long nested for loops and was very difficult to read. LINQ, Lodash, java's streams... I sincerely can't understand how go developers live without them. They make me a lot more productive both at reading and writing the code.
For example: let's say we want to implement something akin to Java's Comparator interface.
Java allows interfaces to be extended with default implementations. It also allows methods to specify their own generics separate from the entire interface / class.
Thus the "comparing()" method can take in a Function<T, U> that extracts a value of type U from T that is used for comparison purposes. The return type is Comparator<T>.
(Generics simplified a bit, there are other overloads, etc.)
There's also thenComparing(), which allows chaining Comparator instances and / or chaining Function<T, U>.
As a consequence, one can use .thenComparing() to build up a Comparator from the fields on a class pretty quickly. Especially with lambda syntax.
Go doesn't support methods having different type parameters than the overall interface / struct.
Go also doesn't have default implementations. It doesn't allow function or method overloading.
Go does have first class functions, however.
To build the equivalent capability, you'd most likely build everything around a comparison function (func[T any](a, b T) int) and write a bunch of functions to glue them together / handle useful operations.
That impacts the readability of a long chain of calls, especially since Go doesn't have a lambda syntax to make things a bit tighter.
Getting rid of the limitation on method-level generics would make this _significantly_ more ergonomic.
The ecosystem isn't huge outside of Flutter, of course.
Sure you can accept some template project or CLI tool to kickstart things if just starting out, but at some point you will need to tweak the configuration and there is an enormous realm of options.
I'm surprised no one mentioned this already, but a runtime like Deno goes to great lengths to solve alot of these pain points. You get testing, linting, bundling, and Typescript out-of-the-box with sane settings. If Deno worked better with GRPC I'd probably be using it right now in my work projects!
Compilation, testing, and automatically formatting TypeScript all have multitudes of options with their own pros and cons. All that stuff is just built into the Go toolchain. Yeah it’s not always perfect but it’s more than good enough and, importantly, it’s ubiquitous. There’s nothing to think about or argue over.
That said, I’ve been wanting to try Deno. My understanding is that it takes a much more Go-like approach to running TypeScript.
just use a for loop. 90% of the time the code will actually be clearer, and faster.
the hoops people just through to avoid 2 extra lines of code is mind boggling.
>> The amount of time spent on things such as linting, selecting the correct library for server routing, the correct server, coding standards,
I don't fully agree here. Those points are pretty straight forward and coding standards (not formatting, for TS prettier is pretty standard, btw.) need to be defined even in Go projects. Also, Deno has much of the setting up solved.
>> basic error-handling and enforcing it with a custom error or Result type to get out of this nested try/catch hell which still loses the majority of errors.
Fully agreed. Maybe I have a big lack of understanding of error handling in NodeJS, but how on earth do I find out what functions can throw errors and what are the errors thrown? If someone could enlighten me I'd be really grateful. To be on the safe side I would need to run my whole code in a try..catch block. How to decide how to handle different errors if I don't know which errors can occur? On the other hand, just yesterday I had to debug a Rust panic in a smallish code base. Even with stacktraces turned on it took half an hour to find out where there error occurred. Still, Go and Rusts error handling is much better. IIRC, in Java you see all types of exceptions that can occur in the docs of a function?
>> Setting up testing and mocking. Setting up Prisma
Again, not a big thing IMO. If you like an ORM, Prisma is one of the best.
>> Don't get me wrong, I really do like Typescript.
Yeah, that's the thing. Typescript is such a fantastic language. Writing Go feels like using a hand-axe. Typescript's null handling alone makes it 10 times better (I hope everyone is using it, but that underlines your point regarding the conventions needed ...). I recently found a lib that gives us compile time checked pattern matching like in Rust.
The main pro and con to the JS ecosystem is the fact that it's so flexible; you can mostly do things how you want and there are so many libraries in each space all competing. But because you can do anything how you like it becomes hard to decide which library you want to use (or a diff one every project) and every dev has a different way of accomplishing the same thing.
I used to write internal libraries and frameworks a lot in my career and one of my mantras was to use TS to try to lock devs into doing things a certain way but there only so far you can go.
My main worry with Go is like, is everything built in? Are there multiple say, Web server libs like with nodejs or only a single option? How are deficiencies addressed if there are fewer options and fewer ways og doing things?
But after spending some time writing Go I now had to re-initialise a typescript project for a small-ish team (4-5 devs). The amount of time spent on things such as linting, selecting the correct library for server routing, the correct server, coding standards, basic error-handling and enforcing it with a custom error or Result type to get out of this nested try/catch hell which still loses the majority of errors. Setting up testing and mocking. Setting up Prisma and what not - and finally the PRs are still a hit and miss, some ok, some make use of weird JS functions..
Don't get me wrong, I really do like Typescript. But I gotta say after all of that it's just great using a language with a fantastic standard library, proper type-safety, with some coding standards built-in. It's obviously not without quirks, but it's pretty decent - and great to see that routing has now also moved into the standard library, another bit that you don't have to worry about - can't wait for some map/filter/find slice functions though!