Skip to content

Instantly share code, notes, and snippets.

@choonkeat
Last active November 24, 2023 01:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save choonkeat/bc603c508b1f5a589cc0040ff18d9679 to your computer and use it in GitHub Desktop.
Save choonkeat/bc603c508b1f5a589cc0040ff18d9679 to your computer and use it in GitHub Desktop.
Task type in Go
package main
import (
"fmt"
"log"
"os"
"strconv"
"strings"
"try-go/task"
)
func main() {
for _, str := range os.Args[1:] {
t := task.Wrap(strconv.Atoi(str)).
Map(func(n int) int { return n * 2 }).
AndThen(func(n int) *task.Task[int] {
if n == 0 {
return task.Err[int](fmt.Errorf("n is zero"))
}
return task.Ok(n)
})
// it's a pity that when we need to change data type, we break out of our Method chain
// and into a standalone function
st := task.Map(t, func(n int) string { return strings.Repeat("x", n) })
// unwrap will give us back the idiomatic `data, err`
str, err := st.Unwrap()
log.Printf("str: %#v, err: %#v", str, err)
// Switch is the preferred way to handle the success and failure scenarios
st.Switch(task.Scenarios[string]{
Ok: func(data string) {
log.Printf("[switch] data: %#v", data)
},
Err: func(err error) {
log.Printf("[switch] err: %#v", err)
},
})
}
}
$ go run main.go 1 2 hello
2023/11/24 09:05:56 str: "xx", err: <nil>
2023/11/24 09:05:56 [switch] data: "xx"
2023/11/24 09:05:56 str: "xxxx", err: <nil>
2023/11/24 09:05:56 [switch] data: "xxxx"
2023/11/24 09:05:56 str: "", err: &strconv.NumError{Func:"Atoi", Num:"hello", Err:(*errors.errorString)(0x100e0d870)}
2023/11/24 09:05:56 [switch] err: &strconv.NumError{Func:"Atoi", Num:"hello", Err:(*errors.errorString)(0x100e0d870)}
package task
type Task[T interface{}] struct {
data T
err error
}
func Ok[T interface{}](data T) *Task[T] {
return &Task[T]{data, nil}
}
func Err[T interface{}](err error) *Task[T] {
return &Task[T]{err: err}
}
func Wrap[T interface{}](data T, err error) *Task[T] {
return &Task[T]{data, err}
}
// Unwrap method returns the data and error, like Go idiomatic functions
// but we really should strive to use Switch method instead
func (t *Task[T]) Unwrap() (T, error) {
return t.data, t.err
}
type Scenarios[T interface{}] struct {
Ok func(data T)
Err func(err error)
}
// Switch method allows us to unambiguously handle the mutually exclusive
// scenarios of success and failure
func (t *Task[T]) Switch(s Scenarios[T]) {
if t.err != nil {
s.Err(t.err)
} else {
s.Ok(t.data)
}
}
// AndThen method returns an error if the task is already in error state
// Otherwise, pass the data to the callback to change our _task_
func (t *Task[T]) AndThen(callback func(data T) *Task[T]) *Task[T] {
if t.err != nil {
return t
}
return callback(t.data)
}
// AndThen function is like AndThen _method_ but lets you change the type of the data
func AndThen[T, Y any](t *Task[T], callback func(data T) *Task[Y]) *Task[Y] {
if t.err != nil {
return &Task[Y]{err: t.err}
}
return callback(t.data)
}
// Map method returns an error if the task is already in error state
// Otherwise, pass the data to the callback to change our _data_
func (t *Task[T]) Map(callback func(data T) T) *Task[T] {
if t.err != nil {
return t
}
return &Task[T]{callback(t.data), nil}
}
// Map function is like Map _method_ but lets you change the type of the data
func Map[T, Y any](t *Task[T], callback func(data T) Y) *Task[Y] {
if t.err != nil {
return &Task[Y]{err: t.err}
}
return &Task[Y]{data: callback(t.data), err: nil}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment