5 ms·
In celebration of generics release, I was playing around with supporting Optionals via generics. If anyone's interested I am happy to make this a real project
by frenchie4111 5y ago
In celebration of generics release, I was playing around with supporting Optionals via generics. If anyone's interested I am happy to make this a real project
https://github.com/frenchie4111/go-generic-optional https://github.com/frenchie4111/go-generic-optional
- Mawr 5y agotype Optional[T any] struct { value *T } Don't use a pointer - it's bad for performance. func MakeOptional[T any](value *T) Optional[T] { Use `New` instead - it's the idiomatic name for a constructor in Go. Drop the `Optional` part - the module name suffices - the caller will see: `opt.New`. Personally I just call the module `optional`, I think it's clearer: `optional.New`. func (o* Optional[T]) Unwrap() (T, error) { 1. `Unwrap` reminds me of Rust's .Unwrap, which panics. Seems confusing. 2. There's no error here, the situation is equivalent to a missing key in a map - it's enough to return a bool. Here's my implementation so far: https://gist.github.com/Mawr-BF2/0a60da26f66b82ee87b98b03336e1f84 https://gist.github.com/Mawr-BF2/0a60da26f66b82ee87b98b03336.... Only thing I'm not sure about is whether the JSON serialization methods are necessary.
- wtfishackernews 5y agoFor json marshalling, I would defer to the underlying type's marshalling if it's present, and return `null` otherwise.
- shhsshs 5y agoI think this is appropriate in some cases but not others. For example how does the JSON value distinguish between `Some(null)` and `None`?
- dialogbox 5y agoIMO using Optional type means the inner value must be not null. And if it's Some(null), it should mean exactly same as None.
- uryga 5y agodistinguishing `Some(null)` and `None` is often considered a feature of Optional ;) to use a tired example: when getting a value out of a map via some `myMap.get(key)`, you may want to distinguish "not present" = `None` and "present, with value null" = `Some(null)` the right solution is to just not have nulls in the first place, then there's no problem ;)
- alophawen 5y ago(Assuming we are discussing the Option type from rust) Some(null) is not a valid result. The whole point of the Option type is to let you know: a) we got a result: Some(value) b) there is no result: None
- andrewjf 5y agoIt's trivial to store a null pointer in an Option::Some(). struct Foo; fn main() { let option_with_null: Option<*const Foo> = Some(std::ptr::null()); dbg!(option_with_null); dbg!(option_with_null.is_none()); } Output: [src/main.rs:6] option_with_null = Some( 0x0000000000000000, ) [src/main.rs:7] option_with_null.is_none() = false https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=6b87832bf48f4115ae44504e497287c4 https://play.rust-lang.org/?version=stable&mode=debug&editio...
- frenchie4111 5y agoI love it. I will update the library to match some of your suggestions. I agree with peer comment that we should likely defer to the original type for JSON serialization.
- morelisp 5y agoWhether a pointer is bad for performance in this case seems hard to tell a priori. There are also performance advantages if Optional packs nicely.
- frenchie4111 5y agoAgreed on performance being in the air. Tbh, I like the non-pointer option because using a pointer was causing the implementation to feel a bit weird. This cleans up a bit of the handling that was annoying (for example in the old implementation Make(&"something") didn't work because you can't reference a constant without first making a variable.
- Mawr 5y agoIt is just an assumption on my part, I figure that the cost of pointer chasing is generally going to outweigh any disadvantages. It can also help enable optimizations, for example slices and maps that do not contain pointers do not get scanned by the GC ([1]). [1]: https://github.com/golang/go/commit/85e7bee19f9f26dfca414b1e9054e429c448b14f https://github.com/golang/go/commit/85e7bee19f9f26dfca414b1e...
- stingraycharles 5y agoI fully agree with your approach. The important thing is that the user now has the choice to use a pointer or not: they can always use a pointer to the optional if they want to use pointers. Pointers to pointers are generally something you try to avoid if possible.
- morelisp 5y ago> the user now has the choice to use a pointer or not But any benefits of the packing are gone whether I make a pointer or not. And either way, no nested pointers are involved. We’re not taking pointers to options in either case.
- the-smug-one 5y ago> Don't use a pointer - it's bad for performance. That means you must have Optional[*sync.Mutex]. Then what's the point?
- kesslern 5y ago> Use `New` instead - it's the idiomatic name for a constructor in Go. Where do you get this from? I've tried unsuccessfully to find such naming/design guidelines.
- dbaggerman 5y agoIt's noted in the Effective Go guide: https://go.dev/doc/effective_go#package-names https://go.dev/doc/effective_go#package-names
- baby 5y agoWondering how a result type would be implemented
- daptaq 5y agoMy main fear with the introduction of generics was the lack of stdlib support. I know they want to play it safe and are planning to change this in future versions, but the last thing I want is that I have to download some github.com/foobar/... library for every common sense generic type I might want to use.
- heavyset_go 5y agoAgree with this sentiment. It was a very good idea to bring the `typing` module into the Python standard library versus just relying on Mypy.
- pharmakom 5y agoOptionals without do notation will be a bad time.
- LAC-Tech 5y agoWhat's do notation? I've used optionals in many languages, and this is the first I've heard of it. What else do you really need except map and... flatMap (aka `then` aka `and_then` aka `>>=` aka `bind`).
- uryga 5y agohttps://en.m.wikibooks.org/wiki/Haskell/do_notation https://en.m.wikibooks.org/wiki/Haskell/do_notation rough analogy: `await` replaces `.then()`, do-notation replaces `.flatMap()`
- int_19h 5y agoC# has had Nullable<T> for many years before it got stuff like ?. and ?? that mostly covers this, and it was, well, tolerable.
- BoorishBears 5y agoErgonomics sucked and a lot of people didn't use them (or even know they existed!) because ergonomics sucked
- int_19h 5y agoFrom my work experience at the time, it was used pretty heavily in new codebases.
- BoorishBears 5y agoI mean the fact that they've been around since 2.0 but you saw it used heavily in new codebases says a lot doesn't it?
- mountainriver 5y agoAwesome work! I would love to see something like this make it into the language
- assbuttbuttass 5y agoThis looks kind of cool, but I think there's no version of this as a library that could be considered idiomatic(TM), after all "a little copying is better than a little dependency" https://go-proverbs.github.io/ https://go-proverbs.github.io/
- svnpenn 5y agoWhy can't you just do something like this: func hello() (string, bool)
- Mawr 5y agoYou can, and in fact, that's how idiomatic Go's handled this up until now. But it only works in that specific case, and even then there's a small issue with readability - if you're not already familiar with this pattern, it's not immediately obvious that the string is strongly coupled with the bool. An Optional is very explicit about that. The problem becomes clearer once you venture out of this exact case. How would you embed this pattern in a struct? Like so? type A struct { value string valueOK bool } Or maybe with a pointer? type B struct { value *string } This option is the most common one in Go code today. You can perhaps see the issues already - these are awkward, neither properly communicates that what we want is optionality. But the real issue is that now that these are in a struct, nothing prevents us from accessing the value without checking if it's valid first: a := A{value: "", valueOk: false} // oops fmt.Println(a.value) a := B{value: nil} // oops fmt.Println(a.value) This wasn't an issue in your example - if we attempt to only access the value, and omit assigning the bool, the code will not compile: func hello() (string, bool) { return "hi", false } func main() { value := hello() // ^^^ compiler error: assignment mismatch: 1 variable but hello returns 2 values ([1]) fmt.Println(value) } An Optional type fixes this by not allowing you to access the value directly, but to instead have to go through a function like the one above. [1]: https://play.golang.com/p/cJcTD0WWSPO https://play.golang.com/p/cJcTD0WWSPO