Live data from Hacker News

Things about programming I learned with Go

mjk.space

111–120 of 157 posts

Re: Things about programming I learned with Go

#111
post #106

Earlier quoted context omitted.

What's the issue with composing a new type that has otherPackage.Receiver in it, and defining the new method on the new type?

The question could be asked the opposite: why is the new type necessary?

Because if you allow non-local method declarations, you have a ton of stuff to think about:

- Are these methods exported?

- If yes, how do you import them? Explicitly or implicitly?

- Given a method call, how can the developer tell where it was defined? How can she know if the same method call is available when using the same library in some other project? Remember that not everyone uses IDEs.

- etc.

I can see why the Go designers decided that it's just not worth it. I've seen how you turn a language into a mess with non-local method declarations (cough Ruby cough).

Re: Things about programming I learned with Go

#112
post #109

> 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…

> Go has automated delegation, and as we know, (automated) delegation IS inheritance [1, 2]. Some implementations of delegation are a bit more limited, some are a bit more expressive, but fundamentally they have the same purpose. No it is not inheritance, and Go doesn't have automated delegation, it has type embedding. Given struct A, if a function requires A, you can't pass any type B that embeds A, you must pass A.…

> 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 = object method foo = 0 end
  class b = object inherit a method bar = 1 end

  let f (x: a) = ()
  let () = f (new b)
You will get an error that the type of `new b` (= `b`) is not compatible with `a`, because they're not identical, even though `b` is a subclass of `a`.

If you replace the declaration of `f` with:

  let f (x: #a) = ()
it'll work, because `#a` denotes a polymorphic type, matching `a` or any subclass of `a` (same as though you'd specify an interface in Go [1]). You can also cast the type explicitly to `a` to work around the error.

[1] Like Go, OCaml uses structural subtyping.

Re: Things about programming I learned with Go

#113

> 1. It is possible to have both dynamic-like syntax and static safety IHMO: This convenience causes more harm than good in the long term. Note: My experience is that this is a minefield of bugs and defeats the point of type safety. Also, because Go lacks generics, there's a lot of boiler plate and interface being used as function argument (just check large open source repos on github and you will notice). > 2. It’s…

Hi, author here. Thanks for your opinion.

> 1. On a daily basis I work in a 200k LOC Ruby project and to me from that perspective lack of static typing is a minefield ;)

> 2. Since I have finally learned how to do proper composition (~a year ago) I haven't use the inheritance even once. Of course, you may say that my project is special, but I can't help feeling that inheritance is often overused.

> 3. Yes, I've also come across opinions that Go's channels are too low level to be used in a large commercial project. But still, as a concept I find them interesting.

> 4. True. But, you can have one goroutine that'll just "guard" that resource and communicate with it from many places using one shared channel.

> 5. TBH I've seen more flame wars about the "space vs tab" thing ;) As I mentioned in the article I don't think that's the best error handling pattern ever invented, but I just like the concept of treating errors as regular return values. IMO it's good to have it at the back of your head, regardless of the language you use.

Re: Things about programming I learned with Go

#114

Earlier quoted context omitted.

Inheritance is not a way to express sum types, it's a form of subtyping. A sum type is like a discriminated union, it can only be one thing at a time. Subtyping allows a value to have multiple (related) types simultaneously, which is much more expressive. I suppose you can use one level of single inheritance to emulate a sum type, but you could just as well emulate it with a discriminated union in Go, e.g. a struct w…

> Inheritance is not a way to express sum types, it's a form of subtyping. A distinction without a difference. This is probably most visible in languages like Scala and Kotlin, which implement algebraic data types by way of inheritance. That inheritance creates a subtyping relationship is irrelevant; there's a similar subtyping relationship between variants (or groups of variants) and the overarching type using a tra…

Pony (https://ponylang.org) uses sum types, perhaps excessively. Just yesterday, I wrote:

    (None | (In, USize))
I.e., None (the null valued type) or a pair made of a type variable (In) and a USize.

The thing is, the values that satisfy this type are not subtypes of None and a pair. (That would be silly, given None.) Such a value is either None, or a pair.

Re: Things about programming I learned with Go

#115
post #69

Earlier quoted context omitted.

A sum type `T = A | B` means that a value of type `T` can be either of type `A` or of type `B`. Such a type is used to express polymorphism. A product type (from "Cartesian product", i.e tuples) `T = A * B` means that a value of type `T` has a component that is of type `A` and another component that is of type `B`. It is used to aggregate parts into a whole. > Yes, I could go and research functional programming langu…

>A sum type `T = A | B` means that a value of type `T` can be either of type `A` or of type `B`. Such a type is used to express polymorphism. Taking this example (in English / pseudocode): Define a class Animal. Define Dog as a subclass of Animal. Define Cat as a subclass of Animal. Case 1) Now if we have a variable a1 that can, at runtime, contain (or refer to) either a Dog or an Animal instance. Case 2) And if we h…

I'm thinking you just violated the Liskov substitution principle (https://en.wikipedia.org/wiki/Liskov_substitution_principle).

A sum type is the same thing as a tagged union, a variant record, or a discriminated union (https://en.wikipedia.org/wiki/Tagged_union).

Re: Things about programming I learned with Go

#116

Earlier quoted context omitted.

There's a lot of languages that can make that claim, and lots of developers that would pick up a new C-like language in a week or so. The challenge is that companies that picked Go or $uncommon_language need to provide the space and training opportunity. Go may be easy to learn, but mastering it is a thing on its own, just like the other languages.

Go's spec is much smaller than most mainstream languages. Seriously, it's a weekend read to understand the entire language specification (and it's actually readable)

Like it's numeric tower?

Re: Things about programming I learned with Go

#117

> 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…

What affordances does Go even have for real composition though?

Re: Things about programming I learned with Go

#118
post #89
post #80

Earlier quoted context omitted.

You'd need generics and algebraic types to implement Result/Option

False. That feature allows a more efficient implementation, but (T, error) (or an equivalent struct) can be reasoned about in much the same way as Result . You don't need a tagged union when you can use the nil-ness of one of the two values in the tuple as the tag. Similarly, Option is just a wrapper around a nullable T.

That would let people do this, and I hear people ask for it a lot. I dunno if it'd actually be better without the possiblity of not-wow-slow combinators.

Golang has all these weird performance pitfalls that you only hit if you contort the language too hard. Naming combinatior functions sometimes trips those conditions.

I'm of the opinion that even just pattern matching and the kind of nil type propagation checking that TypeScript does could help enourmously.

Generics won't fix error handling without compiler help, and the best way to get that help is to introduce pattern matching as a forcing function.

Re: Things about programming I learned with Go

#119
post #109

Earlier quoted context omitted.

> Go has automated delegation, and as we know, (automated) delegation IS inheritance [1, 2]. Some implementations of delegation are a bit more limited, some are a bit more expressive, but fundamentally they have the same purpose. No it is not inheritance, and Go doesn't have automated delegation, it has type embedding. Given struct A, if a function requires A, you can't pass any type B that embeds A, you must pass A.…

> 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 polymorphism in Go is through interfaces which are not concrete types, unlike classes.

> [1] Like Go, OCaml uses structural subtyping.

No it doesn't. There is no subtyping in Go. There is only type conversion and type assertion.

Whatever you wrote with OCaml is completely irrelevant to the discussion as the type systems are fundamentally different. but let's pretend it is.

It's interesting that you didn't bother try writing the equivalent of `let f (x: #a) = ()` in Go, because you CANNOT. An interface IS NOT a substitute for OCaml inheritance, as the later is more precise and specialized.

So no, Go doesn't support inheritance at its core. That's a false assertion. Go interfaces do not give a damn about what the actual implementation is, unlike OCaml sub classes.

Re: Things about programming I learned with Go

#120
post #77

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…

> 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 in 'Ok' and `a I'd much rather write this:

    fun some_function(a: &A) -> &B throws SomeError {
        let c = foo(a);
        let d = foobar(a, c);
        if xfoo(c) {
            let e = blah();
            bar(d, e)
        } else {
            baz(d)
        }
     }
See how that's so much cleaner? It's not actually any different from exceptions anyway, you're basically using them like exceptions, and they're implemented in the same way. The difference is that in the latter the code is much simpler and easier to understand. That's all.

In fact, that syntax could be added to Rust (after 6-12 months of bikeshedding as usual) and just have it automatically translated to the above anyway.

The other issue with Result/Option is that people start doing really horrible things like adding Option::map. Sorry but it's not a container that has 0 or 1 things in it. It's an optional value. That they're mathematically equivalent doesn't mean that they're the same thing conceptually. It's as bad as pretending that Result is useless and everyone only needs Either where by convention R is the error value. God please just no.

Post reply on HN