Live data from Hacker News

Things about programming I learned with Go

mjk.space

121–130 of 157 posts

Re: Things about programming I learned with Go

#121
post #67

Earlier quoted context omitted.

I'm about 8 months in to using Go for a few largish projects and I'd say these are probably the two biggest things I still struggle a bit with. (not generics as others seem to obsess about) On errors, I'm really of two minds. In a way, it is a lot like how Java started with checked exceptions, it forced you to deal with the error. But at some point most people decided that was annoying and switched to runtime excepti…

Checked exceptions suck. Every method has to explicitly throw them up to a higher level where they can be handled causing tons of useless boilerplate. Its much better to just let unchecked exception bubble to a higher level of the app. This is a best practice in java so letting the exception bubble all the way back to the user is just poor programming.

That's exactly what you have to do in Rust anyway though.

Except instead of just writing `throws SomeError` in the declaration line of the function, you have to annotate virtually every line of your function with `?` and wrap the return line in `Ok(...)`.

Re: Things about programming I learned with Go

#122
post #25

"Error theater" and zero initialized values, while understandable due to other design decisions, are the biggest source of frustration whenever I have to work on go code. Off topic from the article, I think, but what go has taught me about programming is that some parts of our industry are stuck in time and intellectually stagnant. Go is a better C. I would prefer Go over Python. But really, when the state of the art…

This sort of attitude is never going to win over people that you are talking about. I hate to bring politics into things, but it's like calling all Trump supporters hopeless and backwards. Yeah maybe they are, but they're going to react to that by never listening to anything you say again, so even though it's true, it's not helpful.

The 'state of the art' is increasing, over-the-top complexity to a truly ridiculous level. Go programmers don't want to open up the documentation for a library and see this:

http://i.imgur.com/ALlbPRa.png

or look at the language reference and see this:

http://en.cppreference.com/w/cpp/language/constraints

and I don't blame them.

Re: Things about programming I learned with Go

#123

> Thanks to goroutines and channels Go programmers can take a different approach. Instead of using locks to control access to a shared resource, they can simply use channels to pass around its pointer. Then only a goroutine that holds the pointer can use it and make modifications to the shared structure. How does this prevent data races if more than one goroutine holds a pointer to the shard structure and they're run…

It doesn't. I think goroutines make concurrency easier that it is in, say, Java, but Go still passes around state and that is where a lot of the issues with concurrency arise. Concurrency is much easier to deal with in functional languages, where data is transformed via chains of functions, rather than stored in state.

The difference is that in Go, it is unidiomatic to share mutable state across threads, while in C/C++/Java it's not.

Re: Things about programming I learned with Go

#124
post #56

Earlier quoted context omitted.

In my experience, I've found that inheritance becomes a significant burden on projects, especially when using 3rd party libraries. If you need to modify something up in a base object in a 3rd party library, you essentially have to fork the project creating a new maintenance burden and breaking the upgrade path OR rebuild the entire inheritance tree. It's one of the things that makes Ruby so useful as an object orient…

> In my experience, I've found that inheritance becomes a significant burden on projects, especially when using 3rd party libraries. I think inheritance is like any sufficiently powerful programming technique - with great power comes great potential for shooting yourself (and others) in the foot. But there are situations where inheritance is an elegant and natural approach. They just are not very frequent. Python's s…

I disagree that this is elegant.

    import socket
    import threading
    import socketserver

    class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):

      def handle(self):
        data = str(self.request.recv(1024), 'ascii')
        cur_thread = threading.current_thread()
        response = bytes("{}: {}".format(cur_thread.name, data), 'ascii')
        self.request.sendall(response)

    class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
      pass

    def client(ip, port, message):
      with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.connect((ip, port))
        sock.sendall(bytes(message, 'ascii'))
        response = str(sock.recv(1024), 'ascii')
        print("Received: {}".format(response))

    if __name__ == "__main__":
      # Port 0 means to select an arbitrary unused port
      HOST, PORT = "localhost", 0

      server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
      with server:
        ip, port = server.server_address

        # Start a thread with the server -- that thread will then start one
        # more thread for each request
        server_thread = threading.Thread(target=server.serve_forever)
        # Exit the server thread when the main thread terminates
        server_thread.daemon = True
        server_thread.start()
        print("Server loop running in thread:", server_thread.name)

        client(ip, port, "Hello World 1")
        client(ip, port, "Hello World 2")
        client(ip, port, "Hello World 3")

        server.shutdown()
