opt

package module
v0.6.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 30, 2024 License: MIT Imports: 2 Imported by: 0

README

opt

Optional types and utilities for egonomic data transformation.

GoDoc Go Report Card

opt provides a simple generic optional type with a variety of utilities for performing various transformations without the need for explicit branching.

So, while there are many Optional[T] style packages out there, this one has a focus on making data transformations easier to write and easier to read.

It also prevents certain categories of bug such as nil pointer dereferencing. Of course this comes at a cost and if you're writing performance sensitive code, this library may not be for you and you may be better off just being explicit.

The status of this library is pre-1.0 but the API is stable and probably won't change. It has been dogfooded in 3 production codebases for about a year and all APIs were built to solve some real problems in those projects.

Basics

Let's get the obvious out of the way first...

func main() {
    maybe := opt.New("I exist!")

    maybe.Ok() // true
    value, exists := maybe.Get() // "I exist!", true
    ptr := maybe.Ptr() // some address

    maybe_not := opt.NewEmpty[string]()

    maybe_not.Ok() // false
    value, exists := maybe_not.Get() // "", false
    ptr := maybe_not.Ptr() // nil
}

Optional, generic, yada yada, whatever. Every other optional package does it.

The interesting parts are the construction, mapping and access utilities...

Accessing

Once you've constructed an optional value, you can access the underlying data in a few ways. These make it easy to build branching logic without the need for explicit if statements. Which can be useful for transforming large structures.

The simplest ones are Ok and Get which have examples above. See the GoDoc for more info on these, they're fairly simple and do what you'd expect.

One method that isn't mentioned above is Call. Which simply lets you call a function with the value if it's present:

maybe := opt.New("I exist!")
maybe.Call(func(value string) {
    fmt.Println(value)
})

These have been handy for some ORM setter APIs:

email.Call(accountQuery.SetEmailAddress)

Mapping

One of the core reasons this library was written was to facilitate easy mapping of data types that may or may not be present. Without the need for code that looks like this:

var newValue *T
if oldValue != nil {
    newValue = transform(*oldValue)
}

Which is fine on its own, but if you have many values, it can get quite verbose.

opt instead provides a way to map data as an access or map data as a pipeline. To access the data, you already know about Get but if you want to change the type at the same time as accessing, you can use GetMap:

maybe := opt.New("I exist!")
value, exists := opt.GetMap(maybe, strings.ToUpper)
// "I EXIST!", true

If your destination is expecting a pointer, you can use PtrMap:

maybe := opt.New("I exist!")
value := opt.PtrMap(maybe, strings.ToUpper)
// "I EXIST!" as a `*string`

Note how these are functions of the library, not methods on the type. It would be nice to be able to write maybe.PtrMap(strings.ToUpper) but currently, this is not possible to do in the current version of Go's generics.

If you want to transform the data but keep it wrapped as an optional type, you can use Map or MapErr to execute the closure, only if the value is present:

maybe := opt.New("I exist!")
maybe = opt.Map(maybe, strings.ToUpper)
// opt.Optional[string]("I EXIST!")

And of course MapErr does the same thing but allows you to return an error:

maybe := opt.New("5629")
maybe, err := opt.MapErr(maybe, strconv.Atoi)
// opt.Optional[int](5629)

maybe_not := opt.New("not a number :(")
maybe_not, err := opt.MapErr(maybe, strconv.Atoi)
// Empty optional plus the error from Atoi.

And, as an escape hatch, a .String() method which is useful for tests:

maybe := opt.New("5629")
maybe.String()
// "5629"

If the value exists, it'll use fmt to stringify, if not it'll just be empty.

When there's nothing inside

There are also methods to deal with empty values Or, OrZero and OrCall:

maybe := opt.New("I exist!")
maybe.Or("I don't exist!") // "I exist!"
maybe := opt.Empty[string]()
maybe.Or("I don't exist!") // "I don't exist!"

The Or method simply lets you return a default value if the optional value is empty. This is handy for providing defaults.

The OrZero method simply returns the type's zero-value:

maybe := opt.Empty[time.Time]()
t := maybe.OrZero()
t.IsZero() // true

And finally, OrCall lets you call a function to provide a default value:

maybe := opt.Empty[string]()
t := maybe.OrCall(func() string {
    return "a default value from somewhere"
})
// "a default value from somewhere"

Curried C Functions

