Skip to content

Latest commit

 

History

History
592 lines (469 loc) · 10.7 KB

README.md

File metadata and controls

592 lines (469 loc) · 10.7 KB

Iterator and functional utilities

The design is inspired by samber/lo and iterator proposal. This library does not deal with channel/pipes/concurrency as that is beyond the scope of this project.

Root package

Root package github.com/rprtr258/fun provides common slice and functional utilities.

Core types

// Pair is a data structure that has two values.
type Pair[K, V any] struct {K K; V V}

// Option is either value or nothing.
type Option[T any] struct {Value T; Valid bool}

// Result is either value or error.
type Result[T any] Pair[T, error]

Core constraints

// RealNumber is a generic number interface that covers all Go real number types.
type RealNumber interface {
	int | int8 | int16 | int32 | int64 |
		uint | uint8 | uint16 | uint32 | uint64 |
		float32 | float64
}

// Number is a generic number interface that covers all Go number types.
type Number interface {
	RealNumber | complex64 | complex128
}

Design decisions

Declarations like

func Map[R, T any, F interface {
	func(T) R | func(T, int) R
}](f F, slice ...T) []R

exists for the reason that we want both func(elem) and func(elem, index) functions work. With such declaration Go cannot infer type R, so we have to specify it explicitly on usage: fun.Map[string](fn, slice...)

Another moment is that slice arguments are variadic. That allows user not to construct slice in some cases like fun.Contains(status, "OK", "Success") instead of fun.Contains(status, []string{"OK", "Success"}).

slice functions

Map

Applies function to all elements and returns slice with results.

fun.Map(func(x int64, _ int) string {
	return strconv.FormatInt(x, 10)
}, 0, 1, 2)
// []string{"0", "1", "2"}

Filter

Filters slice elements using given predicate.

fun.Filter(func(x int64, _ int) bool {
	return x%2 == 0
}, 0, 1, 2)
// []int64{0, 2}

FilterMap

Transform each element, leaving only those for which true is returned.

fun.FilterMap(func(x int64, _ int) (string, bool) {
	return strconv.FormatInt(x, 10), x%2 == 0
}, 0, 1, 2)
// []string{"0", "2"}

MapDict

Like Map but uses dictionary instead of transform function.

dict := map[int]string{
	0: "zero",
	1: "one",
	2: "two",
}
fun.MapDict(dict, 0, 1, 2)
// []string{"zero", "one", "two"}

MapErr

Like Map but returns first error got from transform.

fun.MapErr(func(x int64, _ int) (string, error) {
	if x%2 == 0 {
		return strconv.FormatInt(x, 10), nil
	}
	return "", errors.New("odd")
}, 0, 1, 2)
// []string{"0"}, errors.New("odd")

MapToSlice

Transforms map to slice using transform function. Order is not guaranteed.

dict := map[int]string{
	0: "zero",
	1: "one",
	2: "two",
}
fun.MapToSlice(dict, func(k int, v string) string {
	return fmt.Sprintf("%d: %s", k, v)
})
// []string{"0: zero", "1: one", "2: two"}

MapFilterToSlice

Transforms map to slice using transform function and returns only those for which true is returned. Order is not guaranteed.

dict := map[int]string{
	0: "zero",
	1: "one",
	2: "two",
}
fun.MapFilterToSlice(dict, func(k int, v string) (string, bool) {
	return fmt.Sprintf("%d: %s", k, v), k%2 == 0
})
// []string{"0: zero", "2: two"}

Keys

Returns keys of map. Order is not guaranteed.

dict := map[int]string{
	0: "zero",
	1: "one",
	2: "two",
}
fun.Keys(dict)
// []int{0, 1, 2}

Values

Returns values of map. Order is not guaranteed.

dict := map[int]string{
	0: "zero",
	1: "one",
	2: "two",
}
fun.Values(dict)
// []string{"zero", "one", "two"}

FindKeyBy

Returns the key of the first element predicate returns truthy for.

