From 5e74428ab61ce599b1e4b2ee2b9d4394315d7a60 Mon Sep 17 00:00:00 2001 From: moznion Date: Fri, 11 Nov 2022 16:23:36 -0800 Subject: [PATCH] Support two value factory methods: `FromNillable()` and `PtrFromNillable()` These methods accept the nillable pointer value as an argument and make the `Optional[T]` type value. `FromNillable()` ---- If the given value is not nil, this returns Some[T] value with doing value-dereference. On the other hand, if the value is nil, this returns None[T]. example: ```go num := 123 some := FromNillable[int](&num) fmt.Printf("%v\n", some.IsSome()) // => true fmt.Printf("%v\n", some.Unwrap()) // => 123 none := FromNillable[int](nil) fmt.Printf("%v\n", none.IsSome()) // => false fmt.Printf("%v\n", none.Unwrap()) // => 0 (the default value of int) ``` `PtrFromNillable()` ---- If the given value is not nil, this returns Some[*T] value **without** doing value-dereference. On the other hand, if the value is nil, this returns None[*T]. example: ```go num := 123 some := PtrFromNillable[int](&num) fmt.Printf("%v\n", some.IsSome()) // => true fmt.Printf("%v\n", *some.Unwrap()) // => 123 (NOTE: it needs doing dereference) none := PtrFromNillable[int](nil) fmt.Printf("%v\n", none.IsSome()) // => false fmt.Printf("%v\n", none.Unwrap()) // => nil ``` Signed-off-by: moznion --- README.md | 23 ++++++++++++++++++---- examples_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ option.go | 25 ++++++++++++++++++++++-- option_test.go | 20 +++++++++++++++++++ 4 files changed, 112 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2ac29d7d..b019419b 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,15 @@ and more detailed examples are here: [./examples_test.go](./examples_test.go). ### Supported Operations +#### Value Factory Methods + +- [Some[T]() Option[T]](https://pkg.go.dev/github.com/moznion/go-optional#Some) +- [None[T]() Option[T]](https://pkg.go.dev/github.com/moznion/go-optional#None) +- [FromNillable[T]() Option[T]](https://pkg.go.dev/github.com/moznion/go-optional#FromNillable) +- [PtrFromNillable[T]() Option[T]](https://pkg.go.dev/github.com/moznion/go-optional#PtrFromNillable) + +#### Option value handler methods + - [Option[T]#IsNone() bool](https://pkg.go.dev/github.com/moznion/go-optional#Option.IsNone) - [Option[T]#IsSome() bool](https://pkg.go.dev/github.com/moznion/go-optional#Option.IsSome) - [Option[T]#Unwrap() T](https://pkg.go.dev/github.com/moznion/go-optional#Option.Unwrap) @@ -69,6 +78,16 @@ and more detailed examples are here: [./examples_test.go](./examples_test.go). - [Option.Unzip[T, U any](zipped Option[Pair[T, U]]) (Option[T], Option[U])](https://pkg.go.dev/github.com/moznion/go-optional#Unzip) - [Option.UnzipWith[T, U, V any](zipped Option[V], unzipper func(zipped V) (T, U)) (Option[T], Option[U])](https://pkg.go.dev/github.com/moznion/go-optional#UnzipWith) +### nil == None[T] + +This library deals with `nil` as same as `None[T]`. So it works with like the following example: + +```go +var nilValue Option[int] = nil +fmt.Printf("%v\n", nilValue.IsNone()) // => true +fmt.Printf("%v\n", nilValue.IsSome()) // => false +``` + ### JSON marshal/unmarshal support This `Option[T]` type supports JSON marshal and unmarshal. @@ -147,10 +166,6 @@ if err != nil { fmt.Printf("%s\n", marshal) // => {} ``` -## Tips - -- it would be better to deal with an Option value as a non-pointer because if the Option value can accept nil it becomes worthless - ## Known Issues The runtime raises a compile error like "methods cannot have type parameters", so `Map()`, `MapOr()`, `MapWithError()`, `MapOrWithError()`, `Zip()`, `ZipWith()`, `Unzip()` and `UnzipWith()` have been providing as functions. Basically, it would be better to provide them as the methods, but currently, it compromises with the limitation. diff --git a/examples_test.go b/examples_test.go index 16e9481d..774fef70 100644 --- a/examples_test.go +++ b/examples_test.go @@ -10,9 +10,29 @@ func ExampleOption_IsNone() { fmt.Printf("%v\n", some.IsNone()) none := None[int]() fmt.Printf("%v\n", none.IsNone()) + + num := 123 + some = FromNillable[int](&num) + fmt.Printf("%v\n", some.IsNone()) + none = FromNillable[int](nil) + fmt.Printf("%v\n", none.IsNone()) + + ptrSome := PtrFromNillable[int](&num) + fmt.Printf("%v\n", ptrSome.IsNone()) + ptrNone := PtrFromNillable[int](nil) + fmt.Printf("%v\n", ptrNone.IsNone()) + + var nilValue Option[int] = nil + fmt.Printf("%v\n", nilValue.IsNone()) + // Output: // false // true + // false + // true + // false + // true + // true } func ExampleOption_IsSome() { @@ -20,19 +40,49 @@ func ExampleOption_IsSome() { fmt.Printf("%v\n", some.IsSome()) none := None[int]() fmt.Printf("%v\n", none.IsSome()) + + num := 123 + some = FromNillable[int](&num) + fmt.Printf("%v\n", some.IsSome()) + none = FromNillable[int](nil) + fmt.Printf("%v\n", none.IsSome()) + + ptrSome := PtrFromNillable[int](&num) + fmt.Printf("%v\n", ptrSome.IsSome()) + ptrNone := PtrFromNillable[int](nil) + fmt.Printf("%v\n", ptrNone.IsSome()) + + var nilValue Option[int] = nil + fmt.Printf("%v\n", nilValue.IsSome()) + // Output: // true // false + // true + // false + // true + // false + // false } func ExampleOption_Unwrap() { fmt.Printf("%v\n", Some[int](12345).Unwrap()) fmt.Printf("%v\n", None[int]().Unwrap()) fmt.Printf("%v\n", None[*int]().Unwrap()) + + num := 123 + fmt.Printf("%v\n", FromNillable[int](&num).Unwrap()) + fmt.Printf("%v\n", FromNillable[int](nil).Unwrap()) + fmt.Printf("%v\n", *PtrFromNillable[int](&num).Unwrap()) // NOTE: this dereferences tha unwrapped value + fmt.Printf("%v\n", PtrFromNillable[int](nil).Unwrap()) // Output: // 12345 // 0 // + // 123 + // 0 + // 123 + // } func ExampleOption_Take() { diff --git a/option.go b/option.go index 7d1c9795..9bad6325 100644 --- a/option.go +++ b/option.go @@ -18,18 +18,39 @@ const ( value = iota ) -// Some is a function to make an Option type instance with the actual value. +// Some is a function to make an Option type value with the actual value. func Some[T any](v T) Option[T] { return Option[T]{ value: v, } } -// None is a function to make an Option type that doesn't have a value. +// None is a function to make an Option type value that doesn't have a value. func None[T any]() Option[T] { return nil } +// FromNillable is a function to make an Option type value with the nillable value with value de-referencing. +// If the given value is not nil, this returns Some[T] value. On the other hand, if the value is nil, this returns None[T]. +// This function does "dereference" for the value on packing that into Option value. If this value is not preferable, please consider using PtrFromNillable() instead. +func FromNillable[T any](v *T) Option[T] { + if v == nil { + return None[T]() + } + return Some[T](*v) +} + +// PtrFromNillable is a function to make an Option type value with the nillable value without value de-referencing. +// If the given value is not nil, this returns Some[*T] value. On the other hand, if the value is nil, this returns None[*T]. +// This function doesn't "dereference" the value on packing that into the Option value; in other words, this puts the as-is pointer value into the Option envelope. +// This behavior contrasts with the FromNillable() function's one. +func PtrFromNillable[T any](v *T) Option[*T] { + if v == nil { + return None[*T]() + } + return Some[*T](v) +} + // IsNone returns whether the Option *doesn't* have a value or not. func (o Option[T]) IsNone() bool { return o == nil diff --git a/option_test.go b/option_test.go index 0a34826e..b9a61214 100644 --- a/option_test.go +++ b/option_test.go @@ -12,17 +12,37 @@ import ( func TestOption_IsNone(t *testing.T) { assert.True(t, None[int]().IsNone()) assert.False(t, Some[int](123).IsNone()) + + var nilValue Option[int] = nil + assert.True(t, nilValue.IsNone()) + + i := 0 + assert.False(t, FromNillable[int](&i).IsNone()) + assert.True(t, FromNillable[int](nil).IsNone()) } func TestOption_IsSome(t *testing.T) { assert.False(t, None[int]().IsSome()) assert.True(t, Some[int](123).IsSome()) + + var nilValue Option[int] = nil + assert.False(t, nilValue.IsSome()) + + i := 0 + assert.True(t, FromNillable[int](&i).IsSome()) + assert.False(t, FromNillable[int](nil).IsSome()) } func TestOption_Unwrap(t *testing.T) { assert.Equal(t, "foo", Some[string]("foo").Unwrap()) assert.Equal(t, "", None[string]().Unwrap()) assert.Nil(t, None[*string]().Unwrap()) + + i := 123 + assert.Equal(t, i, FromNillable[int](&i).Unwrap()) + assert.Equal(t, 0, FromNillable[int](nil).Unwrap()) + assert.Equal(t, i, *PtrFromNillable[int](&i).Unwrap()) + assert.Nil(t, PtrFromNillable[int](nil).Unwrap()) } func TestOption_Take(t *testing.T) {