From b3ee3e6a2d2fe492d3af9ff9d7f2e8e2d37521e5 Mon Sep 17 00:00:00 2001 From: Jordan Orelli Date: Mon, 29 Nov 2021 14:01:42 +0000 Subject: [PATCH] ohhhhh --- opt/opt.go | 46 +++++++++++++++++++++++++++++++++ opt/opt_test.go | 68 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 opt/opt.go create mode 100644 opt/opt_test.go diff --git a/opt/opt.go b/opt/opt.go new file mode 100644 index 0000000..5a94f76 --- /dev/null +++ b/opt/opt.go @@ -0,0 +1,46 @@ +package opt + +// Val is an optional value +type Val[T any] struct { + val T + ok bool +} + +// New creates an optional value +func New[T any](v T, ok bool) Val[T] { + if ok { + return Some(v) + } + return None[T]() +} + +// None creates an empty optional value +func None[T any]() Val[T] { return Val[T]{} } + +// NoneOf is the same as none, but it takes a parameter that it throws away. +// This allows the type T to be inferred. +func NoneOf[T any](v T) Val[T] { return Val[T]{} } + +// Some creates a filled optional value +func Some[T any](v T) Val[T] { + return Val[T]{ + val: v, + ok: true, + } +} + +// Open retrives the contents of our optional value +func (v Val[T]) Open() (T, bool) { + return v.val, v.ok +} + +// Bind takes a function that doesn't understand optionals and gives you +// another function that does +func Bind[X, Y any](f func(X) Y) func(Val[X]) Val[Y] { + return func(mx Val[X]) Val[Y] { + if x, ok := mx.Open(); ok { + return Some(f(x)) + } + return None[Y]() + } +} diff --git a/opt/opt_test.go b/opt/opt_test.go new file mode 100644 index 0000000..543412a --- /dev/null +++ b/opt/opt_test.go @@ -0,0 +1,68 @@ +package opt + +import ( + "testing" + "unicode/utf8" +) + +func TestNone(t *testing.T) { + s := None[string]() + if s.ok { + t.Error("should not be ok") + } + if _, ok := s.Open(); ok { + t.Error("should not be ok") + } + + s2 := NoneOf("poop") + if s2.ok { + t.Error("should not be ok") + } + if _, ok := s2.Open(); ok { + t.Error("should not be ok") + } +} + +func TestSome(t *testing.T) { + s := Some("poop") + if !s.ok { + t.Error("should be ok") + } + + v, ok := s.Open() + if !ok { + t.Fatal("should be ok") + } + + if v != "poop" { + t.Error("should be poop") + } +} + +func TestBind(t *testing.T) { + count := Bind(utf8.RuneCountInString) + + t.Run("bind some", func(t *testing.T) { + m := count(Some("poop")) + + n, ok := m.Open() + if !ok { + t.Fatal("should be ok") + } + if n != 4 { + t.Errorf("wanted 4 but got %d instead", n) + } + }) + + t.Run("bind non", func(t *testing.T) { + m := count(None[string]()) + + n, ok := m.Open() + if ok { + t.Fatal("should not be ok") + } + if n != 0 { + t.Errorf("wanted 0 but got %d instead", n) + } + }) +}