dict := map[int]string{
	0: "zero",
	1: "one",
	2: "two",
}
fun.FindKeyBy(dict, func(k int, v string) bool {
	return v == "zero"
})
// 0, true

Uniq

Returns unique values of slice. In other words, removes duplicates.

fun.Uniq(1, 2, 3, 1, 2)
// []int{1, 2, 3}

Index

Returns first found element by predicate along with it's index.

fun.Index(func(s string, _ int) bool {
	return strings.HasPrefix(s, "o")
}, "zero", "one", "two")
// "one", 1, true

Contains

Returns true if an element is present in a collection.

fun.Contains("zero", "zero", "one", "two")
// true

SliceToMap

Returns a map containing key-value pairs provided by transform function applied to elements of the given slice.

fun.SliceToMap(func(x int, _ int) (int, int) {
	return x, x * 10
}, 0, 1, 2)
// map[int]int{0: 0, 1: 10, 2: 20}

FromMap

Returns slice of key/value pairs from map.

dict := map[int]string{
	0: "zero",
	1: "one",
	2: "two",
}
fun.FromMap(dict)
// []fun.Pair[int, string]{0: "zero", 1: "one", 2: "two"}

Copy

Returns copy of slice.

fun.Copy(1, 2, 3)
// []int{1, 2, 3}

ReverseInplace

Reverses slice in place.

xs := []int{1, 2, 3}
fun.ReverseInplace(xs)
// xs becomes []int{3, 2, 1}

Subslice

Returns slice from start to end without panicking on out of bounds.

xs := []int{1, 2, 3, 4, 5}
fun.Subslice(1, 4, xs...)
// []int{2, 3, 4}

Chunk

Divides slice into chunks of size chunkSize.

xs := []int{1, 2, 3, 4, 5}
fun.Chunk(2, xs...)
// [][]int{{1, 2}, {3, 4}, {5}}

ConcatMap

Like Map but concatenates results.

fun.ConcatMap(func(x int) []int {
	return []int{x, x + 10, x + 100}
}, 0, 1, 2)
// []int{0, 10, 100, 1, 11, 101, 2, 12, 102}

All

Returns true if all elements satisfy the condition.

fun.All(func(x int) bool {
	return x%2 == 0
}, 0, 2, 4)
// true

Any

Returns true if any (at least one) element satisfies the condition.

fun.Any(func(x int) bool {
	return x%2 == 0
}, 0, 1, 2)
// true

SortBy

Sorts slice in place by given function.

xs := []int{1, 2, 3, 4, 5}
fun.SortBy(func(x int) int {
	return -x
}, xs)
// xs becomes []int{5, 4, 3, 2, 1}

GroupBy

Groups elements by key.

fun.GroupBy(func(x int) int {
	return x % 2
}, 0, 1, 2, 3, 4)
// map[int][]int{0: {0, 2, 4}, 1: {1, 3}}

cmp

Utilities utilizing values comparison.

Min

Returns the minimum of the given values.

fun.Min(1, 2, 3)
// 1

Max

Returns the maximum of the given values.

fun.Max(1, 2, 3)
// 3

Clamp(x, low, high)

Returns x clamped between low and high.

fun.Clamp(99, 1, 10)
// 10

MinBy

Returns first minimum of given values using given order function.

fun.MinBy(func(s string) int {
	return len(s)
}, "one", "two", "three")
// "one"

MaxBy

Returns first maximum of given values using given order function.

fun.MaxBy(func(s string) int {
	return len(s)
}, "one", "two", "three")
// "three"

Working with Option type

Invalid

Returns empty Option.

fun.Invalid[int]()
// Option[int]{}

Valid

Returns Option with given value.

fun.Valid(1)
// Option[int]{Value: 1, Valid: true}

Optional

Returns Option with given value and validity.

fun.Optional(1, true)
// Option[int]{Value: 1, Valid: true}

FromPtr

Returns Option with value from pointer.

x := 1
fun.FromPtr(&x)
// Option[int]{Value: 1, Valid: true}
fun.FromPtr[int](nil)
// Option[int]{}

Option.Unpack

Returns value and validity.

fun.Valid(1).Unpack()
// (1, true)

Option.Or

Returns first valid Option.

fun.Valid(1).Or(fun.Invalid[int]())
// Option[int]{Value: 1, Valid: true}

Option.OrDefault

Returns value if Option is valid, otherwise returns default value.

fun.Valid(1).OrDefault(0)
// 1

Option.Ptr

Returns pointer to value if Option is valid, otherwise returns nil.

fun.Valid(1).Ptr()
// &[]int{1}[0]

OptMap

Returns new Option with transformed value.

fun.Valid(1).OptMap(func(x int) string {
	return fmt.Sprintf("%d", x)
})
// Option[string]{Value: "1", Valid: true}

OptFlatMap

Returns new Option with transformed optional value.

fun.Valid(1).OptFlatMap(func(x int) Option[string] {
	return fun.Valid(fmt.Sprintf("%d", x))
})
// Option[string]{Value: "1", Valid: true}

fp

Zero

Returns zero value of given type.

fun.Zero[int]()
// 0

Debug

Prints value and returns it. Useful for debug printing.

fun.Debug(2+2)*2
// prints 4

Has

Returns true if map has such key.

dict := map[int]string{
	0: "zero",
	1: "one",
	2: "two",
}
fun.Has(dict, 2)
// true

Cond

Returns first value for which true is returned.

fun.Cond(
	1,
	func() (int, bool) { return 2, true },
	func() (int, bool) { return 3, false },
)
// 2

Ptr

Returns pointer to value.

fun.Ptr(1)
// &[]int{1}[0]

Deref

Returns value from pointer. If pointer is nil returns zero value.

fun.Deref[int](nil) // 0
fun.Deref[int](new(int)) // 0
x := 1
fun.Deref[int](&x) // 1

Pipe

Returns value after applying endomorphisms. Endomorphism is just function from type to itself.

fun.Pipe(
	"hello  ",
	strings.TrimSpace,
	strings.NewReplacer("l", "|").Replace,
	strings.ToUpper,
)
// "HE||O"

If

There are multiple variations of if statement usable as expression.

IF

Simple ternary function.

fun.IF(true, 1, 0)
// 1

If, IfF

Returns value from branch for which predicate is true. F suffix can be used to get values not evaluated immediately.

fun.If(true, 1).Else(0)
// 1
fun.If(false, 1).ElseF(func() int { return 0 })
// 0
fun.If(false, 1).ElseIf(true, 2).Else(3)
// 2
fun.IfF(false, func() int { return 1 }).Else(0)
// 0

fun.If(true, db.Get(0)).Else(db.Get(1))
// db.Get(0) result, db.Get called two times
fun.IfF(true, func() Thing { return db.Get(0) }).ElseF(func() Thing { return db.Get(1) })
// db.Get(0) result, db.Get called once

Switch

switch usable as expression.

fun.Switch("one", -1).
	Case("zero", 0).
	Case("one", 1).
	Case("two", 2).
	End()
// 1

Iter

github.com/rprtr258/fun/iter introduces iterator primitives for which iter.Seq[T] is basic.

type Seq[V any] func(yield func(V) bool)

Which is a function which accepts function to yield values from iteration. yield must return false when iteration must stop (analogous to break).

Example iterator yielding numbers from 1 to n, including n:

func Range(n int) iter.Seq[int] {
	return func(yield func(int) bool) {
		for i := range n {
			if !yield(i) {
				return
			}
		}
	}
}

Set

github.com/rprtr258/fun/set introduces Set[T] primitive for collections of unique comparable values.

Ordered map

github.com/rprtr258/fun/orderedmap introduces OrderedMap[K, V] data structure which acts like hashmap but also allows to iterate over keys in sorted order. Internally, binary search tree is used.