From a7da3052366fb76d2aa7d820b8a968f5cf2ecd96 Mon Sep 17 00:00:00 2001 From: Force4760 Date: Mon, 8 Aug 2022 10:45:56 +0100 Subject: [PATCH 1/9] Implement Option constructor from pointer --- option/option.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/option/option.go b/option/option.go index 6464a32..e26bf75 100644 --- a/option/option.go +++ b/option/option.go @@ -20,6 +20,15 @@ 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 From 40ee94b8466a43b0bd8d2c12b7359f387c4f7607 Mon Sep 17 00:00:00 2001 From: Force4760 Date: Mon, 8 Aug 2022 10:48:16 +0100 Subject: [PATCH 2/9] Implement Option Filter --- function.go | 3 +++ option/option.go | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) 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 e26bf75..954beb1 100644 --- a/option/option.go +++ b/option/option.go @@ -86,3 +86,20 @@ 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 + } +} From 2090f448b1f8a105acf9de3c8ae3ec2127ddb5a0 Mon Sep 17 00:00:00 2001 From: Force4760 Date: Mon, 8 Aug 2022 11:03:31 +0100 Subject: [PATCH 3/9] Test Filter and FromPtr --- option/option_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/option/option_test.go b/option/option_test.go index 8acd90e..0ba3f4e 100644 --- a/option/option_test.go +++ b/option/option_test.go @@ -18,6 +18,24 @@ 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 TestFromPtrNil(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 { @@ -98,3 +116,27 @@ 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) + } +} From ce6da1fefa1360e59e184baa1da3256547a333f8 Mon Sep 17 00:00:00 2001 From: Force4760 Date: Mon, 8 Aug 2022 11:06:51 +0100 Subject: [PATCH 4/9] Implement and test Flat --- option/option.go | 9 +++++++++ option/option_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/option/option.go b/option/option.go index 954beb1..6736dca 100644 --- a/option/option.go +++ b/option/option.go @@ -103,3 +103,12 @@ func Filter[T any](fn fp.Pred[T]) func(o Option[T]) Option[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 0ba3f4e..337e172 100644 --- a/option/option_test.go +++ b/option/option_test.go @@ -140,3 +140,27 @@ func TestFilter_None(t *testing.T) { 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) + } +} From 083653c15e138b84f141ebae17997e4d61835430 Mon Sep 17 00:00:00 2001 From: Force4760 Date: Mon, 8 Aug 2022 11:19:58 +0100 Subject: [PATCH 5/9] Implement and test ToPtr --- option/option.go | 9 +++++++++ option/option_test.go | 14 ++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/option/option.go b/option/option.go index 6736dca..1ffa85c 100644 --- a/option/option.go +++ b/option/option.go @@ -51,6 +51,15 @@ func GetOrElse[T any](onNone fp.Lazy[T]) func(Option[T]) T { } } +// 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 { diff --git a/option/option_test.go b/option/option_test.go index 337e172..f735478 100644 --- a/option/option_test.go +++ b/option/option_test.go @@ -76,6 +76,20 @@ 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" { From 527dbb0eda10870afdeb0f6e35f606f624f9eb18 Mon Sep 17 00:00:00 2001 From: Force4760 Date: Mon, 8 Aug 2022 11:27:16 +0100 Subject: [PATCH 6/9] Implement and Test IsSomeAnd --- option/option.go | 7 +++++++ option/option_test.go | 30 ++++++++++++++++++++---------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/option/option.go b/option/option.go index 1ffa85c..1029a25 100644 --- a/option/option.go +++ b/option/option.go @@ -34,6 +34,13 @@ 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 diff --git a/option/option_test.go b/option/option_test.go index f735478..c52a11d 100644 --- a/option/option_test.go +++ b/option/option_test.go @@ -27,8 +27,7 @@ func TestFromPtr(t *testing.T) { t.Error("FromPtr should return a struct with hasValue set to true. Received:", res.hasValue) } } - -func TestFromPtrNil(t *testing.T) { +func TestFromPtr_Nil(t *testing.T) { res := FromPtr[int](nil) if res.hasValue != false { @@ -49,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 { @@ -68,7 +86,6 @@ func TestGetOrElse_Some(t *testing.T) { 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" { @@ -82,7 +99,6 @@ func TestToPtr_Some(t *testing.T) { 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 { @@ -96,7 +112,6 @@ func TestMatch_onSome(t *testing.T) { 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" { @@ -110,7 +125,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 { @@ -140,14 +154,12 @@ func TestFilter_Some_True(t *testing.T) { 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 { @@ -164,14 +176,12 @@ func TestFlat_Some_Some(t *testing.T) { 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 { From e4b9416f8a46fba0cb812df7c2676227c231e277 Mon Sep 17 00:00:00 2001 From: Force4760 Date: Mon, 8 Aug 2022 11:35:09 +0100 Subject: [PATCH 7/9] Add simple documentation --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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`. From 82cffd4c8a44d20b3d6676d6aa6310b08e30c190 Mon Sep 17 00:00:00 2001 From: Force4760 Date: Fri, 19 Aug 2022 00:29:44 +0100 Subject: [PATCH 8/9] Change the Match and Map type signatures --- option/option.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/option/option.go b/option/option.go index 1029a25..940a6c9 100644 --- a/option/option.go +++ b/option/option.go @@ -68,8 +68,8 @@ func ToPtr[T any](option Option[T]) *T { } // 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() @@ -80,11 +80,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)) From bbf152d0441ea8a95be31e23472cd1fc2209b3b1 Mon Sep 17 00:00:00 2001 From: Force4760 Date: Sat, 20 Aug 2022 14:56:05 +0100 Subject: [PATCH 9/9] Fix map and match types and implement Get --- option/option.go | 10 ++++++++++ option/option_test.go | 16 ++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/option/option.go b/option/option.go index 940a6c9..b438f59 100644 --- a/option/option.go +++ b/option/option.go @@ -58,6 +58,16 @@ 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) { diff --git a/option/option_test.go b/option/option_test.go index c52a11d..a391444 100644 --- a/option/option_test.go +++ b/option/option_test.go @@ -80,6 +80,22 @@ 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" {