Skip to content

Commit

Permalink
Merge pull request #10 from moznion/flatmap
Browse files Browse the repository at this point in the history
Support `FlatMap` functions:
  • Loading branch information
moznion authored Jun 13, 2022
2 parents 45dd457 + 3cf8296 commit 5364b97
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 1 deletion.
99 changes: 99 additions & 0 deletions examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ func ExampleMapOrWithError() {
mapped, err = MapOrWithError(some, "N/A", mapperWithError)
fmt.Printf("err is nil: %v\n", err == nil)
fmt.Printf("%s\n", mapped)

// Output:
// err is nil: true
// 1
Expand All @@ -401,3 +402,101 @@ func ExampleMapOrWithError() {
// err is nil: false
// <ignore-me>
}

func ExampleFlatMap() {
mapper := func(v int) Option[string] {
return Some[string](fmt.Sprintf("%d", v))
}

some := Some[int](1)
opt := FlatMap(some, mapper)
fmt.Printf("%s\n", opt.TakeOr("N/A"))

none := None[int]()
opt = FlatMap(none, mapper)
fmt.Printf("%s\n", opt.TakeOr("N/A"))

// Output:
// 1
// N/A
}

func ExampleFlatMapOr() {
mapper := func(v int) Option[string] {
return Some[string](fmt.Sprintf("%d", v))
}

some := Some[int](1)
mapped := FlatMapOr(some, "N/A", mapper)
fmt.Printf("%s\n", mapped)

none := None[int]()
mapped = FlatMapOr(none, "N/A", mapper)
fmt.Printf("%s\n", mapped)

// Output:
// 1
// N/A
}

func ExampleFlatMapWithError() {
mapperWithNoError := func(v int) (Option[string], error) {
return Some[string](fmt.Sprintf("%d", v)), nil
}

some := Some[int](1)
opt, err := FlatMapWithError(some, mapperWithNoError)
fmt.Printf("err is nil: %v\n", err == nil)
fmt.Printf("%s\n", opt.TakeOr("N/A"))

none := None[int]()
opt, err = FlatMapWithError(none, mapperWithNoError)
fmt.Printf("err is nil: %v\n", err == nil)
fmt.Printf("%s\n", opt.TakeOr("N/A"))

mapperWithError := func(v int) (Option[string], error) {
return Some[string](""), errors.New("something wrong")
}
opt, err = FlatMapWithError(some, mapperWithError)
fmt.Printf("err is nil: %v\n", err == nil)
fmt.Printf("%s\n", opt.TakeOr("N/A"))

// Output:
// err is nil: true
// 1
// err is nil: true
// N/A
// err is nil: false
// N/A
}

func ExampleFlatMapOrWithError() {
mapperWithNoError := func(v int) (Option[string], error) {
return Some[string](fmt.Sprintf("%d", v)), nil
}

some := Some[int](1)
mapped, err := FlatMapOrWithError(some, "N/A", mapperWithNoError)
fmt.Printf("err is nil: %v\n", err == nil)
fmt.Printf("%s\n", mapped)

none := None[int]()
mapped, err = FlatMapOrWithError(none, "N/A", mapperWithNoError)
fmt.Printf("err is nil: %v\n", err == nil)
fmt.Printf("%s\n", mapped)

mapperWithError := func(v int) (Option[string], error) {
return Some[string]("<ignore-me>"), errors.New("something wrong")
}
mapped, err = FlatMapOrWithError(some, "N/A", mapperWithError)
fmt.Printf("err is nil: %v\n", err == nil)
fmt.Printf("%s\n", mapped)

// Output:
// err is nil: true
// 1
// err is nil: true
// N/A
// err is nil: false
//
}
60 changes: 59 additions & 1 deletion option.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package optional

import "errors"
import (
"errors"
)

var (
// ErrNoneValueTaken represents the error that is raised when None value is taken.
Expand Down Expand Up @@ -163,6 +165,62 @@ func MapOrWithError[T, U any](option Option[T], fallbackValue U, mapper func(v T
return mapper(option.value)
}

// FlatMap converts give Option value to another Option value according to the mapper function.
// The difference from the Map is the mapper function returns an Option value instead of the bare value.
// If given Option value is None, this also returns None.
func FlatMap[T, U any](option Option[T], mapper func(v T) Option[U]) Option[U] {
if option.IsNone() {
return None[U]()
}

return mapper(option.value)
}

// FlatMapOr converts given Option value to another *actual* value according to the mapper function.
// The difference from the MapOr is the mapper function returns an Option value instead of the bare value.
// If given Option value is None or mapper function returns None, this returns fallbackValue.
func FlatMapOr[T, U any](option Option[T], fallbackValue U, mapper func(v T) Option[U]) U {
if option.IsNone() {
return fallbackValue
}

return (mapper(option.value)).TakeOr(fallbackValue)
}

// FlatMapWithError converts given Option value to another Option value according to the mapper function that has the ability to return the value with an error.
// The difference from the MapWithError is the mapper function returns an Option value instead of the bare value.
// If given Option value is None, this returns (None, nil). Else if the mapper returns an error then this returns (None, error).
// Unless of them, i.e. given Option value is Some and the mapper doesn't return the error, this returns (Some[U], nil).
func FlatMapWithError[T, U any](option Option[T], mapper func(v T) (Option[U], error)) (Option[U], error) {
if option.IsNone() {
return None[U](), nil
}

mapped, err := mapper(option.value)
if err != nil {
return None[U](), err
}
return mapped, nil
}

// FlatMapOrWithError converts given Option value to another *actual* value according to the mapper function that has the ability to return the value with an error.
// The difference from the MapOrWithError is the mapper function returns an Option value instead of the bare value.
// If given Option value is None, this returns (fallbackValue, nil). Else if the mapper returns an error then returns ($zero_value_of_type, error).
// Unless of them, i.e. given Option value is Some and the mapper doesn't return the error, this returns (U, nil).
func FlatMapOrWithError[T, U any](option Option[T], fallbackValue U, mapper func(v T) (Option[U], error)) (U, error) {
if option.IsNone() {
return fallbackValue, nil
}

maybe, err := mapper(option.value)
if err != nil {
var zeroValue U
return zeroValue, err
}

return maybe.TakeOr(fallbackValue), nil
}

// Pair is a data type that represents a tuple that has two elements.
type Pair[T, U any] struct {
Value1 T
Expand Down
78 changes: 78 additions & 0 deletions option_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,81 @@ func TestOption_IfNoneWithError(t *testing.T) {
})
assert.NoError(t, err)
}

func TestFlatMap(t *testing.T) {
some := Some[int](123)
mapped := FlatMap(some, func(v int) Option[string] {
return Some[string](fmt.Sprintf("%d", v))
})
taken, err := mapped.Take()
assert.NoError(t, err)
assert.Equal(t, "123", taken)

none := None[int]()
mapped = FlatMap(none, func(v int) Option[string] {
return Some[string](fmt.Sprintf("%d", v))
})
assert.True(t, mapped.IsNone())
}

func TestFlatMapOr(t *testing.T) {
some := Some[int](123)
mapped := FlatMapOr(some, "666", func(v int) Option[string] {
return Some[string](fmt.Sprintf("%d", v))
})
assert.Equal(t, "123", mapped)

none := None[int]()
mapped = FlatMapOr(none, "666", func(v int) Option[string] {
return Some[string](fmt.Sprintf("%d", v))
})
assert.Equal(t, "666", mapped)
}

func TestFlatMapWithError(t *testing.T) {
some := Some[int](123)
mapped, err := FlatMapWithError(some, func(v int) (Option[string], error) {
return Some[string](fmt.Sprintf("%d", v)), nil
})
assert.NoError(t, err)
taken, err := mapped.Take()
assert.NoError(t, err)
assert.Equal(t, "123", taken)

none := None[int]()
mapped, err = FlatMapWithError(none, func(v int) (Option[string], error) {
return Some[string](fmt.Sprintf("%d", v)), nil
})
assert.NoError(t, err)
assert.True(t, mapped.IsNone())

mapperError := errors.New("mapper error")
mapped, err = FlatMapWithError(some, func(v int) (Option[string], error) {
return Some[string](""), mapperError
})
assert.ErrorIs(t, err, mapperError)
assert.True(t, mapped.IsNone())
}

func TestFlatMapOrWithError(t *testing.T) {
some := Some[int](123)
mapped, err := FlatMapOrWithError(some, "666", func(v int) (Option[string], error) {
return Some[string](fmt.Sprintf("%d", v)), nil
})
assert.NoError(t, err)
assert.Equal(t, "123", mapped)

none := None[int]()
mapped, err = FlatMapOrWithError(none, "666", func(v int) (Option[string], error) {
return Some[string](fmt.Sprintf("%d", v)), nil
})
assert.NoError(t, err)
assert.Equal(t, "666", mapped)

mapperError := errors.New("mapper error")
mapped, err = FlatMapOrWithError(some, "666", func(v int) (Option[string], error) {
return Some[string](""), mapperError
})
assert.ErrorIs(t, err, mapperError)
assert.Equal(t, "", mapped)
}

0 comments on commit 5364b97

Please sign in to comment.