Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

That wouldn't explain C, then, which does not have sum types either.

All three languages do have enums (as it is normally defined), though. Go is only the odd one out by using a different keyword. As these programs were told to be written as carbon copies of each other, not to the idioms of each language, it is likely the author didn't take time to understand what features are available. No enum keyword was assumed to mean it doesn't exist at all, I guess.





C has numeric enums and tagged unions, which are sum types without any compile time safety. That’s idiomatic C.

Go doesn’t have any equivalent. How do you do stuff like this in Go, at all?

I’ve been programming for 30+ years. Long enough to know direct translations between languages are rarely beautiful. But I’m not an expert in Go. Maybe there’s some tricks I’m missing?

Here’s the problem, if you want to have a stab at it. The code in question defines a text editing operation as a list of editing components: Insert, Delete and Skip. When applying an editing operation, we start at the start of the document. Skip moves the cursor forward by some specified length. Insert inserts at the current position and delete deletes some number of characters at the position.

Eg:

    enum OpComponent {
        Skip(int),
        Insert(String),
        Delete(int),
    }

    type Op = List<OpComponent>
Then there’s a whole bunch of functions with use operations - eg to apply them to a document, to compose them together and to do operational transform.

How would you model this in Go?


> C has numeric enums and tagged unions

C has unions, but they're not tagged. You can roll your own tagged unions, of course, but that's moving beyond it being a feature of the language.

> How would you model this in Go?

I'm committing the same earlier sin by trying to model it from the solution instead of the problem, so the actual best approach might be totally different, but at least in staying somewhat true to your code:

    type OpComponent interface { op() }
    type Op = []OpComponent

    type Skip struct { Value int }
    func (s Skip) op() {}
    type Insert struct { Value string }
    func (i Insert) op() {}
    type Delete struct { Value int }
    func (d Delete) op() {}

    op := Op{
        Skip{Value: 5},
        Insert{Value: "hello"},
        Delete{Value: 3},
    }

> C has unions, but they're not tagged. You can roll your own tagged unions, of course, but that's moving beyond it being a feature of the language.

This feels like a distinction without a real difference. Hand-rolled tagged unions are how lots of problems are approached in real, professional C. And I think they're the right tool here.

> the actual best approach might be totally different, but at least in staying somewhat true to your code: (...)

Thanks for having a stab at it. This is more or less what I ended up with in Go. As I said, I ended up needing about 50% more lines to accomplish the same thing in Go using this approach compared to the equivalent Typescript, rust and swift.

If anyone is curious, here's my C implementation: https://github.com/ottypes/libot

Swift: https://github.com/josephg/libot-swift

Rust: https://github.com/josephg/textot.rs

Typescript: https://github.com/ottypes/text-unicode

I wish I'd kept my Go implementation. I never uploaded it to github because I was unhappy with it, and I accidentally lost it somewhere along the way.

> the actual best approach might be totally different

Maybe. But honestly I doubt it. I think I accidentally chose a problem which happens to be an ideal use case for sum types. You'd probably need a different problem to show Go or C# in their best light.

But ... sum types are really amazing. Once you start using them, everything feels like a sum type. Programming without them feels like programming with one of your hands tied behind your back.


> As I said, I ended up needing about 50% more lines to accomplish the same thing in Go

I'd be using Perl if that bothered me. But there is folly in trying to model from a solution instead of the problem. For example, maybe all you needed was:

    type OpType int
    const (
        OpTypeSkip OpType = iota
        OpTypeInsert
        OpTypeDelete
    )

    type OpComponent struct {
        Type OpType
        Int int
        Str string
    }
Or something else entirely. Without fully understanding the exact problem, it is hard to say what the right direction is, even where the direction you chose in other language is the right one for that language. What is certain is that you don't want to write code in language X as if it were language Y. That doesn't work in programming languages, just as it does not work in natural languages. Every language has their own rules and idioms that don't transfer to another. A new language means you realistically have to restart finding the solution from scratch.

> You'd probably need a different problem to show Go or C# in their best light.

That said, my profession sees me involved in working on a set of libraries in various languages, including Go and Typescript, that appear to be an awful lot like your example. And I can say from that experience that the Go version is much more pleasant to work on. It just works.

I'll agree with you all day every day that the Typescript version's types are much more desirable to read. It absolutely does a better job at modelling the domain. No question about it. But you only need to read it once to understand the model. When you have to fight everything else beyond that continually it is of little consolation how beautiful the type definitions are.

You're right, though, it all depends on what you find most important. No two programmers are ever going to ever agree on what to prioritize. You want short code, whereas I don't care. Likewise, you probably don't care about the things I care about. Different opinions is the spice of life, I suppose!


Yes I think I mentioned in another comment that that would be another way to code it up. It’s ugly in a different way to the interface approach. I haven’t written enough go to know which is the least bad.

What are you “fighting all day” in typescript? That’s not my experience with TS at all.

What are the virtues of go, that you’re so enamoured by? If we give up beauty and type safety, what do you get in trade?


I don't become enamoured by language. I really don't care if I have to zig or zag. I'll happily work in every language under the sun. It is no more interesting than trying to determine if Milwaukee or Mikita make a better drill. Who cares? Maybe you have to press a different button, but they both do the same thing in the end. As far as I'm concerned, It's all just 1s and 0s at the end of the day.

However, I have found the Go variant of said project to be more pleasant because, as before, it just works. The full functionality of those libraries is fairly complex and it has had effectively no bugs. The Typescript version on the other hand... I am disenchanted by software that fails.

Yeah, you can blame the people who have worked on it. Absolutely. A perfect programmer can program bug-free code in every language. But for all the hand-wringing about how complex types are supposed to magically save you from making mistakes that keeps getting trumped around here, I shared it as a fun anecdote to the opposite — that, under real-world conditions where you are likely to encounter programers that aren't perfect, Go actually excelled in a space that seems to reflect your example.

But maybe it's not the greatest example to extol the virtues of a language. I don't know, but I am not going to start caring about one language over another anyway. I'm far more interested in producing great software. Which brand of drill was used to build that software matters not one bit to me. But to each their own. Different opinions is the spice of life, I suppose!




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

Search: