Skip to content

Commit

Permalink
value: add a Maybe[T] type (#7)
Browse files Browse the repository at this point in the history
A Maybe[T] is a container for an optional single value of arbirary type, which
may be either present or absent.

There are several more interesting things we could do with this type, but for
now this is just the basics.
  • Loading branch information
creachadair authored Sep 3, 2024
1 parent 4894a3c commit 23409e4
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 0 deletions.
33 changes: 33 additions & 0 deletions value/example_test.go
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
}
58 changes: 58 additions & 0 deletions value/maybe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
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]{}
}
79 changes: 79 additions & 0 deletions value/maybe_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
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)
}
})
}

0 comments on commit 23409e4

Please sign in to comment.