-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
A Maybe[T] is a container for an optional single value of arbirary type, which may be either present or absent.
- Loading branch information
1 parent
4894a3c
commit 3b59cc3
Showing
3 changed files
with
239 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package value_test | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/creachadair/mds/value" | ||
) | ||
|
||
var randomValues = []int{1, 6, 16, 19, 4} | ||
|
||
func ExampleMaybe() { | ||
even := make([]value.Maybe[int], 5) | ||
for i, r := range randomValues { | ||
if r%2 == 0 { | ||
even[i] = value.Just(r) | ||
} | ||
} | ||
|
||
var count int | ||
for _, v := range even { | ||
if v.Present() { | ||
count++ | ||
} | ||
} | ||
|
||
fmt.Println("input:", randomValues) | ||
fmt.Println("result:", even) | ||
fmt.Println("count:", count) | ||
// Output: | ||
// input: [1 6 16 19 4] | ||
// result: [Absent[int] 6 16 Absent[int] 4] | ||
// count: 3 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package value | ||
|
||
import "fmt" | ||
|
||
// Maybe is a container that can hold a value of type T. | ||
// Just(v) returns a Maybe holding the value v. | ||
// Absent() returns a Maybe that holds no value. | ||
// A zero Maybe is ready for use and is equivalent to Absent(). | ||
// | ||
// It is safe to copy and assign a Maybe value, but note that if a value is | ||
// present, only a shallow copy of the underlying value is made. Maybe values | ||
// are comparable if and only if T is comparable. | ||
type Maybe[T any] struct { | ||
value T | ||
present bool | ||
} | ||
|
||
// Just returns a Maybe holding the value v. | ||
func Just[T any](v T) Maybe[T] { return Maybe[T]{value: v, present: true} } | ||
|
||
// Absent returns a Maybe holding no value. | ||
// A zero Maybe is equivalent to Absent(). | ||
func Absent[T any]() Maybe[T] { return Maybe[T]{} } | ||
|
||
// Present reports whether m holds a value. | ||
func (m Maybe[T]) Present() bool { return m.present } | ||
|
||
// GetOK reports whether m holds a value, and if so returns that value. | ||
// If m is empty, GetOK returns the zero of T. | ||
func (m Maybe[T]) GetOK() (T, bool) { return m.value, m.present } | ||
|
||
// Get returns value held in m, if present; otherwise it returns the zero of T. | ||
func (m Maybe[T]) Get() T { return m.value } | ||
|
||
// Or returns m if m holds a value; otherwise it returns Just(o). | ||
func (m Maybe[T]) Or(o T) Maybe[T] { | ||
if m.present { | ||
return m | ||
} | ||
return Just(o) | ||
} | ||
|
||
// String returns the string representation of m. If m holds a value v, the | ||
// string representation of m is that of v. | ||
func (m Maybe[T]) String() string { | ||
if m.present { | ||
return fmt.Sprint(m.value) | ||
} | ||
return fmt.Sprintf("Absent[%T]", m.value) | ||
} | ||
|
||
// Check returns Just(v) if err == nil; otherwise it returns Absent(). | ||
func Check[T any](v T, err error) Maybe[T] { | ||
if err == nil { | ||
return Just(v) | ||
} | ||
return Maybe[T]{} | ||
} | ||
|
||
// MapMaybe returns a function from Maybe[T] to Maybe[U]. | ||
// If the argument is present and has value v, the result is present and has | ||
// value f(v). Otherwise, the result is absent and f is not called. | ||
func MapMaybe[T, U any](f func(T) U) func(Maybe[T]) Maybe[U] { | ||
return func(a Maybe[T]) Maybe[U] { | ||
if v, ok := a.GetOK(); ok { | ||
return Just(f(v)) | ||
} | ||
return Absent[U]() | ||
} | ||
} | ||
|
||
// First returns the element of vs that holds a value, if any exists; otherwise | ||
// it returns Absent(). | ||
func First[T any](vs ...Maybe[T]) Maybe[T] { | ||
for _, v := range vs { | ||
if v.Present() { | ||
return v | ||
} | ||
} | ||
return Maybe[T]{} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
package value_test | ||
|
||
import ( | ||
"strconv" | ||
"testing" | ||
|
||
"github.com/creachadair/mds/value" | ||
) | ||
|
||
func TestMaybe(t *testing.T) { | ||
t.Run("Zero", func(t *testing.T) { | ||
var v value.Maybe[int] | ||
if v.Present() { | ||
t.Error("Zero maybe should not be present") | ||
} | ||
if got := v.Get(); got != 0 { | ||
t.Errorf("Get: got %d, want 0", got) | ||
} | ||
}) | ||
|
||
t.Run("Present", func(t *testing.T) { | ||
v := value.Just("apple") | ||
if got, ok := v.GetOK(); !ok || got != "apple" { | ||
t.Errorf("GetOK: got %q, %v; want apple, true", got, ok) | ||
} | ||
if got := v.Get(); got != "apple" { | ||
t.Errorf("Get: got %q, want apple", got) | ||
} | ||
if !v.Present() { | ||
t.Error("Value should be present") | ||
} | ||
}) | ||
|
||
t.Run("Or", func(t *testing.T) { | ||
v := value.Just("pear") | ||
absent := value.Absent[string]() | ||
tests := []struct { | ||
lhs value.Maybe[string] | ||
rhs, want string | ||
}{ | ||
{absent, "", ""}, | ||
{v, "", "pear"}, | ||
{absent, "plum", "plum"}, | ||
{v, "plum", "pear"}, | ||
} | ||
for _, tc := range tests { | ||
if got := tc.lhs.Or(tc.rhs); got != value.Just(tc.want) { | ||
t.Errorf("%v.Or(%v): got %v, want %v", tc.lhs, tc.rhs, got, tc.want) | ||
} | ||
} | ||
}) | ||
|
||
t.Run("String", func(t *testing.T) { | ||
v := value.Just("pear") | ||
if got := v.String(); got != "pear" { | ||
t.Errorf("String: got %q, want pear", got) | ||
} | ||
|
||
var w value.Maybe[string] | ||
if got, want := w.String(), "Absent[string]"; got != want { | ||
t.Errorf("String: got %q, want %q", got, want) | ||
} | ||
}) | ||
} | ||
|
||
func TestCheck(t *testing.T) { | ||
t.Run("OK", func(t *testing.T) { | ||
got := value.Check(strconv.Atoi("1")) | ||
if want := value.Just(1); got != want { | ||
t.Errorf("Check(1): got %v, want %v", got, want) | ||
} | ||
}) | ||
t.Run("Error", func(t *testing.T) { | ||
got := value.Check(strconv.Atoi("bogus")) | ||
if got.Present() { | ||
t.Errorf("Check(bogus): got %v, want absent", got) | ||
} | ||
}) | ||
} | ||
|
||
func TestMapMaybe(t *testing.T) { | ||
length := value.MapMaybe(func(s string) int { | ||
return len(s) | ||
}) | ||
|
||
tests := []struct { | ||
input value.Maybe[string] | ||
want value.Maybe[int] | ||
}{ | ||
{value.Just(""), value.Just(0)}, | ||
{value.Just("plum"), value.Just(4)}, | ||
{value.Absent[string](), value.Absent[int]()}, | ||
} | ||
for _, tc := range tests { | ||
if got := length(tc.input); got != tc.want { | ||
t.Errorf("Length %q: got %v, want %v", tc.input, got, tc.want) | ||
} | ||
} | ||
} | ||
|
||
func TestFirst(t *testing.T) { | ||
type tv = value.Maybe[int] | ||
var absent tv | ||
v1 := value.Just(1) | ||
v2 := value.Just(2) | ||
tests := []struct { | ||
input []tv | ||
want tv | ||
}{ | ||
{nil, absent}, | ||
{[]tv{}, absent}, | ||
{[]tv{absent}, absent}, | ||
{[]tv{absent, absent, absent}, absent}, | ||
|
||
{[]tv{v1}, v1}, | ||
{[]tv{absent, v1}, v1}, | ||
{[]tv{absent, v1, v2}, v1}, | ||
{[]tv{absent, absent, v1, absent, v2}, v1}, | ||
} | ||
for _, tc := range tests { | ||
if got := value.First(tc.input...); got != tc.want { | ||
t.Errorf("First %+v: got %v, want %v", tc.input, got, tc.want) | ||
} | ||
} | ||
} |