3 ms·
> it was annoying to program (due to a lack of enums) Typescript also lacks enums. Why wasn't it considered annoying? I mean, technically it does have an enum
by 9rx 10mo ago
> it was annoying to program (due to a lack of enums)
Typescript also lacks enums. Why wasn't it considered annoying?
I mean, technically it does have an enum keyword that offers what most would consider to be enums, but that keyword behaves exactly the same as what Go offers, which you don't consider to be enums.
- hombre_fatal 10mo agoThey presumably mean tagged unions like `User = Guest | LoggedIn(id, username)`.
- 9rx 10mo agoThat 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.
- josephg 10mo agoC 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?
- 9rx 10mo ago> 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}, }
- josephg 10mo ago> 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 https://github.com/ottypes/libot Swift: https://github.com/josephg/libot-swift https://github.com/josephg/libot-swift Rust: https://github.com/josephg/textot.rs https://github.com/josephg/textot.rs Typescript: https://github.com/ottypes/text-unicode 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.
- josephg 10mo agoIn typescript I typed my text editing operations like this: type Operation = {type: “insert”, …} | {type: “delete”, …} | …; It’s trivial to switch based on the type field. And when you do, typescript gives you full type checking for that specific variant. It’s not as efficient at runtime as C, but it’s very clean code. Go doesn’t have any equivalent to this. Nor does go support tagged unions - which is what I used in C. The most idiomatic approach I could think of in Go was to use interface {} and polymorphism. But that was more verbose (~50% more lines of code) and more error prone. And it’s much harder to read - instead of simply branching based on the operation type, I implemented a virtual method for all my different variants and called it. But that spread my logic all over the place. If I did it again I’d consider just making a struct in go with the superset of all the fields across all my variants. Still ugly, but maybe it would be better than dynamic dispatch? I dunno. I wish I still had the go code I wrote. The C, rust, swift and typescript variants are kicking around on my github somewhere. If you want a poke at the code, I can find them when I’m at my desk.