diff --git a/README.md b/README.md index 6470cf7..ae7c434 100644 --- a/README.md +++ b/README.md @@ -237,9 +237,9 @@ curryedSum(1)(2) ## Structs -#### Option +### Option Option represents encapsulation of an optional value, it might be used as the return type of functions which may or may not return a meaningful value when they are applied. You could instanciate an `opt.Option[T]` with a value with `opt.Some(val)`. If the value is missing you can use `opt.None[T]()`. -Option exports `Some`, `None`, `IsSome`, `IsNone`, `GetOrElse`, `Match`, `Map`, `Chain`. +Option exports:`Some`, `None`, `IsSome`, `IsSomeAnd`, `IsNone`, `FromPtr`, `ToPtr`, `GetOrElse`, `Match`, `Map`, `Chain`, `Filter`, `Flat`. diff --git a/function.go b/function.go index 946c135..ff4c316 100644 --- a/function.go +++ b/function.go @@ -5,3 +5,6 @@ type Lazy[T any] func() T // Callback function that takes an argument and return a value of the same type type LazyVal[T any] func(x T) T + +// Callback function that takes an argument of a specific type and returns a boolean +type Pred[T any] func(T) bool diff --git a/option/option.go b/option/option.go index 6464a32..b438f59 100644 --- a/option/option.go +++ b/option/option.go @@ -20,11 +20,27 @@ func None[T any]() Option[T] { return Option[T]{} } +// Constructor for Option from a pointer. nil pointer == None, otherwise Some +func FromPtr[T any](ptr *T) Option[T] { + if ptr == nil { + return None[T]() + } + + return Some(*ptr) +} + // Helper to check if the Option has a value func IsSome[T any](option Option[T]) bool { return option.hasValue } +// Helper to check if the Option has a value and if that value satisfies a predicate +func IsSomeAnd[T any](pred fp.Pred[T]) func(Option[T]) bool { + return func(option Option[T]) bool { + return option.hasValue && pred(option.value) + } +} + // Helper to check if the Option is missing the value func IsNone[T any](option Option[T]) bool { return !option.hasValue @@ -42,9 +58,28 @@ func GetOrElse[T any](onNone fp.Lazy[T]) func(Option[T]) T { } } +// Extracts the value out of the Option, if it exists. Otherwise panics +func Get[T any](option Option[T]) T { + if IsNone(option) { + panic("Can't extract a value out of None") + } + + return option.value + +} + +// Extracts the value out of the Option as a pointer, if it exists. Otherwise returns a nil pointer +func ToPtr[T any](option Option[T]) *T { + if IsNone(option) { + return nil + } + + return &option.value +} + // Extracts the value out of the Option, if it exists, with a function. Otherwise returns the function with a default value -func Match[T any](onNone fp.Lazy[T], onSome fp.LazyVal[T]) func(Option[T]) T { - return func(option Option[T]) T { +func Match[T, R any](onNone func() R, onSome func(T) R) func(Option[T]) R { + return func(option Option[T]) R { if IsNone(option) { return onNone() @@ -55,11 +90,11 @@ func Match[T any](onNone fp.Lazy[T], onSome fp.LazyVal[T]) func(Option[T]) T { } // Execute the function on the Option value if it exists. Otherwise return the empty Option itself -func Map[T any](fn fp.LazyVal[T]) func(o Option[T]) Option[T] { - return func(option Option[T]) Option[T] { +func Map[T, R any](fn func(T) R) func(o Option[T]) Option[R] { + return func(option Option[T]) Option[R] { if IsNone(option) { - return None[T]() + return None[R]() } return Some(fn(option.value)) @@ -77,3 +112,29 @@ func Chain[A any, B any](fn func(a A) Option[B]) func(Option[A]) Option[B] { return fn(a.value) } } + +// Execute a predicate on the Option value if it exists. +// If the result is false or the Option is empty, return the empty Option. +// Otherwise, return the option itself +func Filter[T any](fn fp.Pred[T]) func(o Option[T]) Option[T] { + return func(option Option[T]) Option[T] { + if IsNone(option) { + return None[T]() + } + + if !fn(option.value) { + return None[T]() + } + + return option + } +} + +// Removes one level of nesting at a time. Option[Option[T]] -> Option[T] +func Flat[T any](option Option[Option[T]]) Option[T] { + if IsNone(option) { + return None[T]() + } + + return option.value +} diff --git a/option/option_test.go b/option/option_test.go index 8acd90e..a391444 100644 --- a/option/option_test.go +++ b/option/option_test.go @@ -18,6 +18,23 @@ func TestNone(t *testing.T) { } } +func TestFromPtr(t *testing.T) { + i := 1 + + res := FromPtr(&i) + + if res.hasValue != true { + t.Error("FromPtr should return a struct with hasValue set to true. Received:", res.hasValue) + } +} +func TestFromPtr_Nil(t *testing.T) { + res := FromPtr[int](nil) + + if res.hasValue != false { + t.Error("FromPtr should return a struct with hasValue set to false. Received:", res.hasValue) + } +} + func TestIsSome_Some(t *testing.T) { res := IsSome(Some("value")) if res != true { @@ -31,6 +48,25 @@ func TestIsSome_None(t *testing.T) { } } +func TestIsSomeAnd_Some_True(t *testing.T) { + res := IsSomeAnd(func(x int) bool { return x > 10 })(Some(42)) + if res != true { + t.Error("IsSomeAnd should return true. Received:", res) + } +} +func TestIsSomeAnd_Some_False(t *testing.T) { + res := IsSomeAnd(func(x int) bool { return x < 10 })(Some(42)) + if res != false { + t.Error("IsSomeAnd should return false. Received:", res) + } +} +func TestIsSomeAnd_None(t *testing.T) { + res := IsSomeAnd(func(x int) bool { return x < 10 })(None[int]()) + if res != false { + t.Error("IsSomeAnd should return false. Received:", res) + } +} + func TestIsNone_Some(t *testing.T) { res := IsNone(None[string]()) if res != true { @@ -44,13 +80,28 @@ func TestIsNone_None(t *testing.T) { } } +func TestGet_Some(t *testing.T) { + res := Get(Some("val")) + if res != "val" { + t.Error("Get should return the Some value. Received:", res) + } +} +func TestGet_None(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("Get should have raised a panic.") + } + }() + + _ = Get(None[int]()) +} + func TestGetOrElse_Some(t *testing.T) { res := GetOrElse(func() string { return "fail" })(Some("val")) if res != "val" { t.Error("GetOrElse should return the Some value. Received:", res) } } - func TestGetOrElse_None(t *testing.T) { res := GetOrElse(func() string { return "elseValue" })(None[string]()) if res != "elseValue" { @@ -58,13 +109,25 @@ func TestGetOrElse_None(t *testing.T) { } } +func TestToPtr_Some(t *testing.T) { + res := ToPtr(Some("val")) + if *res != "val" { + t.Error("ToPtr should return a pointer to the Some value. Received:", res) + } +} +func TestToPtr_None(t *testing.T) { + res := ToPtr(None[string]()) + if res != nil { + t.Error("ToPtr should return a nil pointer. Received:", res) + } +} + func TestMatch_onSome(t *testing.T) { res := Match(func() string { return "onNone" }, func(x string) string { return x + x })(Some("val")) if res != "valval" { t.Error("Match should return the onSome() value. Received:", res) } } - func TestMatch_onNone(t *testing.T) { res := Match(func() string { return "onNone" }, func(x string) string { return x + x })(None[string]()) if res != "onNone" { @@ -78,7 +141,6 @@ func TestMap_Some(t *testing.T) { t.Error("Map should return the result of the callback function. Received:", res.value) } } - func TestMap_None(t *testing.T) { res := Map(func(x string) string { return x + x })(None[string]()) if res.hasValue != false { @@ -98,3 +160,47 @@ func TestChain_None(t *testing.T) { t.Error("Chain should return a None value. Received:", res.value) } } + +func TestFilter_Some_True(t *testing.T) { + res := Filter(func(x int) bool { return x > 10 })(Some(42)) + if res.hasValue != true { + t.Error("Filter should return a struct with hasValue set to true. Received:", res.value) + } + if res.value != 42 { + t.Error("Filter should return a struct with the same value as the original (42). Received:", res.value) + } +} +func TestFilter_Some_False(t *testing.T) { + res := Filter(func(x int) bool { return x < 10 })(Some(42)) + if res.hasValue != false { + t.Error("Filter should return a struct with hasValue set to false. Received:", res.value) + } +} +func TestFilter_None(t *testing.T) { + res := Filter(func(x int) bool { return x < 10 })(None[int]()) + if res.hasValue != false { + t.Error("Filter should return a struct with hasValue set to false. Received:", res.value) + } +} + +func TestFlat_Some_Some(t *testing.T) { + res := Flat(Some(Some(42))) + if res.hasValue != true { + t.Error("Flat should return a struct with hasValue set to true. Received:", res.value) + } + if res.value != 42 { + t.Error("Flat should return a struct with the same value as the original (42). Received:", res.value) + } +} +func TestFlat_Some_None(t *testing.T) { + res := Flat(Some(None[int]())) + if res.hasValue != false { + t.Error("Flat should return a struct with hasValue set to false. Received:", res.value) + } +} +func TestFlat_None(t *testing.T) { + res := Flat(None[Option[int]]()) + if res.hasValue != false { + t.Error("Flat should return a struct with hasValue set to false. Received:", res.value) + } +}