Skip to content

Option #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
3 changes: 3 additions & 0 deletions function.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
71 changes: 66 additions & 5 deletions option/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand All @@ -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))
Expand All @@ -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
}
112 changes: 109 additions & 3 deletions option/option_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -44,27 +80,54 @@ 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" {
t.Error("GetOrElse should return the onNone() value. Received:", res)
}
}

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" {
Expand All @@ -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 {
Expand All @@ -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)
}
}