4 ms·
> You can kind of do that by passing a struct as an argument, and represent the optionalness by having a pointer in the struct. That's still awkward, you can't
by autarch 5y ago
> You can kind of do that by passing a struct as an argument, and represent the optionalness by having a pointer in the struct. That's still awkward, you can't easily make a pointer to an integer or string literal, you need a separate helper function for each primitive type. Overall it could be nicer.
I think using a builder is a better option in Go if you have more than 2-3 arguments.
> Yeah, I refer to it as Algebraic Data Types, where you can for example have your tree type to be a Node with children or a Leaf with value. And a function can accept a Tree that can be one of these. IIRC, the Go people claim that you can do kind of something like this with interfaces, but it's not very nice to use interfaces in this way and has some drawbacks.
Except you can't really do this with interfaces. If you need to get back to the concrete implementation of the interface, there's no way to know what all possible implementations might be. This is especially true since any type can implement an interface, not just the types you create initially. So someone else could implement `Tree` and you would not know to account for that in your function that accepts the `Tree` interface.
I don't think there's any substitute for proper sum types in Go.
- kubb 5y ago> I think using a builder is a better option in Go if you have more than 2-3 arguments. But then you need to write all the setters, and something that was supposed to be simple, a function, becomes a whole contraption with a bunch of auxiliary code. Personally I would avoid that, and I definitely wouldn't make it a rule of thumb to use builders in every function with more than 2-3 arguments (maybe you meant something else).
- autarch 5y ago> But then you need to write all the setters, and something that was supposed to be simple, a function, becomes a whole contraption with a bunch of auxiliary code. Personally I would avoid that, and I definitely wouldn't make it a rule of thumb to use builders in every function with more than 2-3 arguments (maybe you meant something else). Yeah, it's tedious, but that's Go for you ;) Rust has the same issue, but there are macros libraries that will create the builder for you based on the struct definition, so it's _very_ trivial to create these builders. You could do the same with codegen for Go, but this always feels much worse to me than using macros for a number of reasons like having to install a separate tool, checking in generated files, making people run `go generate` after some (but not all) changes, etc.
- kubb 5y ago> Except you can't really do this with interfaces Apparently they decided that it would be too confusing to have variant types alongside interfaces for some reason, and they claim that interfaces handle a lot of the use cases of variants: https://go.dev/doc/faq#variant_types https://go.dev/doc/faq#variant_types
- autarch 5y agoYeah, "for some reason" is a good summary of many Go decisions. Rust has both (traits are somewhat like interfaces) and I don't feel like it's too painful, though using the trait type in function signatures is a lot more involved than in Go, and I still don't fully understand all the nuances. But the complexity isn't because of any overlap between traits and sum types.
- throwaway894345 5y agoRust trait objects are surprisingly painful. I've reached for them a lot where I would normally use Go interfaces, but ended up avoiding them. I suspect this has more to do with Rust's memory management model--having sum types and interfaces would almost certainly work out perfectly in Go.
- preseinger 5y agoBuilders aren't a good pattern in Go, because it's difficult to express continuations, and they leave types in incomplete states. It's almost always better to use config structs.