Hacker News new | past | comments | ask | show | jobs | submit login
[dupe] We Switched from Python to Go (2017) (getstream.io)
37 points by metadat on Oct 27, 2022 | hide | past | favorite | 30 comments




Thank you, @pvg, for pointing out the recent thread, I hadn't seen it!


Needs a (2020) since that's when it was last updated. Originally posted in 2017 and updated in 2019 and 2020.


From the article it seems they were needlessly using classes and doing funky stuff with Python everywhere. It speaks more about the quality of team than the move to Go.

I've used both and maintaining legacy Go code is still quite a nightmare with the bazillion ways people can fuck up dependencies, error handling, and non existent types. Python is ok in comparison, not good, just ok. If I wanted a dynamic language that offers speed I would have just stuck with Common Lisp tbh.


Author here, this post is ancient by now. Definitely time for an update since people keep on posting it.

Go has been great at Stream. We are hiring atm. No go experience required and various levels of seniority.


I feel python’s performance pain with anything related to pandas. The issue with this post is that Go isn’t an obvious substitute due to its more verbose syntax.


Huh, as a non-go programmer, I was pretty shocked to see it doesn't have operator overloading. How have experiences with that been? Seems like a big omission but it is a pretty well regarded language, so I guess they must have thought this out.


I think the only time I used operator overloading was when I was a junior C++ programming trying to be clever - maybe 15 years ago.


Operator overloading is more or less banned in most style guides unless you're working with well known "math" types. And for good reason. I'd love anyone to tell me what the (^@?!) operator in Haskell's popular lens library does.

It's just... not a good feature for the most part.


The few times I've dealt with code that does some (even just very basic) algebraic manipulation, I found the lack of operator overloading to be _really_ awkward (look no further the math/big library). For this reason there are many types of mathematical code that I would never consider writing in Go. But outside of those domains, I haven't really missed operator overloads at all.

Well, I guess the lack of an overload for <, >, and == is somewhat annoying. But I don't think those as are essential as arithmetic overloads are for mathematical code.


I don’t like operator overloading much. I think it hides a lot of what’s happening behind the scenes. It’s good syntactic sugar but wouldn’t be a deal breaker for me.

I love python. My personal complaints about it - speed, gil and static typing. Other than that I love using it.


It's cute for trivial cases like algebra libraries, but very ripe for abuse (std::cout) and hard to get right for scenarios where it’s two different types being operated on, especially if a) the leftmost one is a built-in or b) various combinations of casts could get you to a supported operator.

Rust obviously does do operator overloading, but it addresses (a) mostly through culture, and (b) by insisting on explicit casts everywhere.


> It's cute for trivial cases like algebra libraries

I don't understand this wording. Algebra libraries are nontrivial to implement, as the algorithms that operate on such structures can be exceedingly complex, and there are many types of algebra relevant to computer programming. Linear algebra (and its big brother, tensor algebra), boolean algebras, polynomials...


I get that under the hood, a library like eigen has loads of tricks and optimizations to make vector and matrix operations maximally efficient on modern CPUs.

But the comment was specifically about the interface to those operations, that it's convenient and non-harmful ("cute") to have an infix operator just work, because no one would expect that multiplying vectors is going to secretly be a function call that could block or throw or have other unknown side effects the way something like using "stream insertion" on a logger object might. Or have an uncertain effect, like defining operator+ for just one of the multiple possible schemes by which two dicts may be combined.


This is the example I had in the back of my head. Like ok I'll write my code in C or Fortran and dutifully provide all 13 arguments to BLAS because I'm a dummy but I thought people used languages like Go to avoid this sort of thing.


It’s hard to design good operator overloading. In c++ it’s difficult to do correctly to the point of being considered an antipattern in many shops


It's all about culture. I learned that operator overloading is evil when I worked in C++. Then I worked in python and it's actually fine. Python has much more powerful overloading capabilities (you can overload a LOT more), and yet it's not a total mess like in C++.


C++ making bad choices more or less universally impugns on operator overloading no more than on anything else.


How is the dependency handling situation today? Not being able to refer to specific versions seems like a deal breaker for serious production applications.


You've been able to refer to specific versions for as long as vendoring has been used (Godep was first released in 2013 so at least that long, if not longer) and Go has supported it natively with Go modules since Go 1.11 (2019).


Go today has arguably the best dependency handling story: https://go.dev/blog/supply-chain