Some APIs will have a second version with C appended to the name. These are curried versions of those functions to aid in ergonomic usage.

Say for example you have a function that converts a number to a GBP currency representation. You want to apply this function to a few values in a struct or to a slice of items.

// Given: ConvertUSD(value int) string

func Convert(input Table) PriceBreakdown {
    return PriceBreakdown{
        Cost:          ConvertGBP(input.UnitCost),
        ShippingFee:   NewPtrMap(input.ShippingFee, ConvertGBP),
        ServiceCharge: NewPtrMap(input.ServiceCharge, ConvertGBP),
        Discount:      NewPtrMap(input.Discount, ConvertGBP),
    }
}

A small example, but you could imagine how much this can get in a larger system.

Using curried APIs, we can make this a little more terse:

func Convert(input Table) PriceBreakdown {
    gbp := NewPtrMapC(ConvertGBP)
    return PriceBreakdown{
        Cost:          ConvertGBP(input.UnitCost),
        ShippingFee:   gbp(input.ShippingFee),
        ServiceCharge: gbp(input.ServiceCharge),
        Discount:      gbp(input.Discount),
    }
}

Now this may not seem like much but it can make refactors easier and keep diffs small. Once you start thinking in curried functions, certain tasks get simpler!

Let's see what this looks like for a slice of items:

func ConvertMany(prices []*int) []Optional[string] {
    output := []Optional[string]{}
    for _, v := range prices {
        output = append(output, NewPtrMap(v, ConvertGBP))
    }
    return output
}

If you like to use functional libraries like lo and fp-go then this might be useful:

func ConvertMany(prices []*int) []Optional[string] {
    fn := PtrMapC(ConvertGBP)
    mapper := fp.Map(fn)
    return mapper(prices)
}

Construction

There are quite a few places data can come from. opt provides a few helpers to create optional wrappers from various sources.

We've covered the boring ones already, New and NewEmpty just create values from either something or nothing.

NewMap

This tool creates an optional type but facilitates mapping the data type using a function first. This is similar to .map( x => y ) in many other languages.

v := opt.NewMap("hello", strings.ToUpper)

v now contains an optional string value set to "HELLO". because, before storing the data, it passed the input value through strings.ToUpper.

NewSafe

A common Go pattern is return values that look like (T, bool) where the bool represents validity. NewSafe lets you easily build optional values from this.

// where getThing is: func getThing() (v string, ok bool)
v := opt.NewSafe(getThing())

It's also just handy sometimes for simple logic:

v := opt.NewSafe(account.Email, account.IsEmailPublic)

Here, we're storing the optional value of the account's email only if the value of IsEmailPublic is true.

Sadly this does not work with built-in operations:

hash := map[string]string{"s": "asd"}
NewSafe(hash["dsf"])
// not enough arguments in call to NewSafe have (string) want (T, bool)

var cast any = "hi"
NewSafe(cast.(string))
// not enough arguments in call to NewSafe have (string) want (T, bool)

This is because the bool part of these expressions is optional.

NewIf

This one is another way to encode optionality based on some branching logic. In this variant, the logic exists within a closure that returns a bool.

v := opt.NewIf(account.Email, isValidEmailAddress)
v := opt.NewIf(company.LegalName, func(s string) bool { return s != "" })
v := opt.NewIf(createdAt, func(t time.Time) bool { return !t.IsZero() })
NewPtr, NewPtrMap, NewPtrIf and NewPtrOr

If one area of your application is using pointers already but you want to expose optionals, you can use this one to easily construct an optional from a pointer.

type Account struct {
    Twitter *string
}

// ...

v := opt.NewPtr(account.Twitter)
v := opt.NewPtrOr(account.Twitter, "@southclaws")

Prior Art

Contributing

Issues and pull requests welcome!

Documentation

Overview

Package opt provides an optional type for expressing the possibility of a value being present or not. It's more ergonomic than pointers as it forces you to use accessor methods instead of dereferencing, removing the risk of a nil pointer dereference.

There are also utilities for mapping the optional type to another type if it is present, constructing optional types from pointers and getting a pointer to the optional value or nil for easy usage with APIs that accept pointers.

Note that some APIs are not implemented as methods due to the way that Go's generics are designed. This may change in future Go versions if it becomes possible to write a method of some type T which takes additional type params.

Example (Call)
package main

