Skip to content

Commit

Permalink
Merge pull request #2 from go-board/feat/fp
Browse files Browse the repository at this point in the history
[fp] add fp functions & curry & compose & state
  • Loading branch information
leaxoy authored May 15, 2022
2 parents 1228ad7 + a8aceca commit 9235e68
Show file tree
Hide file tree
Showing 8 changed files with 375 additions and 0 deletions.
26 changes: 26 additions & 0 deletions fp/compose.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package fp

// Compose1 is a function that compose one function.
func Compose1[A, R any](f func(A) R) func(A) R {
return func(a A) R { return f(a) }
}

// Compose2 is a function that compose two functions.
func Compose2[A, B, R any](f func(A) B, g func(B) R) func(A) R {
return func(x A) R { return g(f(x)) }
}

// Compose3 is a function that compose three functions.
func Compose3[A, B, C, R any](f func(A) B, g func(B) C, h func(C) R) func(A) R {
return func(x A) R { return h(g(f(x))) }
}

// Compose4 is a function that compose four functions.
func Compose4[A, B, C, D, R any](f func(A) B, g func(B) C, h func(C) D, i func(D) R) func(A) R {
return func(x A) R { return i(h(g(f(x)))) }
}

// Compose5 is a function that compose five functions.
func Compose5[A, B, C, D, E, R any](f func(A) B, g func(B) C, h func(C) D, i func(D) E, j func(E) R) func(A) R {
return func(x A) R { return j(i(h(g(f(x))))) }
}
29 changes: 29 additions & 0 deletions fp/fn1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package fp

// Function1 is a function that takes one argument.
type Function1[A, R any] interface {
Apply(A) R
Curry() func(A) R
}

// Fn1 is a function that takes one argument.
type Fn1[A, R any] func(A) R

func (f Fn1[A, R]) Apply(a A) R { return f(a) }

func (f Fn1[A, R]) Curry() func(A) R { return Fn1[A, R](func(a A) R { return f.Apply(a) }) }

// Apply1 is a function that apply one argument to a function.
func Apply1[A, R any](f func(A) R, a A) R {
return Fn1[A, R](f).Apply(a)
}

// Curry1 is a function that curry one argument function.
func Curry1[A, R any](f func(A) R) func(A) R {
return Fn1[A, R](f).Curry()
}

// Uncurry1 is a function that uncurry one argument function.
func Uncurry1[A, R any](f func(A) R) func(A) R {
return func(a A) R { return f(a) }
}
31 changes: 31 additions & 0 deletions fp/fn2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package fp

// Function2 is a function that takes two arguments.
type Function2[A, B, R any] interface {
Apply(A, B) R
Curry() func(A) func(B) R
}

// Fn2 is a function that takes two arguments.
type Fn2[A, B, R any] func(A, B) R

func (f Fn2[A, B, R]) Apply(a A, b B) R { return f(a, b) }

func (f Fn2[A, B, R]) Curry() func(A) func(B) R {
return func(a A) func(B) R { return func(b B) R { return f.Apply(a, b) } }
}

// Apply2 is a function that takes two arguments.
func Apply2[A, B, R any](f func(A, B) R, a A, b B) R {
return Fn2[A, B, R](f).Apply(a, b)
}

// Curry2 is a function that curry two arguments function.
func Curry2[A, B, R any](f func(A, B) R) func(A) func(B) R {
return Fn2[A, B, R](f).Curry()
}

// Uncurry2 is a function that uncurry two arguments function.
func Uncurry2[A, B, R any](f func(A) func(B) R) func(A, B) R {
return func(a A, b B) R { return f(a)(b) }
}
33 changes: 33 additions & 0 deletions fp/fn3.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package fp

// Function3 is a function that takes three arguments.
type Function3[A, B, C, R any] interface {
Apply(A, B, C) R
Curry() func(A) func(B) func(C) R
}

// Fn3 is a function that takes three arguments.
type Fn3[A, B, C, R any] func(A, B, C) R

func (f Fn3[A, B, C, R]) Apply(a A, b B, c C) R { return f(a, b, c) }

func (f Fn3[A, B, C, R]) Curry() func(A) func(B) func(C) R {
return func(a A) func(B) func(C) R {
return func(b B) func(C) R { return func(c C) R { return f.Apply(a, b, c) } }
}
}

// Apply3 is a function that takes three arguments.
func Apply3[A, B, C, R any](f func(A, B, C) R, a A, b B, c C) R {
return Fn3[A, B, C, R](f).Apply(a, b, c)
}

// Curry3 is a function that curry three arguments function.
func Curry3[A, B, C, R any](f func(A, B, C) R) func(A) func(B) func(C) R {
return Fn3[A, B, C, R](f).Curry()
}

// Uncurry3 is a function that uncurry three arguments function.
func Uncurry3[A, B, C, R any](f func(A) func(B) func(C) R) func(A, B, C) R {
return func(a A, b B, c C) R { return f(a)(b)(c) }
}
35 changes: 35 additions & 0 deletions fp/fn4.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package fp