Why not this:

    def handle(request):
      data = str(request.recv(1024), 'ascii')
      cur_thread = threading.current_thread()
      response = bytes("{}: {}".format(cur_thread.name, data), 'ascii')
      request.sendall(response)
and this:

    options = socketserver.ServerOptions(
       concurrency=socketserver.Threading,
       sockets=socketserver.TcpSockets)
    server = socketserver.Server((HOST, PORT), options)
There's really not any use of inheritance here anyway: it's just a hack. It's such a hack that you need to inherit those two classes (ThreadingMixin and TCPServer) in that order, because one overrides a method of the other, and if you inherit them in the other order it just doesn't work.

It might simplify the implementation, I don't know, but it definitely doesn't simplify the interface.

Re: Things about programming I learned with Go

#125
post #12

"There is nothing exceptional in exceptions" No there's not, but it's a royal pain the arse to have to keep passing them up through your function calls to the level that actually cares about them and will do something about them. Try/catch eliminates boilerplate.

And even with exceptions, C++ is the only mainstream language I know that has a story how to undo any investments so far, including those that are not memory allocations . In any other language, you have to at least write wrappers that execute commands and catch any exceptions to do specific cleanup actions (like aborting a database transaction). And in C++, while it has a story how to do that, implementations must i…

I don't really agree that it's inelegant to decide whether the reason for quitting is a success or an exception, or whether it's necessary. Like, you've either committed, in which case you shouldn't need to do any cleanup, or you haven't committed, in which case you need to clean up, right?

If you have an object representing some sort of transaction, and you just have actually-do-all-the-work-at-once-on-commit-being-called semantics, you don't actually need to do anything in your destructor at all, right?

    template
    class transaction {
        std::vector things_to_do;
    public:
        void add_work_item(T t) {
            things_to_do.push_back(t);
        }
        void commit() {
            for (auto t : things_to_do) {
                t.do();
            }
        }
    };

Re: Things about programming I learned with Go

#126
post #119

Earlier quoted context omitted.

> No it is not inheritance, and Go doesn't have automated delegation, it has type embedding. Different names for the same thing. > func acceptA(a A}{} // you can't pass B here This says that the function isn't polymorphic in its argument, not that the types aren't polymorphic. Function resolution that is not polymorphic is not limited to Go, but occurs in inheritance-based languages, too. Example in OCaml: class a =…

> Different names for the same thing. No, different names for completely different concepts. > This says that the function isn't polymorphic in its argument, not that the types aren't polymorphic. Function resolution that is not polymorphic is not limited to Go, but occurs in inheritance-based languages, too. Example in OCaml: Struct types in Go are not polymorphic in anyway period, the only way to achieve polymorphi…

> There is no subtyping in Go.

There is. Go simply has structural subtyping [1] rather than nominal subtyping.

> It's interesting that you didn't bother try writing the equivalent of `let f (x: #a) = ()` in Go, because you CANNOT.

You forget that OCaml also uses structural subtyping. Writing `#a` is effectively the shorthand for the inferred interface. So you can write it also as:

  let f (x:  ) = ()
where

  
is the interface of any class implementing at least a method `foo` of type `int`, i.e. what you'd write as

  interface {
    foo() int
  }
in Go. You just don't in practice, because `#a` is both more convenient and readable.

And the corresponding Go function would be:

  func f(x interface { foo() int }) {
  }
[1] https://en.wikipedia.org/wiki/Structural_type_system

Re: Things about programming I learned with Go

#127
post #77

Earlier quoted context omitted.

> not generics as others seem to obsess about One reason people obsess about generics is specifically because of error handling. With generics, you could implement Result and Option types, which make error handling significantly more sane.