import (
	"fmt"

	"github.com/Southclaws/opt"
)

func main() {
	i := 1001
	values := []opt.Optional[int]{
		opt.NewEmpty[int](),
		opt.New(1000),
		opt.NewPtr[int](nil),
		opt.NewPtr(&i),
	}

	for _, v := range values {
		v.Call(func(i int) {
			fmt.Println(i)
		})
	}

}
Output:

1000
1001
Example (Else)
package main

import (
	"fmt"

	"github.com/Southclaws/opt"
)

func main() {
	i := 1001
	values := []opt.Optional[int]{
		opt.NewEmpty[int](),
		opt.New(1000),
		opt.NewPtr[int](nil),
		opt.NewPtr(&i),
	}

	for _, v := range values {
		fmt.Println(v.Or(1))
	}

}
Output:

1
1000
1
1001
Example (ElseFunc)
package main

import (
	"fmt"

	"github.com/Southclaws/opt"
)

func main() {
	i := 1001
	values := []opt.Optional[int]{
		opt.NewEmpty[int](),
		opt.New(1000),
		opt.NewPtr[int](nil),
		opt.NewPtr(&i),
	}

	for _, v := range values {
		fmt.Println(v.OrCall(func() int {
			return 2
		}))
	}

}
Output:

2
1000
2
1001
Example (ElseZero)
package main

import (
	"fmt"

	"github.com/Southclaws/opt"
)

func main() {
	i := 1001
	values := []opt.Optional[int]{
		opt.NewEmpty[int](),
		opt.New(1000),
		opt.NewPtr[int](nil),
		opt.NewPtr(&i),
	}

	for _, v := range values {
		fmt.Println(v.OrZero())
	}

}
Output:

0
1000
0
1001
Example (Get)
package main

import (
	"fmt"

	"github.com/Southclaws/opt"
)

func main() {
	i := 1001
	values := []opt.Optional[int]{
		opt.NewEmpty[int](),
		opt.New(1000),
		opt.NewPtr[int](nil),
		opt.NewPtr(&i),
	}

	for _, v := range values {
		if i, ok := v.Get(); ok {
			fmt.Println(i)
		}
	}

}
Output:

1000
1001
Example (JsonMarshalEmpty)
package main

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/Southclaws/opt"
)

func main() {
	s := struct {
		Bool    opt.Optional[bool]      `json:"bool"`
		Byte    opt.Optional[byte]      `json:"byte"`
		Float32 opt.Optional[float32]   `json:"float32"`
		Float64 opt.Optional[float64]   `json:"float64"`
		Int16   opt.Optional[int16]     `json:"int16"`
		Int32   opt.Optional[int32]     `json:"int32"`
		Int64   opt.Optional[int64]     `json:"int64"`
		Int     opt.Optional[int]       `json:"int"`
		Rune    opt.Optional[rune]      `json:"rune"`
		String  opt.Optional[string]    `json:"string"`
		Time    opt.Optional[time.Time] `json:"time"`
		Uint16  opt.Optional[uint16]    `json:"uint16"`
		Uint32  opt.Optional[uint32]    `json:"uint32"`
		Uint64  opt.Optional[uint64]    `json:"uint64"`
		Uint    opt.Optional[uint]      `json:"uint"`
		Uintptr opt.Optional[uintptr]   `json:"uintptr"`
	}{
		Bool:    opt.NewEmpty[bool](),
		Byte:    opt.NewEmpty[byte](),
		Float32: opt.NewEmpty[float32](),
		Float64: opt.NewEmpty[float64](),
		Int16:   opt.NewEmpty[int16](),
		Int32:   opt.NewEmpty[int32](),
		Int64:   opt.NewEmpty[int64](),
		Int:     opt.NewEmpty[int](),
		Rune:    opt.NewEmpty[rune](),
		String:  opt.NewEmpty[string](),
		Time:    opt.NewEmpty[time.Time](),
		Uint16:  opt.NewEmpty[uint16](),
		Uint32:  opt.NewEmpty[uint32](),
		Uint64:  opt.NewEmpty[uint64](),
		Uint:    opt.NewEmpty[uint](),
		Uintptr: opt.NewEmpty[uintptr](),
	}

	output, _ := json.MarshalIndent(s, "", "  ")
	fmt.Println(string(output))

}
Output:

{
  "bool": null,
  "byte": null,
  "float32": null,
  "float64": null,
  "int16": null,
  "int32": null,
  "int64": null,
  "int": null,
  "rune": null,
  "string": null,
  "time": null,
  "uint16": null,
  "uint32": null,
  "uint64": null,
  "uint": null,
  "uintptr": null
}
Example (JsonMarshalOmitEmpty)
package main

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/Southclaws/opt"
)

func main() {
	s := struct {
		Bool    opt.Optional[bool]      `json:"bool,omitempty"`
		Byte    opt.Optional[byte]      `json:"byte,omitempty"`
		Float32 opt.Optional[float32]   `json:"float32,omitempty"`
		Float64 opt.Optional[float64]   `json:"float64,omitempty"`
		Int16   opt.Optional[int16]     `json:"int16,omitempty"`
		Int32   opt.Optional[int32]     `json:"int32,omitempty"`
		Int64   opt.Optional[int64]     `json:"int64,omitempty"`
		Int     opt.Optional[int]       `json:"int,omitempty"`
		Rune    opt.Optional[rune]      `json:"rune,omitempty"`
		String  opt.Optional[string]    `json:"string,omitempty"`
		Time    opt.Optional[time.Time] `json:"time,omitempty"`
		Uint16  opt.Optional[uint16]    `json:"uint16,omitempty"`
		Uint32  opt.Optional[uint32]    `json:"uint32,omitempty"`
		Uint64  opt.Optional[uint64]    `json:"uint64,omitempty"`
		Uint    opt.Optional[uint]      `json:"uint,omitempty"`
		Uintptr opt.Optional[uintptr]   `json:"uintptr,omitempty"`
	}{
		Bool:    opt.NewEmpty[bool](),
		Byte:    opt.NewEmpty[byte](),
		Float32: opt.NewEmpty[float32](),
		Float64: opt.NewEmpty[float64](),
		Int16:   opt.NewEmpty[int16](),
		Int32:   opt.NewEmpty[int32](),
		Int64:   opt.NewEmpty[int64](),
		Int:     opt.NewEmpty[int](),
		Rune:    opt.NewEmpty[rune](),
		String:  opt.NewEmpty[string](),
		Time:    opt.NewEmpty[time.Time](),
		Uint16:  opt.NewEmpty[uint16](),
		Uint32:  opt.NewEmpty[uint32](),
		Uint64:  opt.NewEmpty[uint64](),
		Uint:    opt.NewEmpty[uint](),
		Uintptr: opt.NewEmpty[uintptr](),
	}

	output, _ := json.MarshalIndent(s, "", "  ")
	fmt.Println(string(output))

}
Output:

{}
Example (JsonMarshalPresent)
package main

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/Southclaws/opt"
)

func main() {
	s := struct {
		Bool    opt.Optional[bool]      `json:"bool"`
		Byte    opt.Optional[byte]      `json:"byte"`
		Float32 opt.Optional[float32]   `json:"float32"`
		Float64 opt.Optional[float64]   `json:"float64"`
		Int16   opt.Optional[int16]     `json:"int16"`
		Int32   opt.Optional[int32]     `json:"int32"`
		Int64   opt.Optional[int64]     `json:"int64"`
		Int     opt.Optional[int]       `json:"int"`
		Rune    opt.Optional[rune]      `json:"rune"`
		String  opt.Optional[string]    `json:"string"`
		Time    opt.Optional[time.Time] `json:"time"`
		Uint16  opt.Optional[uint16]    `json:"uint16"`
		Uint32  opt.Optional[uint32]    `json:"uint32"`
		Uint64  opt.Optional[uint64]    `json:"uint64"`
		Uint    opt.Optional[uint]      `json:"uint"`
		Uintptr opt.Optional[uintptr]   `json:"uintptr"`
	}{
		Bool:    opt.New(true),
		Byte:    opt.New[byte](1),
		Float32: opt.New[float32](2.1),
		Float64: opt.New(2.2),
		Int16:   opt.New[int16](3),
		Int32:   opt.New[int32](4),
		Int64:   opt.New[int64](5),
		Int:     opt.New(6),
		Rune:    opt.New[rune](7),
		String:  opt.New("string"),
		Time:    opt.New(time.Date(2006, 1, 2, 15, 4, 5, 0, time.UTC)),
		Uint16:  opt.New[uint16](8),
		Uint32:  opt.New[uint32](9),
		Uint64:  opt.New[uint64](10),
		Uint:    opt.New[uint](11),
		Uintptr: opt.New[uintptr](12),
	}

	output, _ := json.MarshalIndent(s, "", "  ")
	fmt.Println(string(output))

}
Output:

