functional

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Dec 23, 2023 License: MIT Imports: 2 Imported by: 0

README

Go Reference

Functional

functional is a Go package for functional programming with generics (it requires at least Go 1.21.5 now). Most of the functionality is found in the sub packages.

It is heavily inspired by the standard library API for F#. It is not one-to-one equivalent but most functions that are available in the F# standard library have an equivalent here. There are examples for most functions, which is also mostly how it is tested. You can see the documentation here.

This type of programming is not necessarily considered "idiomatic" programming in the Go language, but I find it useful for coding the way I think and also for translating algorithms from functional languages into Go.

I make no claim to this being stable or useful. The API may change as I see a need for my own purposes. However, many of the types and functions can be copied easily into your own code and used without taking a dependency on this package.

Get it

go get -u github.com/flowonyx/functional

Use it

You will just need to import the packages you want to use. Below is the full set of packages in this repository.

import (
    // this package: basic types and high level functions
    "github.com/flowonyx/functional"
    // standard errors that are used by different packages
    "github.com/flowonyx/functional/errors"
    // functions for working with slices
    "github.com/flowonyx/functional/list"
    // provides functions for working with the builtin map type
    "github.com/flowonyx/functional/maps"
    // wraps the standard math package functions to make them generic
    // and get rid of the need for casting (not sure how useful it is)
    "github.com/flowonyx/functional/math"
    // provides an Option type and functions that go with it
    "github.com/flowonyx/functional/option"
    // provides an OrderedMap type that works in a similar way to map but
    // keeps the entries in order (either order they are added or sorted order)
    "github.com/flowonyx/functional/orderedMap"
    // provides a Result type with Success or Failure and related functions
    "github.com/flowonyx/functional/result"
    // provides a generic Set based on the OrderedMap
    "github.com/flowonyx/functional/set"
    // strings provides generic functions for working with strings, runes, and types based on them
    "github.com/flowonyx/functional/strings"
)

This is the top-level package. Here you will find some basic types and high level functions.

Tuple Types

Tuples allow you to pass around pairs or triples of values without creating special structs for them. It is a very common pattern in many programming languages.

  • Pair[T1, T2 any] is a basic tuple type with two items.
  • Triple[T1, T2, T3 any] is a basic tuple type with three items.

Tuple functions

  • PairOf(T1, T2) Pair[T1, T2] creates a Pair type.
  • TripleOf(T1, T2, T3) Triple[T1, T2, T3] creates a Triple type.
  • FromPair(Pair[T1, T2]) (T1, T2) returns the two values in the Pair.
  • FromTriple(Triple[T1, T2, T3]) (T1, T2, T3) returns the three values in the Triple.

Curry Functions

Currying is the process of turning a function that takes parameters into a function that already has some parameters set and takes fewer parameters. Many of the functions in these packages were designed with currying in mind. While it might make more sense at times for the parameters to be in a different order, I tried to put the parameters that would be more likely to be curried at the beginning of the parameter list.

  • Curry, Curry2, and Curry3 accept functions that have 1, 2, and 3 parameters respectively with the values for those parameters and return a function that has no parameters.
  • Curry2To1, Curry3to2, and Curry3to1 accept functions that have 2 or 3 parameters and return a function that has 1 or 2 parameters as the name implies.
  • Curry2To1F accepts a function with 2 parameters and returns a function that has 1 parameter and returns a function with no parameters.
  • Curry functions that end with _0 accept functions with no return value.
  • Curry functions that end with _2 accept functions with 2 return values.

Swap Parameters

  • SwapParams0 takes a function with 2 parameters and no return value (the 0 is for the number of values returned) and returns a function in which the parameters are swapped.
  • SwapParams1 and SwapParams2 are the same but have 1 or 2 return values.

Ternary function (If->ElIf->Else)

There are two different styles of ternary functions. Neither is probably a good idea if Go. It is always going to be faster to use the builtin if statements. However, there may be some cases, where this is useful to you.

  • The first kind takes a boolean test and a function to call for the result if it is true.
    • If(bool, resultIfFunc).ElIf(bool, resultElseIfFunc).Else(resultElseFunc)
  • The second kind takes a boolean test and value for the result if it is true.
    • IfV(bool, resultIf).ElIf(bool, resultElseIf).Else(resultElse)

Similar Work

Sub Packages

Most of the work is done by the sub packages.

  • errors
    • Has very few error constants that are used (generally wrapped by other errors) by the other packages here.
  • list
    • This is where functions live for working with generic slices. I named it list to mirror the terminology in F# as most of these functions are inspired by the API in the builtin list library for F#.
  • maps
    • This provides some functions for working with generic maps.
  • math
    • This mostly wraps the functions from the standard library math package so that it can take numbers of different types and return numbers of different types without casting (on the part of the caller).
  • option
    • This provides a generic Option type where something can either be Some(value) or None.
    • It also provides many functions for interacting with Options and types that fit the same interface.
  • orderedMap
    • This provides a generic map type that keeps items in order: either the order in which they were added or a provided sorted order.
    • This is not as well implemented as it probably could be, but it works well enough for my purposes.
  • result
    • This provides a generic Result type where something can either be Success(value) or Error(error) where Success values and errors can be any type you want.
    • It also provides many functions for interacting with Results.
  • set
    • This provides a generic set type which is built on the orderedMap.
    • It also provides many functions for interacting with Sets.
  • strings
    • This provides functions for working with strings, runes, and types that are aliases for them.
    • I believe it wraps all the functions in the builtin strings package and also several from strconv.

Documentation

Overview

functional is a package to assist with functional style programming in go. At least go 1.18 is required as generics are heavily used throughout. Most functionality is found in the packages under this one. This top level package contains some generally applicable types and functions that are used in the sub packages.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Curry

func Curry[T, R any](f func(T) R, input T) func() R

Curry accepts a function that receives a parameter and the value of the parameter and returns a function that accepts no parameters but returns the result of applying the function to the given parameter.

Example
package main

import (
	"fmt"

	"github.com/flowonyx/functional"
)

func main() {
	f := func(i int) int { return i * 2 }
	f2 := functional.Curry(f, 2)
	r := f2()
	fmt.Println(r)
}
Output:

4

func Curry2

func Curry2[T1, T2, R any](f func(T1, T2) R, input1 T1, input2 T2) func() R

Curry2 accepts a function that receives two parameters and the values of those parameters and returns a function that accepts no parameters but returns the result of applying the function to the given parameters.

Example
package main

import (
	"fmt"

	"github.com/flowonyx/functional"
)

func main() {
	f := func(mul, i int) int { return i * mul }
	f2 := functional.Curry2(f, 2, 2)
	r := f2()
	fmt.Println(r)
}
Output:

4

func Curry2To1

func Curry2To1[T1, T2, R any](f func(T1, T2) R, input1 T1) func(T2) R

Curry2To1 accepts a function that receives two parameters and the value of the first parameter and returns a function that accepts one parameter which is the second parameter in the original function and returns the result of applying the function with input1 as the first parameter and whatever is given to the curried function as the second parameter.

Example
package main

import (
	"fmt"

	"github.com/flowonyx/functional"
)

func main() {
	f := func(mul, i int) int { return i * mul }
	f2 := functional.Curry2To1(f, 2)
	r := f2(2)
	fmt.Println(r)
}
Output:

4

func Curry2To1F

func Curry2To1F[T1, T2, R any](f func(T1, T2) R) func(T1) func(T2) R

Curry2To1F accepts a function that receives two parameters and returns a function that accepts one parameter which is the first parameter in the original function and returns another function that accepts one parameter, which is the second parameter in the original function. The last function returns the result of applying the original function with the input to the first returned function as the first parameter and whatever is given to the last returned function as the second parameter.

Example
package main

import (
	"fmt"

	"github.com/flowonyx/functional"
)

func main() {
	f := func(mul, i int) int { return i * mul }
	f2 := functional.Curry2To1F(f) // func(mul int) func(int) int { return func(i int) int { return i * mul }}
	f1 := f2(2)                    // func(i int) int { return i * 2 }
	r := f1(3)
	fmt.Println(r)
}
Output:

6

func Curry2To1_0

func Curry2To1_0[T1, T2 any](f func(T1, T2), input1 T1) func(T2)

Curry2To1_0 accepts a function that receives two parameters and the value of the first parameter and returns a function that accepts one parameter which is the second parameter in the original function. The curried function returns the result of applying the original function to input1 as the first parameter and whatever is given to the last returned function as the second parameter. The function must have no return value.

func Curry2To1_2

func Curry2To1_2[T1, T2, R1, R2 any](f func(T1, T2) (R1, R2), input1 T1) func(T2) (R1, R2)

Curry2To1_2 accepts a function that receives two parameters and the value of the first parameter and returns a function that accepts one parameter which is the second parameter in the original function and returns the result of applying the function with input1 as the first parameter and whatever is given to the curried function as the second parameter. The function must 2 return values.

func Curry2_0

func Curry2_0[T1, T2 any](f func(T1, T2), input1 T1, input2 T2) func()

Curry2_0 accepts a function that receives two parameters and the values of those parameters and returns a function that accepts no parameters. The function must have no return value.

func Curry2_2

func Curry2_2[T1, T2, R1, R2 any](f func(T1, T2) (R1, R2), input1 T1, input2 T2) func() (R1, R2)

Curry2_2 accepts a function that receives two parameters and the values of those parameters and returns a function that accepts no parameters but returns the result of applying the function to the given parameters. The function must have 2 return values.

func Curry3

func Curry3[T1, T2, T3, R any](f func(T1, T2, T3) R, input1 T1, input2 T2, input3 T3) func() R

Curry3 accepts a function that receives three parameters and the values of those parameters and returns a function that accepts no parameters but returns the result of applying the function to the given parameters.

Example
package main

import (
	"fmt"

	"github.com/flowonyx/functional"
)

func main() {
	f := func(a, b, c int) int { return a + b + c }
	f0 := functional.Curry3(f, 1, 2, 3)
	r := f0()
	fmt.Println(r)
}
Output:

6

func Curry3To1

func Curry3To1[T1, T2, T3, R any](f func(T1, T2, T3) R, input1 T1, input2 T2) func(T3) R

Curry3To1 accepts a function that receives three parameters and returns a function that accepts one parameter which is the last parameter in the original function. The curried function returns the result of applying the original function to input1 and input2 as the first two parameters and whatever is given to the last returned function as the third parameter.

Example
package main

import (
	"fmt"

	"github.com/flowonyx/functional"
)

func main() {
	f := func(a, b, c int) int { return a + b + c }
	f1 := functional.Curry3To1(f, 1, 2)
	r := f1(3)
	fmt.Println(r)
}
Output:

6

func Curry3To1_0

func Curry3To1_0[T1, T2, T3 any](f func(T1, T2, T3), input1 T1, input2 T2) func(T3)

Curry3To1_0 accepts a function that receives three parameters and returns a function that accepts one parameter which is the last parameter in the original function. The curried function applies the original function to input1 and input2 as the first two parameters and whatever is given to the last returned function as the third parameter. The function must have no return value.

func Curry3To1_2

func Curry3To1_2[T1, T2, T3, R1, R2 any](f func(T1, T2, T3) (R1, R2), input1 T1, input2 T2) func(T3) (R1, R2)

Curry3To1_2 accepts a function that receives three parameters and returns a function that accepts one parameter which is the last parameter in the original function. The curried function returns the result of applying the original function to input1 and input2 as the first two parameters and whatever is given to the last returned function as the third parameter. The function must have 2 return values.

func Curry3To2

func Curry3To2[T1, T2, T3, R any](f func(T1, T2, T3) R, input1 T1) func(T2, T3) R

Curry3To2 accepts a function that receives three parameters and returns a function that accepts two parameters which are the last two parameters in the original function. The curried function returns the result of applying the original function to input1 as the first parameter and whatever is given to the last returned function as the second and third parameters.

Example
package main

import (
	"fmt"

	"github.com/flowonyx/functional"
)

func main() {
	f := func(a, b, c int) int { return a + b + c }
	f2 := functional.Curry3To2(f, 1)
	r := f2(2, 3)
	fmt.Println(r)
}
Output:

6

func Curry3To2_0

func Curry3To2_0[T1, T2, T3 any](f func(T1, T2, T3), input1 T1) func(T2, T3)

Curry3To2_0 accepts a function that receives three parameters and returns a function that accepts two parameters which are the last two parameters in the original function. The curried function applies the original function to input1 as the first parameter and whatever is given to the last returned function as the second and third parameters. The function must have no return value.

func Curry3To2_2

func Curry3To2_2[T1, T2, T3, R1, R2 any](f func(T1, T2, T3) (R1, R2), input1 T1) func(T2, T3) (R1, R2)

