1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-08-10 22:31:32 +02:00

fix: add json serialization support for option

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
This commit is contained in:
Dr. Carsten Leue
2023-07-09 10:21:56 +02:00
parent c169cb42bb
commit 691ceb0675
4 changed files with 86 additions and 52 deletions

View File

@@ -2,29 +2,21 @@ package either
import "fmt" import "fmt"
type EitherTag string // Either defines a data structure that logically holds either an E or an A. The flag discriminates the cases
type (
const ( Either[E, A any] struct {
leftTag = "Left" isLeft bool
rightTag = "Right"
)
// Either defines a data structure that logically holds either an E or an A. The tag discriminates the cases
type Either[E, A any] struct {
tag EitherTag `default:"Left"`
left E left E
right A right A
} }
)
// String prints some debug info for the object // String prints some debug info for the object
func (s Either[E, A]) String() string { func (s Either[E, A]) String() string {
switch s.tag { if s.isLeft {
case leftTag: return fmt.Sprintf("Left[%T, %T](%v)", s.left, s.right, s.left)
return fmt.Sprintf("%s[%T, %T](%v)", s.tag, s.left, s.right, s.left)
case rightTag:
return fmt.Sprintf("%s[%T, %T](%v)", s.tag, s.left, s.right, s.right)
} }
return "Invalid" return fmt.Sprintf("Right[%T, %T](%v)", s.left, s.right, s.right)
} }
// Format prints some debug info for the object // Format prints some debug info for the object
@@ -38,19 +30,19 @@ func (s Either[E, A]) Format(f fmt.State, c rune) {
} }
func IsLeft[E, A any](val Either[E, A]) bool { func IsLeft[E, A any](val Either[E, A]) bool {
return val.tag == leftTag return val.isLeft
} }
func IsRight[E, A any](val Either[E, A]) bool { func IsRight[E, A any](val Either[E, A]) bool {
return val.tag == rightTag return !val.isLeft
} }
func Left[E, A any](value E) Either[E, A] { func Left[E, A any](value E) Either[E, A] {
return Either[E, A]{tag: leftTag, left: value} return Either[E, A]{isLeft: true, left: value}
} }
func Right[E, A any](value A) Either[E, A] { func Right[E, A any](value A) Either[E, A] {
return Either[E, A]{tag: rightTag, right: value} return Either[E, A]{isLeft: false, right: value}
} }
func MonadFold[E, A, B any](ma Either[E, A], onLeft func(e E) B, onRight func(a A) B) B { func MonadFold[E, A, B any](ma Either[E, A], onLeft func(e E) B, onRight func(a A) B) B {

View File

@@ -11,6 +11,12 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestDefault(t *testing.T) {
var e Either[error, string]
assert.Equal(t, Of[error](""), e)
}
func TestIsLeft(t *testing.T) { func TestIsLeft(t *testing.T) {
err := errors.New("Some error") err := errors.New("Some error")
withError := Left[error, string](err) withError := Left[error, string](err)

View File

@@ -6,32 +6,22 @@ import (
"fmt" "fmt"
) )
type OptionTag string
const (
noneTag = "None"
someTag = "Some"
)
var ( var (
jsonNull = []byte("null") jsonNull = []byte("null")
) )
// Option defines a data structure that logically holds a value or not // Option defines a data structure that logically holds a value or not
type Option[A any] struct { type Option[A any] struct {
tag OptionTag `default:"None"` isSome bool
value A some A
} }
// String prints some debug info for the object // String prints some debug info for the object
func (s Option[A]) String() string { func (s Option[A]) String() string {
switch s.tag { if s.isSome {
case noneTag: return fmt.Sprintf("Some[%T](%v)", s.some, s.some)
return fmt.Sprintf("%s[%T]", s.tag, s.value)
case someTag:
return fmt.Sprintf("%s[%T](%v)", s.tag, s.value, s.value)
} }
return "Invalid" return fmt.Sprintf("None[%T]", s.some)
} }
// Format prints some debug info for the object // Format prints some debug info for the object
@@ -45,28 +35,29 @@ func (s Option[A]) Format(f fmt.State, c rune) {
} }
func (s Option[A]) MarshalJSON() ([]byte, error) { func (s Option[A]) MarshalJSON() ([]byte, error) {
if s.tag == noneTag { if IsSome(s) {
return json.Marshal(s.some)
}
return jsonNull, nil return jsonNull, nil
} }
return json.Marshal(s.value)
}
func (s Option[A]) UnmarshalJSON(data []byte) error { func (s *Option[A]) UnmarshalJSON(data []byte) error {
// decode the value // decode the value
if bytes.Equal(data, jsonNull) { if bytes.Equal(data, jsonNull) {
s.tag = noneTag s.isSome = false
s.some = *new(A)
return nil return nil
} }
s.tag = someTag s.isSome = true
return json.Unmarshal(data, &s.value) return json.Unmarshal(data, &s.some)
} }
func IsNone[T any](val Option[T]) bool { func IsNone[T any](val Option[T]) bool {
return val.tag == noneTag return !val.isSome
} }
func Some[T any](value T) Option[T] { func Some[T any](value T) Option[T] {
return Option[T]{tag: someTag, value: value} return Option[T]{isSome: true, some: value}
} }
func Of[T any](value T) Option[T] { func Of[T any](value T) Option[T] {
@@ -74,20 +65,20 @@ func Of[T any](value T) Option[T] {
} }
func None[T any]() Option[T] { func None[T any]() Option[T] {
return Option[T]{tag: noneTag} return Option[T]{isSome: false}
} }
func IsSome[T any](val Option[T]) bool { func IsSome[T any](val Option[T]) bool {
return val.tag == someTag return val.isSome
} }
func MonadFold[A, B any](ma Option[A], onNone func() B, onSome func(A) B) B { func MonadFold[A, B any](ma Option[A], onNone func() B, onSome func(A) B) B {
if IsNone(ma) { if IsSome(ma) {
return onNone() return onSome(ma.some)
} }
return onSome(ma.value) return onNone()
} }
func Unwrap[A any](ma Option[A]) (A, bool) { func Unwrap[A any](ma Option[A]) (A, bool) {
return ma.value, ma.tag == someTag return ma.some, ma.isSome
} }

View File

@@ -1,14 +1,59 @@
package option package option
import ( import (
"encoding/json"
"fmt" "fmt"
"testing" "testing"
F "github.com/ibm/fp-go/function" F "github.com/ibm/fp-go/function"
"github.com/ibm/fp-go/internal/utils" "github.com/ibm/fp-go/internal/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
type (
SampleData struct {
Value string
OptValue Option[string]
}
)
func TestJson(t *testing.T) {
sample := SampleData{
Value: "value",
OptValue: Of("optValue"),
}
data, err := json.Marshal(&sample)
require.NoError(t, err)
var deser SampleData
err = json.Unmarshal(data, &deser)
require.NoError(t, err)
assert.Equal(t, sample, deser)
sample = SampleData{
Value: "value",
OptValue: None[string](),
}
data, err = json.Marshal(&sample)
require.NoError(t, err)
err = json.Unmarshal(data, &deser)
require.NoError(t, err)
assert.Equal(t, sample, deser)
}
func TestDefault(t *testing.T) {
var e Option[string]
assert.Equal(t, None[string](), e)
}
func TestReduce(t *testing.T) { func TestReduce(t *testing.T) {
assert.Equal(t, 2, F.Pipe1(None[int](), Reduce(utils.Sum, 2))) assert.Equal(t, 2, F.Pipe1(None[int](), Reduce(utils.Sum, 2)))