{
  "bool": true,
  "byte": 1,
  "float32": 2.1,
  "float64": 2.2,
  "int16": 3,
  "int32": 4,
  "int64": 5,
  "int": 6,
  "rune": 7,
  "string": "string",
  "time": "2006-01-02T15:04:05Z",
  "uint16": 8,
  "uint32": 9,
  "uint64": 10,
  "uint": 11,
  "uintptr": 12
}
Example (JsonUnmarshalEmpty)
package main

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/Southclaws/opt"
)

func main() {
	s := struct {
		Bool    opt.Optional[bool]      `json:"bool"`
		Byte    opt.Optional[byte]      `json:"byte"`
		Float32 opt.Optional[float32]   `json:"float32"`
		Float64 opt.Optional[float64]   `json:"float64"`
		Int16   opt.Optional[int16]     `json:"int16"`
		Int32   opt.Optional[int32]     `json:"int32"`
		Int64   opt.Optional[int64]     `json:"int64"`
		Int     opt.Optional[int]       `json:"int"`
		Rune    opt.Optional[rune]      `json:"rune"`
		String  opt.Optional[string]    `json:"string"`
		Time    opt.Optional[time.Time] `json:"time"`
		Uint16  opt.Optional[uint16]    `json:"uint16"`
		Uint32  opt.Optional[uint32]    `json:"uint32"`
		Uint64  opt.Optional[uint64]    `json:"uint64"`
		Uint    opt.Optional[uint]      `json:"uint"`
		Uintptr opt.Optional[uintptr]   `json:"uintptr"`
	}{}

	x := `{}`
	err := json.Unmarshal([]byte(x), &s)
	fmt.Println("error:", err)
	fmt.Println("Bool:", s.Bool.Ok())
	fmt.Println("Byte:", s.Byte.Ok())
	fmt.Println("Float32:", s.Float32.Ok())
	fmt.Println("Float64:", s.Float64.Ok())
	fmt.Println("Int16:", s.Int16.Ok())
	fmt.Println("Int32:", s.Int32.Ok())
	fmt.Println("Int64:", s.Int64.Ok())
	fmt.Println("Int:", s.Int.Ok())
	fmt.Println("Rune:", s.Rune.Ok())
	fmt.Println("String:", s.String.Ok())
	fmt.Println("Time:", s.Time.Ok())
	fmt.Println("Uint16:", s.Uint16.Ok())
	fmt.Println("Uint32:", s.Uint32.Ok())
	fmt.Println("Uint64:", s.Uint64.Ok())
	fmt.Println("Uint64:", s.Uint64.Ok())
	fmt.Println("Uint:", s.Uint.Ok())
	fmt.Println("Uintptr:", s.Uint.Ok())

}
Output:

error: <nil>
Bool: false
Byte: false
Float32: false
Float64: false
Int16: false
Int32: false
Int64: false
Int: false
Rune: false
String: false
Time: false
Uint16: false
Uint32: false
Uint64: false
Uint64: false
Uint: false
Uintptr: false
Example (JsonUnmarshalPresent)
package main

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/Southclaws/opt"
)

func main() {
	s := struct {
		Bool    opt.Optional[bool]      `json:"bool"`
		Byte    opt.Optional[byte]      `json:"byte"`
		Float32 opt.Optional[float32]   `json:"float32"`
		Float64 opt.Optional[float64]   `json:"float64"`
		Int16   opt.Optional[int16]     `json:"int16"`
		Int32   opt.Optional[int32]     `json:"int32"`
		Int64   opt.Optional[int64]     `json:"int64"`
		Int     opt.Optional[int]       `json:"int"`
		Rune    opt.Optional[rune]      `json:"rune"`
		String  opt.Optional[string]    `json:"string"`
		Time    opt.Optional[time.Time] `json:"time"`
		Uint16  opt.Optional[uint16]    `json:"uint16"`
		Uint32  opt.Optional[uint32]    `json:"uint32"`
		Uint64  opt.Optional[uint64]    `json:"uint64"`
		Uint    opt.Optional[uint]      `json:"uint"`
		Uintptr opt.Optional[uintptr]   `json:"uintptr"`
	}{}

	x := `{
   "bool": false,
   "byte": 0,
   "float32": 0,
   "float64": 0,
   "int16": 0,
   "int32": 0,
   "int64": 0,
   "int": 0,
   "rune": 0,
   "string": "string",
   "time": "0001-01-01T00:00:00Z",
   "uint16": 0,
   "uint32": 0,
   "uint64": 0,
   "uint": 0,
   "uintptr": 0
 }`
	err := json.Unmarshal([]byte(x), &s)
	fmt.Println("error:", err)
	fmt.Println("Bool:", s.Bool.Ok(), s.Bool)
	fmt.Println("Byte:", s.Byte.Ok(), s.Byte)
	fmt.Println("Float32:", s.Float32.Ok(), s.Float32)
	fmt.Println("Float64:", s.Float64.Ok(), s.Float64)
	fmt.Println("Int16:", s.Int16.Ok(), s.Int16)
	fmt.Println("Int32:", s.Int32.Ok(), s.Int32)
	fmt.Println("Int64:", s.Int64.Ok(), s.Int64)
	fmt.Println("Int:", s.Int.Ok(), s.Int)
	fmt.Println("Rune:", s.Rune.Ok(), s.Rune)
	fmt.Println("String:", s.String.Ok(), s.String)
	fmt.Println("Time:", s.Time.Ok(), s.Time)
	fmt.Println("Uint16:", s.Uint16.Ok(), s.Uint16)
	fmt.Println("Uint32:", s.Uint32.Ok(), s.Uint32)
	fmt.Println("Uint64:", s.Uint64.Ok(), s.Uint64)
	fmt.Println("Uint64:", s.Uint64.Ok(), s.Uint64)
	fmt.Println("Uint:", s.Uint.Ok(), s.Uint)
	fmt.Println("Uintptr:", s.Uint.Ok(), s.Uint)

}
Output:

error: <nil>
Bool: true false
Byte: true 0
Float32: true 0
Float64: true 0
Int16: true 0
Int32: true 0
Int64: true 0
Int: true 0
Rune: true 0
String: true string
Time: true 0001-01-01 00:00:00 +0000 UTC
Uint16: true 0
Uint32: true 0
Uint64: true 0
Uint64: true 0
Uint: true 0
Uintptr: true 0

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func GetMap

func GetMap[In, Out any](in Optional[In], fn func(In) Out) (v Out, ok bool)

GetMap returns the wrapped value if it's present and applies `fn`.

func GetMapC added in v0.6.0

func GetMapC[In, Out any](fn func(In) Out) func(in Optional[In]) (v Out, ok bool)

GetMapC is the curried version of GetMap. See `Map` for an example.

Example
package main

import (
	"fmt"
	"strings"

	"github.com/Southclaws/opt"
)

func main() {
	a := opt.New("hello")
	b := opt.New("my name is")
	c := opt.NewEmpty[string]()

	fn := opt.GetMapC(strings.ToUpper)

	fmt.Println(fn(a))
	fmt.Println(fn(b))
	fmt.Println(fn(c))

}
Output:

HELLO true
MY NAME IS true
 false

func MapC added in v0.6.0

func MapC[In, Out any](fn func(In) Out) func(in Optional[In]) (v Optional[Out])

MapC is the curried version of Map. It's useful for applying the same mapping function to many values either manually or using a functor library.

First, construct the function with the transformer.

fn := Map(transformer)

Now you can use `fn` to transform any optional value using the transformer.

b := fn(a)
dt.Map(array, fn)
Example
package main

import (
	"fmt"
	"strings"

	"github.com/Southclaws/opt"
)

func main() {
	a := opt.New("hello")
	b := opt.New("my name is")
	c := opt.NewEmpty[string]()

	fn := opt.MapC(strings.ToUpper)

	fmt.Println(fn(a))
	fmt.Println(fn(b))
	fmt.Println(fn(c))

}
Output:

HELLO
MY NAME IS

func MapErrC added in v0.6.0

func MapErrC[In, Out any](fn func(In) (Out, error)) func(in Optional[In]) (v Optional[Out], err error)

MapErrC calls `fn` on `in` if it's present and returns the result or an error.

func PtrMap

func PtrMap[In any, Out any](o Optional[In], fn func(In) Out) *Out