Personally I loathe this style of programming. It's not that it's difficult, it just seems to obscure code a great deal. Writing this sort of thing in Rust: fun some_function(a: &A) -> Result { let c = foo(a)?; let d = foobar(a, c)?; Ok(if xfoo(c) { let e = blah()?; bar(d, e)? } else { baz(d)? }) } where you have to write every function in this pseudo-do-notation where 'return' is just wrapping the return expression…

> See how that's so much cleaner?

No, I don't. I look at the former snippet and I can easily tell each and every function invocation that can cause SomeError. In your theoretical style, I have no idea whether foo, foobar, xfoo, bla, bar and/or baz will throw that error. I prefer explicit over implicit since I find it far more readable.

> really horrible things like adding Option::map

You can quibble about the names (Option and map), but Option is essentially the Maybe monad and map is bind, so you're kinda arguing against core functional programing constructs.

Re: Things about programming I learned with Go

#128
post #97

> It’s better to compose than inherit I know that this is just a restatement of the "composition, not inheritance" mantra in Go, but it still makes about as much sense as "product types, not sum types". A more meaningful statement would be: "use inheritance to express sum types, use composition to express product types." There's no "better" relation between the two concepts, each has its own distinct purpose. Yes, in…

"but that's a misunderstanding of what inheritance is used for" I'm not sure what you imply. Most OOP languages where people tell you not to use inheritance, but composition instead, so java, c++, c#, etc. In those languages inheritance can be used to create forms of product types, but also to share and override behaviour hierarchically. They can also create sum types, and all possibility of hybrids, like weird mix o…

> I'm not sure what you imply. Most OOP languages where people tell you not to use inheritance, but composition instead, so java, c++, c#, etc. In those languages inheritance can be used to create forms of product types, but also to share and override behaviour hierarchically. They can also create sum types, and all possibility of hybrids, like weird mix of sum and product types, partially closed, etc.

My point wasn't to give an exhaustive list of use cases for inheritance (which would require a small essay); I was pointing out that "composition over inheritance" is a nonsensical statement, just as (say) "loops over modules" would be, as it's a qualitative comparison of orthogonal concepts.

Re: Things about programming I learned with Go

#129
post #84

Earlier quoted context omitted.

What languages are you familiar with? It might help with explaining the concepts. Since we're on a Go thread, I'll point out that Go's structs and tuples are both examples of product types.

Most familiar with Python currently. Done some Ruby and Java and C and Pascal earlier. Some D and a bit of C++ and a bit of Go. I do understand that structs and tuples are examples of product types (because the range of the values for a struct or tuple is the Cartesian product of all the possible values for each field). My question was mainly about sum types as described by rbehrends, was trying to relate them in my…

Cool, so unions in C and C++ are a kind of sum type because the variable can have one of a set of types. More usually people think of tagged unions when they think of sum types, I believe Pascal's Variant Records are an example of tagged unions.

Re: Things about programming I learned with Go

#130
post #119

Earlier quoted context omitted.

> Different names for the same thing. No, different names for completely different concepts. > This says that the function isn't polymorphic in its argument, not that the types aren't polymorphic. Function resolution that is not polymorphic is not limited to Go, but occurs in inheritance-based languages, too. Example in OCaml: Struct types in Go are not polymorphic in anyway period, the only way to achieve polymorphi…

> There is no subtyping in Go. There is. Go simply has structural subtyping [1] rather than nominal subtyping. > It's interesting that you didn't bother try writing the equivalent of `let f (x: #a) = ()` in Go, because you CANNOT. You forget that OCaml also uses structural subtyping. Writing `#a` is effectively the shorthand for the inferred interface. So you can write it also as: let f (x: ) = () where is the interf…

> There is. Go simply has structural subtyping [1] rather than nominal subtyping.

No there is not, period.

  interface {
    foo() int
  }
is not sub typing. but it's interesting how you move the goal post on each comment. You go from inheritance to sub typing to "structural subtyping". You're not interested in a serious discussion.
Post reply on HN