4 ms·
Actually I think while the compiler is a bit naive, the compiler writers are not. They are striving for simplicity, maintainability, speed, etc., vs trying to o
by kortex 5y ago
Actually I think while the compiler is a bit naive, the compiler writers are not. They are striving for simplicity, maintainability, speed, etc., vs trying to optimize high-level FP-esque idioms into fast machine code.
The compiler has definitely gotten more optimal over the years, but it's a different beastie than the rust one. Because go is not rust.
- lucian1900 5y agoGo is not Rust, but there is a lot it could learn from Rust without sacrifice: sum types, Result for error handling, iterators with for loop support, [edit] non-nullable types etc.
- sacado2 5y agoNot as simple as it seems. For instance, what is the zero value of a sum type? Let's say, the zero value of a Result<int, error>?
- kortex 5y agoPerfect example of a caveat to "without sacrifice". For context, go really cares about having "zero values" - every initialized variable of some type must have a zero value. The zero of Error is nil, so logically this would be Result(0, nil). But what do you set the internal state? It's not Ok, cause that int isn't used, and it's not Err either. Option would work fine though and it would be amazing to map over options instead of nil checks. I think you'd need to build Result on top of Option, it's naturally kind of ternary - "I have value" "I have error" "idk", either internally or externally. What might be awesome would be if the Error were a slice type, nil would be "uninitialized" and empty slice means "initialized". still not...pleasant.
- DylanSp 5y agoZero values are definitely an issue, and it'd be hard to get rid of them while sticking to Go's imperative/statement-based style. Still, it'd nice to use an Option type instead of pointers.
- Lev1a 5y agoIIRC in Rust it has to be either of the Ok or Err variants and the enclosed type can then provide its own "zero value" via the standard library "Default" [0] trait or your own implementation for the particular type. That trait is also implemented not for "Result" but for "Option" giving it a default value of "None". [0]: https://doc.rust-lang.org/std/default/trait.Default.html https://doc.rust-lang.org/std/default/trait.Default.html
- sacado2 5y agoYes, for Option the default value is quite obvious. I'm talking about Result, because it's the second best-known sum type, and it doesn't have an obvious default value.
- deleted 5y ago[deleted]
- Lev1a 5y agoBindings in Rust have to initialized with an explicit value if the first use comes before the first modification/assignment otherwise [0] you get an error message with error E0381 [1]. Even if that explicit assignment is just through the use of the type's default method [2], if available. Since it's implemented for Option<T> you can do it the same way [3] for that, but that's not available for Result<T,U> since the semantics are different from Option which is basically just Rust's alternative for 'null'/'nil' (but verifiable by the compiler). Thus you'd have to choose what variant of the enum you want to have at initialization, after which you can use the Default implementation of the enclosed type, if available (and so on, if there are more nested types). Though honestly I can't recall using the kind of pattern in Rust that would "need" something like that. Through the handling of code blocks as expressions, early returns etc. you for example don't need to initialize a variable before having an if-/match-/for-/while-/etc. block, you can simply use that block as an expression on the right side of the assignment [4]. [0]: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=50e8cbc37459e52eadad4384261a479e https://play.rust-lang.org/?version=stable&mode=debug&editio... [1]: https://doc.rust-lang.org/stable/error-index.html#E0381 https://doc.rust-lang.org/stable/error-index.html#E0381 [2]: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=938c7a8674d72f87f0817eaa31c88f93 https://play.rust-lang.org/?version=stable&mode=debug&editio... [3]: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=062ec5c467bba12fc76fa08a404a7adc https://play.rust-lang.org/?version=stable&mode=debug&editio... [4]: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=fab6733af1a9cd98dfd8afff1ea5c7df https://play.rust-lang.org/?version=stable&mode=debug&editio...
- lucian1900 5y agoThe zero value of a sum type can be the zero value of the first (or chosen) constituent. In this case, it could be Result<0>.
- sacado2 5y agoI'm not sure that would be any better, in practice, than doing return 0, nil.
- lucian1900 5y agoIt would allow functions to be treated generically as returning error-able types. Since some functions even return 3 arguments where the third is the error, this isn't possible today. It would also allow defer-like syntax to be used on the value a function returns, to return early from the calling function. Rust's ? is an excellent example of this, allowing explicit delegation of error handling that is still very lightweight syntactically.
- kortex 5y agoFunction composition becomes a lot easier with Result/Option, because you can write functions in terms of assuming a "good" value (not nil/err or otherwise "bad") and then map over it. Or if we really wanna be fancy, flatmap/bind over it. Foo, err requires either that all receiving functions accept (foo, err), or manually nil-checking at every stage. Try/catch is the least composable, because deeper error types can "jump out" from any layer. Which means you have to keep wrapping catches, until you hit some base layer you can recover from. You get stack traces, but you lose type information along the way. Sum (result) and product (foo, nil) types preserve type information. Meaning you can operate more generically over it. Meaning less code duplication.
- kortex 5y agoI've wanted a Result type I could map over in Go as soon as I saw the idea. But someone recently pointed out, without pattern matching and early return, it's kind of nerfed. Not sure I totally agree (I think it'd still be awesome to have Result, if only for .map()), but I see their point. I disagree that it would be without sacrifice. That's kind of the whole point. Unless I'm missing something, go has iterators with for loops.
- lucian1900 5y agoGo switches with exhaustion checks like in [1] would go a long way. An early return construct would be important as well, though, you're right. Go's for loops can only iterate over builtin types: slices, maps, channels, etc. You can't produce a custom type that is to be iterated over. 1. https://github.com/BurntSushi/go-sumtype https://github.com/BurntSushi/go-sumtype
- Sharlin 5y agoIt should be noted, though, that rustc does not really have any particular tricks up its sleeve to optimize functional patterns, or indeed much of anything. A lot of the LLVM IR that rustc generates is astonishingly naive and verbose – largely by design! rustc totally depends on LLVM's optimization passes to throw out all the boilerplate and output lean and mean optimized machine code.
- kortex 5y agoRight but that just means it's LLVM being clever, not rustc. The main compiler is the self-hosting gc go compiler. Iirc ~~it's the reference implementation~~ erm I guess it's determined by spec, not reference implemenation. Gc is definitely "the default" though. Then There's the gofrontend, which is a compiler frontend to other compilers, which can work with gcc and llvm. I am not aware of the state of the art for these, but I wonder if there are performance differences.
- Zababa 5y agoSpeed and maintainability vs trying to optimize high-level FP-esque idioms into fast machine code is a false dichotomy. OCaml has a really fast compiler, it has been maintained by a small team for 25 years, and it produces fast machine code. A big caveat in comparing OCaml and Go would be multicore support, but it's coming in OCaml, without breaking existing programs and with only a few percent of performance lost.