PtrMap turns an optional value into a pointer to that value then transforms the value to a new type using the given transformer function.

func PtrMapC added in v0.6.0

func PtrMapC[In any, Out any](fn func(In) Out) func(o Optional[In]) *Out

PtrMapC is the curried version of PtrMap. See `Map` for an example.

Example
package main

import (
	"fmt"
	"strings"

	"github.com/Southclaws/opt"
)

func main() {
	a := opt.New("hello")
	b := opt.New("my name is")
	c := opt.NewEmpty[string]()

	fn := opt.PtrMapC(strings.ToUpper)

	fmt.Println(*fn(a))
	fmt.Println(*fn(b))
	fmt.Println(fn(c))

}
Output:

HELLO
MY NAME IS
<nil>

Types

type Optional

type Optional[T any] container[T]

Optional wraps the type `T` within a container which provides abstractions to make conditional access and transformation of optional types easier.

func Map

func Map[In, Out any](in Optional[In], fn func(In) Out) (v Optional[Out])

Map calls `fn` on `in` if it's present and returns the new optional value.

func MapErr

func MapErr[In, Out any](in Optional[In], fn func(In) (Out, error)) (v Optional[Out], err error)

MapErr calls `fn` on `in` if it's present and returns the result or an error.

func New

func New[T any](value T) Optional[T]

New wraps the input value in an optional type.

func NewEmpty

func NewEmpty[T any]() Optional[T]

NewEmpty creates an empty optional of the specified type `T`.

func NewIf

func NewIf[T any](v T, fn func(T) bool) Optional[T]

NewIf wraps `v` if `fn` returns true. Useful for sanitisation of input such as trimming spaces and treating empty strings as none.

func NewMap

func NewMap[T, R any](v T, fn func(T) R) Optional[R]

NewMap wraps `v` after applying `fn` and producing a new type `R`.

func NewPtr

func NewPtr[T any](ptr *T) Optional[T]

NewPtr wraps the input if it's non-nil, otherwise returns an empty optional.

func NewPtrIf

func NewPtrIf[T any](ptr *T, fn func(T) bool) Optional[T]

NewPtrIf is the same as `NewIf` except will return empty if `ptr` is nil.

func NewPtrMap

func NewPtrMap[T, R any](ptr *T, fn func(T) R) Optional[R]

NewPtrMap wraps the input if it's non-nil and applies the transformer function, otherwise returns an empty optional.

func NewPtrOr added in v0.6.1

func NewPtrOr[T any](ptr *T, fallback T) Optional[T]

NewPtrOr wraps the input if it's non-nil, otherwise returns a fallback value.

func NewSafe

func NewSafe[T any](value T, ok bool) Optional[T]

NewSafe works with common "safe" APIs that return (T, boolean)

func (Optional[T]) Call

func (o Optional[T]) Call(f func(value T))

Call calls `fn` if there is a value wrapped by this optional.

func (Optional[T]) Get

func (o Optional[T]) Get() (value T, ok bool)

Get returns the wrapped value if it's present, `ok` signals existence.

func (Optional[T]) GoString

func (o Optional[T]) GoString() string

GoString is only used for verbose printing.

func (Optional[T]) MarshalJSON

func (o Optional[T]) MarshalJSON() (data []byte, err error)

MarshalJSON marshals the value being wrapped to JSON. If there is no vale being wrapped, the zero value of its type is marshaled.

func (Optional[T]) Ok

func (o Optional[T]) Ok() bool

Ok returns true if there's a value inside.

func (Optional[T]) Or

func (o Optional[T]) Or(v T) (value T)

Or returns the underlying value or `v`.

func (Optional[T]) OrCall

func (o Optional[T]) OrCall(fn func() T) (value T)

OrCall calls `fn` if the optional is empty.

func (Optional[T]) OrZero

func (o Optional[T]) OrZero() (value T)

OrZero returns the zero value of `T` if it's not present.

func (Optional[T]) Ptr

func (o Optional[T]) Ptr() *T

Ptr turns an optional value into a pointer to that value or nil.

func (Optional[T]) String

func (o Optional[T]) String() string

String returns the string representation of the value or an empty string.

func (*Optional[T]) UnmarshalJSON

func (o *Optional[T]) UnmarshalJSON(data []byte) error

UnmarshalJSON unmarshals the JSON into a value wrapped by this optional.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL