Skip to content

Commit

Permalink
feat(errorFuncF): added ErrorFunc and ErrorFuncF
Browse files Browse the repository at this point in the history
  • Loading branch information
protoman92 committed Mar 25, 2018
1 parent f5bbec4 commit a533090
Show file tree
Hide file tree
Showing 13 changed files with 140 additions and 66 deletions.
4 changes: 2 additions & 2 deletions compose/constant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import (
)

const (
delayTime = time.Duration(1e8)
retryCount = uint(10)
delay = time.Duration(1e8)
retries = uint(10)

// This value should be returned by test Functions.
valueOp = 1
Expand Down
10 changes: 10 additions & 0 deletions compose/errorFunc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package compose

// ErrorFunc represents an error-returning function. This is a specialized
// form of Func, and thus is constructible from a Func.
type ErrorFunc func() error

// Invoke invokes the underlying error function.
func (ef ErrorFunc) Invoke() error {
return ef()
}
27 changes: 27 additions & 0 deletions compose/errorFuncF.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package compose

// ErrorFuncF converts an ErrorFunc to another ErrorFunc.
type ErrorFuncF func(ErrorFunc) ErrorFunc

// Wrap is a convenience method that calls the underlying ErrorFuncF.
func (eff ErrorFuncF) Wrap(ef ErrorFunc) ErrorFunc {
return eff(ef)
}

// ErrorFuncF converts a FuncF to an ErrorFuncF. This method should be used at
// the last stage of a chain so we do not need to redefine other higher-order
// functions.
func (ff FuncF) ErrorFuncF() ErrorFuncF {
return func(ef ErrorFunc) ErrorFunc {
var function Func = func() (interface{}, error) {
return nil, ef()
}

wrapped := ff.Wrap(function)

return func() error {
_, err := wrapped.Invoke()
return err
}
}
}
18 changes: 13 additions & 5 deletions compose/function.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
package compose

// Function represents an operation that could return an error.
type Function func() (interface{}, error)
// Func represents an operation that could return an error.
type Func func() (interface{}, error)

// Invoke is a convenience method to call a Function. Although it is the same
// as if we call the function normally, this may look nicer in a chain.
func (f Function) Invoke() (interface{}, error) {
// Invoke is a convenience method to call a Func. Although it is the same as if
// we call the function normally, this may look nicer in a chain.
func (f Func) Invoke() (interface{}, error) {
return f()
}

// ErrorFunc converts the current function into an ErrorFunc.
func (f Func) ErrorFunc() ErrorFunc {
return func() error {
_, err := f.Invoke()
return err
}
}
22 changes: 11 additions & 11 deletions compose/functionF.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
package compose

// FunctionF transforms a Function into another Function.
type FunctionF func(Function) Function
// FuncF transforms a Func into another Func.
type FuncF func(Func) Func

// Compose composes the functionalities of both FunctionF. We can use this to
// chain enhance a base Function without exposing implementation details.
func (ff FunctionF) Compose(selector FunctionF) FunctionF {
return func(f Function) Function {
// Compose composes the functionalities of both FuncF. We can use this to chain
// enhance a base Func without exposing implementation details.
func (ff FuncF) Compose(selector FuncF) FuncF {
return func(f Func) Func {
return ff(selector(f))
}
}

// ComposeFn is similar to Compose, but it is more convenient when we deal with
// functions that return FunctionF.
func (ff FunctionF) ComposeFn(selectorFn func() FunctionF) FunctionF {
// functions that return FuncF.
func (ff FuncF) ComposeFn(selectorFn func() FuncF) FuncF {
return ff.Compose(selectorFn())
}

// Wrap is a convenience method to invoke the wrap on a Function. This may look
// nice in a function chain.
func (ff FunctionF) Wrap(f Function) Function {
// Wrap is a convenience method to invoke the wrap on a Func. This may look
// nicer in a function chain.
func (ff FuncF) Wrap(f Func) Func {
return ff(f)
}
20 changes: 17 additions & 3 deletions compose/functionF_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
func TestCompose(t *testing.T) {
published := 0

retryF := RetryF(retryCount)
retryF := RetryF(retries)

publishF := PublishF(func(value interface{}, err error) {
published++
Expand All @@ -20,8 +20,8 @@ func TestCompose(t *testing.T) {
/// When && Then 1
retryF.Compose(publishF).ComposeFn(NoopF)(errF)()

if uint(published) != retryCount+1 {
t.Errorf("Expected %d, got %d", retryCount+1, published)
if uint(published) != retries+1 {
t.Errorf("Expected %d, got %d", retries+1, published)
}

/// When && Then 2
Expand All @@ -33,6 +33,20 @@ func TestCompose(t *testing.T) {
}
}

func TestComposeConvertToErrorFuncF(t *testing.T) {
/// Setup
errF := func() error {
return errOp
}

retryF := RetryF(retries).ErrorFuncF()

/// When & Then
if err := retryF.Wrap(errF).Invoke(); err != errOp {
t.Errorf("Expected %v, got %v", errOp, err)
}
}

func BenchmarkComposition(b *testing.B) {
errF := func() (interface{}, error) {
return valueOp, errOp
Expand Down
15 changes: 15 additions & 0 deletions compose/function_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package compose

import "testing"

func TestConvertToErrorFunc(t *testing.T) {
/// Setup
var errF Func = func() (interface{}, error) {
return valueOp, errOp
}

/// When & Then
if err := errF.ErrorFunc().Invoke(); err != errOp {
t.Errorf("Expected %v, got %v", errOp, err)
}
}
6 changes: 3 additions & 3 deletions compose/noop.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package compose

// NoopF does nothing and simply returns the function.
func NoopF() FunctionF {
return func(f Function) Function {
func NoopF() FuncF {
return func(f Func) Func {
return f
}
}

// Noop is a convenience function that calls the composable NoopF under the hood.
func (f Function) Noop() Function {
func (f Func) Noop() Func {
return NoopF().Wrap(f)
}
2 changes: 1 addition & 1 deletion compose/noop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import "testing"

func TestNoop(t *testing.T) {
/// Setup
var errF Function = func() (interface{}, error) {
var errF Func = func() (interface{}, error) {
return valueOp, errOp
}

Expand Down
8 changes: 4 additions & 4 deletions compose/publish.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package compose

// PublishF publishes the result of a Function for side effects.
func PublishF(callback func(interface{}, error)) FunctionF {
return func(f Function) Function {
// PublishF publishes the result of a Func for side effects.
func PublishF(callback func(interface{}, error)) FuncF {
return func(f Func) Func {
return func() (interface{}, error) {
value, err := f()
callback(value, err)
Expand All @@ -13,6 +13,6 @@ func PublishF(callback func(interface{}, error)) FunctionF {

// Publish is a convenience function that calls the composable PublishF under
// the hood.
func (f Function) Publish(callback func(interface{}, error)) Function {
func (f Func) Publish(callback func(interface{}, error)) Func {
return PublishF(callback).Wrap(f)
}
8 changes: 4 additions & 4 deletions compose/publish_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ func TestPublish(t *testing.T) {
var publishedValue interface{}
var publishedErr error

var errF Function = func() (interface{}, error) {
var errF Func = func() (interface{}, error) {
return valueOp, errOp
}

Expand All @@ -21,7 +21,7 @@ func TestPublish(t *testing.T) {
}

/// When & Then
value, err := errF.Publish(publishF).Retry(retryCount).Invoke()
value, err := errF.Publish(publishF).Retry(retries).Invoke()

if err != errOp || value != nil {
t.Errorf("Expected %v, got %v", errOp, err)
Expand All @@ -35,7 +35,7 @@ func TestPublish(t *testing.T) {
t.Errorf("Expected %v, got %v", errOp, publishedErr)
}

if uint(published) != retryCount+1 {
t.Errorf("Expected %v, got %v", retryCount+1, published)
if uint(published) != retries+1 {
t.Errorf("Expected %v, got %v", retries+1, published)
}
}
34 changes: 17 additions & 17 deletions compose/retry.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ package compose

import "time"

// CountRetryF composes a Function with retry capabilities. The error function
// has access to the current retry count in its first parameter, which is
// useful e.g when we are implementing a delay mechanism.
func CountRetryF(retryCount uint) func(func(uint) (interface{}, error)) Function {
return func(f func(uint) (interface{}, error)) Function {
// CountRetryF composes a Func with retry capabilities. The error function has
// access to the current retry count in its first parameter, which is useful
// e.g when we are implementing a delay mechanism.
func CountRetryF(retryCount uint) func(func(uint) (interface{}, error)) Func {
return func(f func(uint) (interface{}, error)) Func {
var retryF func(uint) (interface{}, error)

retryF = func(current uint) (interface{}, error) {
Expand All @@ -31,24 +31,24 @@ func CountRetryF(retryCount uint) func(func(uint) (interface{}, error)) Function

// RetryF has the same semantics as CountRetry, but ignores the current retry
// count.
func RetryF(retryCount uint) FunctionF {
return func(f Function) Function {
func RetryF(retryCount uint) FuncF {
return func(f Func) Func {
return CountRetryF(retryCount)(func(retry uint) (interface{}, error) {
return f()
})
}
}

// Retry is a convenience method to chain Function, using the compose RetryF
// function under the hood.
func (f Function) Retry(retryCount uint) Function {
// Retry is a convenience method to chain Func, using the compose RetryF under
// the hood.
func (f Func) Retry(retryCount uint) Func {
return RetryF(retryCount).Wrap(f)
}

// delayRetry composes a function with retry-delaying capabilities. The output
// of the return function can be fed to a CountRetry composition.
func delayRetry(d time.Duration) func(Function) func(uint) (interface{}, error) {
return func(f Function) func(uint) (interface{}, error) {
func delayRetry(d time.Duration) func(Func) func(uint) (interface{}, error) {
return func(f Func) func(uint) (interface{}, error) {
return func(retry uint) (interface{}, error) {
if retry > 0 {
time.Sleep(d)
Expand All @@ -60,18 +60,18 @@ func delayRetry(d time.Duration) func(Function) func(uint) (interface{}, error)
}

// DelayRetryF composes retry with delay capabilities.
func DelayRetryF(retryCount uint) func(time.Duration) FunctionF {
return func(delay time.Duration) FunctionF {
return func(f Function) Function {
func DelayRetryF(retryCount uint) func(time.Duration) FuncF {
return func(delay time.Duration) FuncF {
return func(f Func) Func {
return CountRetryF(retryCount)(delayRetry(delay)(f))
}
}
}

// DelayRetry is a convenience function that calls the composable DelayRetryF
// function under the hood.
func (f Function) DelayRetry(retryCount uint) func(time.Duration) Function {
return func(d time.Duration) Function {
func (f Func) DelayRetry(retryCount uint) func(time.Duration) Func {
return func(d time.Duration) Func {
return DelayRetryF(retryCount)(d).Wrap(f)
}
}

0 comments on commit a533090

Please sign in to comment.