News for hackers. Stuff that matters.
Re:Go is not an easy language by strken on Sunday February 21, 2021 @11:47PM (#26220964)
Go's lack of generics and metaprogramming can be extremely frustrating when you're working with services that copy data around from one struct to another, which ends up being a lot of them. You're left with reflection, code generation, or doing it field by field.

It'd be nice to have syntactic sugar for splatting structs together, or a library that let you do it in a typesafe and efficient way.

EDIT: I will defend the random map order, though. Golang maps were only predictable up until a set number of items, which caused hard-to-detect bugs where your small test case would be in order but your large real-life data would be out of order.

Re:Go is not an easy language by hombre_fatal on Sunday February 21, 2021 @11:55PM (#26221010)
I certainly agree. I would love for Go to get to a place where I never have to copy and paste code again. Generics will get us part way there.

The next item on my wishlist are algebraic data types. In complex projects I've opted for Rust over Go just because of the abstraction power of being able to represent things like:

    Dest = Register (A | B | C)
    Src = Register (A | B | C) | Immediate value
    OpCode = Nop | Add(Dest, Src) | ...
In Go, I'm so tired of `interface{}`.

Edit: To respond to your edit about map iter order, that's related to my complaint of Go's lack of dev vs release build. Randomizing map iter order in dev builds is acceptable to me. Go makes you pay for that in prod.

Re:Go is not an easy language by yuribro on Monday February 22, 2021 @01:25AM (#26221457)
I don't understand the jump from the lack of algebraic types, to using interface{}.

You can spell out all your types into explicit structs (where you can choose between a simple implementation with a little worse performance or to make the implementation a little more complex) and pass them around.

There are many complex code bases (kernels & databases come to mind) which use C (not C++), and they don't resort to passing void* around the "business logic" parts.

The idea that for a complex project you would choose a different language with so many different characteristics due to a minor detail about the type system (this is not exactly Haskell vs JS...). This kind of decision making would not pass in any reasonable organization...

Re:Go is not an easy language by kuschku on Monday February 22, 2021 @06:34AM (#26223331)
> (this is not exactly Haskell vs JS...

No, it's more like Haskell vs C, considering the expressiveness of Rust’s vs Go’s type system.

Re:Go is not an easy language by kaoD on Monday February 22, 2021 @03:38AM (#26222173)
I don't know Go. How can you emulate sum types on it? And how do you pattern match them?
Re:Go is not an easy language by simiones on Monday February 22, 2021 @06:36AM (#26223344)
It depends on what you mean exactly by "emulating sum types" and "pattern matching them".

An example is rolling your own discriminated union:

  type Avariant struct {
    a int
  }
  type Bvariant struct {
    b string
  }
  type SumChoice int
  const (
    SumA SumChoice = 0
    SumB SumChoice = 1
  )
  type Sum struct {
    type SumChoice
    A *Avariant
    B *Bvariant
  }

  sumA := Sum{choice: SumA, A: &{a: 7} }
  sumB := Sum{choice: SumB, B: &{b: "abc"} }

  func foo (x Sum) {
    switch(x.choice) {
      case SumA: {
        fmt.Printf("Value is %d", x.A.a)
      }
      case SumB: {        
        fmt.Printf("Value is \"%s\"", x.B.b)
      }
    }
  }
As usual with Go, it's ugly and verbose and error prone, but it just about works.

The previous poster was probably thinking of something similar but using interface{} (equivalent to Object or void*) instead of Sum, and then pattern matching using an explicit type switch:

  func foo (x interface{}) {
    switch actual := x.(type) {
      case AVariant: {
        fmt.Printf("Value is %d", actual.a)
      }
      case BVariant: {        
        fmt.Printf("Value is \"%s\"", actual.b)
      }
    }
  }
This is slightly less risky and has less ceremony, but it's also less clear which types foo() supports based on its signature. Since Go has only structural subtyping [0] for interfaces, you would have to add more boilerplate to use a marker interface instead.

[0] In Go, a type implements an interface if it has functions that match (name and signature) the interface functions. The name of the interface is actually irrelevant: any two interfaces with the same functions are equal. So if you declare an interface MyInterface with no methods, it is perfectly equivalent to the built-in `interface{}`: any type implements this interface, and any function which takes a MyInterface value can be called with any type in the program.

Re:Go is not an easy language by kaoD on Monday February 22, 2021 @08:48AM (#26224437)
Thanks for the write up effort.

On the first approach, with "error prone" you mean the tag could be incorrect, right? (or even have an impossible variant with 0 or multiple variants set).

Re:Go is not an easy language by simiones on Monday February 22, 2021 @09:30AM (#26224920)
Yup. Also, there is no exhaustiveness checking (the constants we chose to define for SumChoice(0) and SumChoice(1) mean nothing to the compiler anyway - exhaustiveness checking would have you test any possible int, since SumChoice is simply an alias for int).
Re:Go is not an easy language by BenFrantzDale on Monday February 22, 2021 @07:07AM (#26223557)
But are those two variants held in memory inside Sum or are they heap-allocated sitting out on some other cache line? Can one write a performant Sum type?
Re:Go is not an easy language by simiones on Monday February 22, 2021 @08:09AM (#26224074)
With pointers, they are in the heap, but at least there is no overhead. You could also declare them embedded (without the *s), but then the Sum struct would have a size that is a sum of all the variants' sizes, instead of a proper union which should have the max size (more or less).

  type Sum struct {
    type SumChoice
    A Avariant
    B Bvariant
  }
This is what I meant by saying that it depends on exactly what you mean by "sum types".
Re:Go is not an easy language by BenFrantzDale on Monday February 22, 2021 @08:29AM (#26224243)
Got it. Although I’m not sure what “no overhead” means if the instances have to live way far away on the heap. That means you’ve got an alloc (including a mutex lock), then the value lives far away, then the garbage-collection overhead then the delete. When I think sum-type I think a flag plus storage for the maximum size and alignment type, and all of the above bookkeeping and cache misses go away.
Re:Go is not an easy language by simiones on Monday February 22, 2021 @09:33AM (#26224952)
Yes, you're right - I was thinking of "no space overhead", and even that is not entirely correct (you pay an extra pointer per variant size, which in this case would be some overhead over the embedded version).

Still, I think most people don't worry so much about efficient packing of sum types, and instead the safety and convenience factors are more important. Of course, YMMV based on exact usage.

I'm not in anyway claiming that Go supports sum types. Someone just asked how they may be emulated, and I don't think it should be surprising that emulation has an overhead.