// Function4 is a function that takes four arguments.
type Function4[A, B, C, D, R any] interface {
Apply(A, B, C, D) R
Curry() func(A) func(B) func(C) func(D) R
}

// Fn4 is a function that takes four arguments.
type Fn4[A, B, C, D, R any] func(A, B, C, D) R

func (f Fn4[A, B, C, D, R]) Apply(a A, b B, c C, d D) R { return f(a, b, c, d) }

func (f Fn4[A, B, C, D, R]) Curry() func(A) func(B) func(C) func(D) R {
return func(a A) func(B) func(C) func(D) R {
return func(b B) func(C) func(D) R {
return func(c C) func(D) R { return func(d D) R { return f.Apply(a, b, c, d) } }
}
}
}

// Apply4 is a function that takes four arguments.
func Apply4[A, B, C, D, R any](f func(A, B, C, D) R, a A, b B, c C, d D) R {
return Fn4[A, B, C, D, R](f).Apply(a, b, c, d)
}

// Curry4 is a function that curry four argument function.
func Curry4[A, B, C, D, R any](f func(A, B, C, D) R) func(A) func(B) func(C) func(D) R {
return Fn4[A, B, C, D, R](f).Curry()
}

// Uncurry4 is a function that uncurry four argument function.
func Uncurry4[A, B, C, D, R any](f func(A) func(B) func(C) func(D) R) func(A, B, C, D) R {
return func(a A, b B, c C, d D) R { return f(a)(b)(c)(d) }
}
37 changes: 37 additions & 0 deletions fp/fn5.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package fp

// Function5 is a function that takes five arguments.
type Function5[A, B, C, D, E, R any] interface {
Apply(A, B, C, D, E) R
Curry() func(A) func(B) func(C) func(D) func(E) R
}

// Fn5 is a function that takes five arguments.
type Fn5[A, B, C, D, E, R any] func(A, B, C, D, E) R

func (f Fn5[A, B, C, D, E, R]) Apply(a A, b B, c C, d D, e E) R { return f(a, b, c, d, e) }

func (f Fn5[A, B, C, D, E, R]) Curry() func(A) func(B) func(C) func(D) func(E) R {
return func(a A) func(B) func(C) func(D) func(E) R {
return func(b B) func(C) func(D) func(E) R {
return func(c C) func(D) func(E) R {
return func(d D) func(E) R { return func(e E) R { return f.Apply(a, b, c, d, e) } }
}
}
}
}

// Apply5 is a function that takes five arguments.
func Apply5[A, B, C, D, E, R any](f func(A, B, C, D, E) R, a A, b B, c C, d D, e E) R {
return Fn5[A, B, C, D, E, R](f).Apply(a, b, c, d, e)
}

// Curry5 is a function that curry five argument function.
func Curry5[A, B, C, D, E, R any](f func(A, B, C, D, E) R) func(A) func(B) func(C) func(D) func(E) R {
return Fn5[A, B, C, D, E, R](f).Curry()
}

// Uncurry5 is a function that uncurry five argument function.
func Uncurry5[A, B, C, D, E, R any](f func(A) func(B) func(C) func(D) func(E) R) func(A, B, C, D, E) R {
return func(a A, b B, c C, d D, e E) R { return f(a)(b)(c)(d)(e) }
}
167 changes: 167 additions & 0 deletions fp/fp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package fp

import (
"strconv"
"testing"

qt "github.com/frankban/quicktest"
)

func TestCurry(t *testing.T) {
a := qt.New(t)
a.Run("curry1", func(c *qt.C) {
f := func(a int) int { return a + 2 }
curryAdd1 := Curry1[int, int](f)
c.Assert(curryAdd1(1), qt.Equals, 3)
})
a.Run("curry2", func(t *qt.C) {
add2 := func(a, b int) int { return a + b }
curryAdd2 := Curry2(add2)
t.Assert(curryAdd2(1)(2), qt.Equals, 3)
})
a.Run("curry3", func(c *qt.C) {
add3 := func(a, b, c int) int { return a + b + c }
curryAdd3 := Curry3(add3)
c.Assert(curryAdd3(1)(2)(3), qt.Equals, 6)
})
a.Run("curry4", func(c *qt.C) {
add4 := func(a, b, c, d int) int { return a + b + c + d }
curryAdd4 := Curry4(add4)
c.Assert(curryAdd4(1)(2)(3)(4), qt.Equals, 10)
})
a.Run("curry5", func(c *qt.C) {
add5 := func(a, b, c, d, e int) int { return a + b + c + d + e }
curryAdd5 := Curry5(add5)
c.Assert(curryAdd5(1)(2)(3)(4)(5), qt.Equals, 15)
})
}

func TestUncurry(t *testing.T) {
a := qt.New(t)
a.Run("uncurry1", func(c *qt.C) {
f := func(a int) int { return a + 2 }
c.Assert(Uncurry1(f)(1), qt.Equals, 3)
})
a.Run("uncurry2", func(c *qt.C) {
f := func(a int) func(int) int {
return func(b int) int { return a + b }
}
c.Assert(Uncurry2(f)(1, 2), qt.Equals, 3)
})
a.Run("uncurry3", func(c *qt.C) {
f := func(a int) func(int) func(int) int {
return func(b int) func(int) int {
return func(c int) int { return a + b + c }
}
}
c.Assert(Uncurry3(f)(1, 2, 3), qt.Equals, 6)
})
a.Run("uncurry4", func(c *qt.C) {
f := func(a int) func(int) func(int) func(int) int {
return func(b int) func(int) func(int) int {
return func(c int) func(int) int {
return func(d int) int { return a + b + c + d }
}
}
}
c.Assert(Uncurry4(f)(1, 2, 3, 4), qt.Equals, 10)
})
a.Run("uncurry5", func(c *qt.C) {
f := func(a int) func(int) func(int) func(int) func(int) int {
return func(b int) func(int) func(int) func(int) int {
return func(c int) func(int) func(int) int {
return func(d int) func(int) int {
return func(e int) int { return a + b + c + d + e }
}
}
}
}
c.Assert(Uncurry5(f)(1, 2, 3, 4, 5), qt.Equals, 15)
})
}

func TestApply(t *testing.T) {
a := qt.New(t)
a.Run("apply1", func(c *qt.C) {
f := func(a int) int { return a + 2 }
c.Assert(Apply1(f, 1), qt.Equals, 3)
})
a.Run("apply2", func(c *qt.C) {
f := func(a, b int) int { return a + b }
c.Assert(Apply2(f, 1, 2), qt.Equals, 3)
})
a.Run("apply3", func(c *qt.C) {
f := func(a, b, c int) int { return a + b + c }
c.Assert(Apply3(f, 1, 2, 3), qt.Equals, 6)
})
a.Run("apply4", func(c *qt.C) {
f := func(a, b, c, d int) int { return a + b + c + d }
c.Assert(Apply4(f, 1, 2, 3, 4), qt.Equals, 10)
})
a.Run("apply5", func(c *qt.C) {
f := func(a, b, c, d, e int) int { return a + b + c + d + e }
c.Assert(Apply5(f, 1, 2, 3, 4, 5), qt.Equals, 15)
})
}

func TestCompose(t *testing.T) {
a := qt.New(t)
a.Run("compose", func(c *qt.C) {
f := func(a int) int { return a + 1 }
c.Assert(Compose1(f)(1), qt.Equals, 2)
})
a.Run("compose2", func(c *qt.C) {
f := func(a int) int { return a + 1 }
g := func(a int) int { return a + 2 }
c.Assert(Compose2(f, g)(1), qt.Equals, 4)
})

a.Run("compose3", func(c *qt.C) {
f := func(a int) int { return a + 1 }
g := func(a int) int { return a + 2 }
h := func(a int) int { return a + 3 }
c.Assert(Compose3(f, g, h)(1), qt.Equals, 7)
})

a.Run("compose4", func(c *qt.C) {
f := func(a int) int { return a + 1 }
g := func(a int) int { return a + 2 }
h := func(a int) int { return a + 3 }
i := func(a int) int { return a + 4 }
c.Assert(Compose4(f, g, h, i)(1), qt.Equals, 11)
})

a.Run("compose5", func(c *qt.C) {
f := func(a int) int { return a + 1 }
g := func(a int) int { return a + 2 }
h := func(a int) int { return a + 3 }
i := func(a int) int { return a + 4 }
j := func(a int) int { return a + 5 }
c.Assert(Compose5(f, g, h, i, j)(1), qt.Equals, 16)
})

a.Run("multiple type compose", func(c *qt.C) {
f := func(a int) int32 { return int32(a) + 2 }
g := func(a int32) int64 { return int64(a) * 3 }
h := func(a int64) int { return int(a) + 4 }
i := func(a int) int { return a * 100 }
j := func(a int) string { return strconv.FormatInt(int64(a), 10) }
c.Assert(Compose5(f, g, h, i, j)(1), qt.Equals, "1300")
})
}

func TestState(t *testing.T) {
a := qt.New(t)
a.Run("state", func(c *qt.C) {
getter, setter := UseState(1)
c.Assert(getter(), qt.Equals, 1)
setter(2)
c.Assert(getter(), qt.Equals, 2)
})
a.Run("funcState", func(c *qt.C) {
getter, setter := UseFuncState(1)
c.Assert(getter(), qt.Equals, 1)
setter(func(prevState int) int { return prevState + 1 })
c.Assert(getter(), qt.Equals, 2)
})
}
17 changes: 17 additions & 0 deletions fp/state.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package fp

// UseState is a convenience function for creating a stateful function.
func UseState[T any](initial T) (func() T, func(T)) {
state := initial
getter := func() T { return state }
setter := func(newState T) { state = newState }
return getter, setter
}

// UseFuncState is a convenience function for creating a stateful function.
func UseFuncState[T any](initial T) (func() T, func(func(T) T)) {
state := initial
getter := func() T { return state }
setter := func(f func(prevState T) T) { state = f(state) }
return getter, setter
}

0 comments on commit 9235e68

Please sign in to comment.