Curry3To2_2 accepts a function that receives three parameters and returns a function that accepts two parameters which are the last two parameters in the original function. The curried function returns the result of applying the original function to input1 as the first parameter and whatever is given to the last returned function as the second and third parameters. The function must have 2 return values.

func Curry3_0

func Curry3_0[T1, T2, T3 any](f func(T1, T2, T3), input1 T1, input2 T2, input3 T3) func()

Curry3_0 accepts a function that receives three parameters and the values of those parameters and returns a function that accepts no parameters. The function must have no return value.

func Curry3_2

func Curry3_2[T1, T2, T3, R1, R2 any](f func(T1, T2, T3) (R1, R2), input1 T1, input2 T2, input3 T3) func() (R1, R2)

Curry3_2 accepts a function that receives three parameters and the values of those parameters and returns a function that accepts no parameters but returns the result of applying the function to the given parameters. The function must have 2 return values.

func Curry_0

func Curry_0[T any](f func(T), input T) func()

Curry_0 accepts a function that accepts one parameter and the value of the parameter and returns a function that accepts no parameters. The function must have no return value.

func Curry_2

func Curry_2[T, R1, R2 any](f func(T) (R1, R2), input T) func() (R1, R2)

Curry_2 accepts a function that accepts one parameter and the value of the parameter and returns a function that accepts no parameters. The function must have 2 return values.

func FromPair

func FromPair[T1, T2 any](p Pair[T1, T2]) (T1, T2)

FromPair pulls the items out of a Pair without needing to refer to the First and Second fields.

Example
package main

import (
	"fmt"

	"github.com/flowonyx/functional"
)

func main() {
	p := functional.PairOf(1, "2")
	f, s := functional.FromPair(p)
	fmt.Println(f, s)
}
Output:

1 2

func FromTriple

func FromTriple[T1, T2, T3 any](t Triple[T1, T2, T3]) (a T1, b T2, c T3)

FromTriple pulls the items out of a Triple without needing to refer to the First, Second, and Third fields.

Example
package main

import (
	"fmt"

	"github.com/flowonyx/functional"
)

func main() {
	p := functional.TripleOf(1, "2", 3)
	f, s, t := functional.FromTriple(p)
	fmt.Println(f, s, t)
}
Output:

1 2 3

func If

func If[T any](cond bool, then func() T) *ifthenelse[T]

If is essentially like a ternary expression. It allows for an if statement on one line. cond is the boolean condition to check against. then is the function to execute if cond is true. The given function must return a value. It will only be called once there is an Else invoked.

Example
t := 1
r := If(t == 1, func() int { return 1 }).Elif(t <= 2, func() int { return 2 }).Else(func() int { return 3 })
fmt.Println(r)
Output:

1
Example (Second)
t := 2
r := If(t == 1, func() int { return 1 }).Elif(t <= 2, func() int { return 2 }).Else(func() int { return 3 })
fmt.Println(r)
Output:

2
Example (Third)
t := 3
r := If(t == 1, func() int { return 1 }).Elif(t <= 2, func() int { return 2 }).Else(func() int { return 3 })
fmt.Println(r)
Output:

3

func IfV

func IfV[T any](cond bool, then T) *ifthenelsev[T]

IfV (for "if value") is essentially like a ternary expression. It allows for an if statement on one line. cond is the boolean condition to check against. then is the value to return if cond is true. It will only be returned once there is an Else invoked.

Example
t := 1
r := IfV(t == 1, 1).Elif(t <= 2, 2).Else(3)
fmt.Println(r)
Output:

1
Example (Second)
t := 2
r := IfV(t == 1, 1).Elif(t <= 2, 2).Else(3)
fmt.Println(r)
Output:

2
Example (Third)
t := 3
r := IfV(t == 1, 1).Elif(t <= 2, 2).Else(3)
fmt.Println(r)
Output:

3

func Must

func Must[T any](t T, err error) T

Must takes the output of a function that returns a value and an error, panics if the error is not nil, or otherwise returns the value.

func Must2

func Must2[T1, T2 any](t1 T1, t2 T2, err error) (T1, T2)

Must2 takes the output of a function that returns two values and an error, panics if the error is not nil, or otherwise returns the values.

func Must_0

func Must_0[T any](f func(T) error) func(T)

Must_0 takes a function that returns an error and returns a functions returns nothing and panics if there is an error.

func Must_1

func Must_1[T, R any](f func(T) (R, error)) func(T) R

Must_1 takes a function that returns a value and an error and returns a functions returns only the value and panics if there is an error.

func Must_2

func Must_2[T, R1, R2 any](f func(T) (R1, R2, error)) func(T) (R1, R2)

Must_2 takes a function that returns two values and an error and returns a functions returns only the two values and panics if there is an error.

func SwapParams0

func SwapParams0[T1, T2 any](f func(T1, T2)) func(T2, T1)

SwapParams0 adapts a function to take the second parameter as the first and the first parameter as the second. The supplied function must have no return value.

Example
package main

import (
	"fmt"

	"github.com/flowonyx/functional"
)

func main() {
	f := func(i int, s string) { fmt.Print(i, s) }
	fs := functional.SwapParams0(f)
	fs("string", 1)
}
Output:

1string

func SwapParams1

func SwapParams1[T1, T2, R any](f func(T1, T2) R) func(T2, T1) R

SwapParams1 adapts a function to take the second parameter as the first and the first parameter as the second. The supplied function must have one return value.

Example
package main

import (
	"fmt"
	"strings"

	"github.com/flowonyx/functional"
)

func main() {
	fs := functional.SwapParams1(strings.Repeat)
	fc := functional.Curry2To1(fs, 5)
	fmt.Print(fc("*"), fc("-"))
}
Output:

*****-----

func SwapParams2

func SwapParams2[T1, T2, R1, R2 any](f func(T1, T2) (R1, R2)) func(T2, T1) (R1, R2)

SwapParams2 adapts a function to take the second parameter as the first and the first parameter as the second. The supplied function must have one return value.

Example
package main

import (
	"errors"
	"fmt"

	"github.com/flowonyx/functional"
)

func main() {
	f := func(a, b int) (int, error) {
		if a < b {
			return -1, errors.New("a < b")
		}
		return b, nil
	}
	fs := functional.SwapParams2(f)
	_, err := fs(2, 1)
	if err == nil {
		panic("should have errored")
	}
	i, err := fs(1, 2)
	if err != nil {
		panic(err)
	}

	fmt.Print(i)
}
Output:

1

Types

type Pair

type Pair[T any, T2 any] struct {
	First  T
	Second T2
}

Pair is a tuple with 2 items.

func PairOf

func PairOf[T, T2 any](a T, b T2) Pair[T, T2]

PairOf is a simple method of creating a Pair.

Example
package main

import (
	"fmt"

	"github.com/flowonyx/functional"
)

func main() {
	p := functional.PairOf(1, "2")
	fmt.Println(p.String())
}
Output:

(1, "2")

func (Pair[T, T2]) String

func (p Pair[T, T2]) String() string

type Triple

type Triple[T, T2, T3 any] struct {
	First  T
	Second T2
	Third  T3
}

Triple is a tuple with 3 items.

func TripleOf

func TripleOf[T, T2, T3 any](a T, b T2, c T3) Triple[T, T2, T3]

TripleOf is a simple method of creating a Triple.

Example
package main

import (
	"fmt"

	"github.com/flowonyx/functional"
)

func main() {
	t := functional.TripleOf(1, "2", 3)
	fmt.Println(t.String())
}
Output:

(1, "2", 3)

func (Triple[T, T2, T3]) String

func (p Triple[T, T2, T3]) String() string

Directories

Path Synopsis
Package list provides generic functions for dealing with slices.
Package list provides generic functions for dealing with slices.
Package simpleMap provides a map[Key]Value that has some convenient methods.
Package simpleMap provides a map[Key]Value that has some convenient methods.
Package math provides some generic mathematical functions.
Package math provides some generic mathematical functions.
Package orderedMap offers a map-like type that keeps it's items in either the order they are added or in sorted order if it is provided a less function at creation.
Package orderedMap offers a map-like type that keeps it's items in either the order they are added or in sorted order if it is provided a less function at creation.
Package result provides a helper type for error handling without returning multiple values.
Package result provides a helper type for error handling without returning multiple values.
Package set provides a generic Set type.
Package set provides a generic Set type.
Package strings provides methods that work on strings or runes--but also types descended from them.
Package strings provides methods that work on strings or runes--but also types descended from them.

Jump to

Keyboard shortcuts

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