Seems like any statically typed language would be a big win over python.


good article - I’d love an excuse to use go


Can someone explain how this is "simple" to understand?

  type openWeatherMap struct{}
  ...
  var d struct {
    Main struct {
      Kelvin float64 `json:"temp"`
    } `json:"main"`
  }

  if err := json.NewDecoder(resp.Body).Decode(&d); err != nil {
    return 0, err
  }
There is a "type" struct at the top, but then another struct without a type later on. Then there's a nested struct, and then backticks with double quotes and json with colons - one with the backticks after float64, and the other after a brace. Then rather than returning the result of the json response with an equal sign, the json decoder line takes in both the response and the output as function parameters! And one of them has an ampersand in front?!

Time and time again Go is said to be a simple language, but can you really say that the Python equivalent of that example is more difficult to understand?

  >>> decoded_json = json.load(urllib.request.urlopen('https://httpbin.org/get'))
  >>> print(decoded_json)
Come on now.


I believe you're conflating "simple in design" with "simple/easy to learn". Go is simple is design. It's not targeted to be easy to learn for someone without basic cs knowledge. As a meta some time spent watching simple made easy https://www.youtube.com/watch?v=kGlVcSMgtV4 will help on the difference. For example:

> There is a "type" struct at the top, but then another struct without a type later on.

1) You not being familiar with anonymous types, does not make go complex. Anonymous types (var d struct) have been around for a _very_ long time.

> Then there's a nested struct, and then backticks with double quotes and json with colons - one with the backticks after float64, and the other after a brace.

2) Anonymous types and tagging. The tags are a convenient, orthogonal language feature.

> Then rather than returning the result of the json response with an equal sign, the json decoder line takes in both the response and the output as function parameters! And one of them has an ampersand in front?!

The json decoder in go works on structs by reference to improve speed. You'll need to understand passing by reference vs value to understand why this code is structured the way it is.

All three of these things are _simple_ features but they may not be easy to understand.

By the way the equivalent go code for the python you posted is actually:

d := make(map[string], interface{})

json.Unmarshal(resp.Body, d)

fmt.Println(d)


> It's not targeted to be easy to learn.

Not sure where you get that idea. "Easy to learn" is literally the second bullet point on the go home page. [0]

The golang authors (Russ Cox, etc.) in Communications of the ACM (May 2022): "Go is efficient, easy to learn, and freely available..."[1]

...and many other places similarly.

[0] https://go.dev/ [1] https://cacm.acm.org/magazines/2022/5/260357-the-go-programm...


_without basic cs knowledge_ is the second part of that sentence you cut out.

Go is easy to learn, for developers who already have fluency with notions such as pass by reference, typing, etc.


It doesn't matter if it's easier or harder to understand when the point is they are doing different things.

The Go code is enforcing some sort of primitive schema, which is why you have the struct definition and the error handling.

The ampersand is moving allocation up the stack and making a pointer to the local object. Pointers can complicate things from the perspective of a higher-level language, but I think that's more of a matter of experience.

I agree that the `json:` choice is something that puts me off Go a bit, but the alternative in other languages usually delve into hard to understand magic with decorators and reflection, so it makes sense from a simplicity standpoint.

Lastly, who ever said Go is simpler than Python? I'd say Go is more like a simpler C. It makes lower level programming more palatable.


`type` declares a type alias. `struct` defines a struct. If you're going to use your struct more than once, give it a name. If not, feel free not to.

The library "encoding/json" reads struct tags to figure out how to map names in JSON objects to the fields in the struct. It can guess, but you can also tell it. The convention `library:"expression"` is enforced by static analysis and is used throughout the standard library and ecosystem. It's not just for JSON.

Decode doesn't return the decoded value because it can't know the desired message type to return. "encoding/json" predates generics, but even with generics, it's not quite as simple as you think. With a decoding function like `func Decode[T any](data []byte) (T, error)`, you still have to invoke it as Decode[MessageType](data) in many circumstances.

Have a play with: https://go.dev/play/p/uKdS47KrsyR. You don't have to pass the result as an argument to decode, and there are no struct tags.

Finally, if you don't want a typed message, decode into a map[string]any. {"foo":{"bar"{"baz":123}}} decodes into an object that you can use like message["foo"]["bar"]["baz"]. Just hope that the server returned data according to the spec; it's shocking how often they don't.


That's not even close to the same thing.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: