dry

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Oct 12, 2018 License: Apache-2.0 Imports: 0 Imported by: 1

README

dry

A minimal set of quasi-functional programming constructs for Go, inspired by dry-rb.

Fauxnads

Rather than actaul monads, we're going to use fauxnads ... things that quack like monads. Currently, the following fauxnads are available: Result (success or failure).

Result

A Result can be either a success or a failure, and in this package, it always wraps a dry.Value (interface{}). Let's save the argument about how this is bad and I'm bad for doing this until after generics are a thing, hmmkay?

To create a Result, pass a Value to either dry.Success() or dry.Failure(), depending on your needs:

rightOn := dry.Success("heck yeah")
oNoes := dry.Failure("you broke it")

Also, in the event that you're handed any old Result and you want to check for success/failure, you can use the Success() and Failure() methods on the resuit:

// if successful, let's do some stuff
if result.Success() {
  doStuff(result.Value())
}

// if failure, let's do some different stuff
if result.Failure() {
  doOtherStuff(result.Error())
}

Transactions

The primary reason behind this whole package, really, is that I rather like dry-transaction when dealing with Ruby endeavors, and I greatly miss it when doing up my Go stuff. There's already at least one Railway-Oriented Programming package for Go (rop), and chances are pretty good that it's close to a real implementation than dry.Transaction.

That said, I never saw a wheel I didn't want to reinvent, so here we are!

The short description of a transaction that I like to go with is "a multi-step process that can fail at any point, stop execution, and allow for recovery".

To that end, a Step is a very specific type of function, in as much as it's any fuction that takes a Value and returns a Result. Following from this, a Transaction is a collection of Steps that can be Called with an input Value, and the content of the returned Result from one Step is passed in as input to the next Step. In the fine tradition of providing complicated examples to perform trivial tasks, here's a quick example that increments an integer:

package main

import (
	"fmt"

	"github.com/ess/dry"
)

func main() {
	transaction := dry.NewTransaction(
		show,
		increment,
	)

	result := transaction.Call(120)

	if result.Failure() {
		panic(result.Error())
	}

	total := result.Value(myValue).(int)
	fmt.Println("final total:", total)
}

func increment(data dry.Value) dry.Result {
	s, ok := data.(int)
	if !ok {
		return dry.Failure(fmt.Errorf("value isn't an integer"))
	}

	return dry.Success(s + 1)
}

func show(input dry.Value) dry.Result {
	fmt.Println("current value:", input)

	return dry.Success(input)
}

Now, that's a fine example right there of something that we certainly could have done in like three lines of code, but didn't. Here's the output when we run it:

current value: 120
final total: 121

We could have also build the transaction manually like so:

	transaction := dry.NewTransaction()
	transaction.Step(show)
	transaction.Step(increment)

If we wanted to do the whole thing twice, we could have done it like this:

	transaction := dry.NewTransaction()
	transaction.Step(show)
	transaction.Step(increment)
	transaction.Step(show)
	transaction.Step(increment)

Here's the new output:

current value: 120
current value: 121
Final total: 122

If you do it that way, though, you're missing out on all the fun, because one transaction can be used as a step in another transaction. Let's give that a shot:

	trivial := dry.NewTransaction()
	trivial.Step(show)
	trivial.Step(increment)

	transaction := dry.NewTransaction(trivial.Call, trivial.Call)

So, what happens when there's a failure along the way? Let's be mean to ourselves and inject a problem into one of the steps:

func increment(data dry.Value) dry.Result {
	s, ok := data.(int)
	if !ok {
		return dry.Failure(fmt.Errorf("value isn't an integer"))
	}

	if s%2 == 0 {
		return dry.Failure(fmt.Errorf("i can't even"))
	}

	return dry.Success(s + 1)
}

There we go. Incrementing the number will now fail if the number is even. Here's the output:

current value: 120
panic: i can't even

goroutine 1 [running]:
main.main()
	/path/to/dry/examples/main.go:26 +0x563

History

  • v1.0.0 - Initial Release

Documentation

Overview

Package dry is all about not repeating yourself by using rough approximations of some of the concepts provided by dry-rb.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Result

type Result interface {
	// Wrapped returns the value that is wrapped by a Result.
	Wrapped() Value

	// Success returns a boolean. If the result is a success, it is true.
	// Otherwise, it is false.
	Success() bool

	// Failure returns a boolean. If the result is a failure, it is true.
	// Otherwise, it is false.
	Failure() bool

	// Value returns the value that is wrapped by a Success result.
	Value() Value

	// Error returns the value that is wrapped by a Failure result.
	Error() Value
}

Result is (not really) a monad that is used to model the result of an operation. It can be either a Success or a Failure that wraps a value.

func Failure

func Failure(value Value) Result

Failure wraps a value in a failure Result

func Success

func Success(value Value) Result

Success wraps a value in a success Result

func Transact

func Transact(input Value, steps ...Step) Result

Transact takes a Value and a list of Steps, performs the transaction described by those arguments, and returns the final Result of those sequential operations.

type Step

type Step func(Value) Result

Step is a function that can be used as a step in a Transaction.

Any given step is passed a Value, and it must return a Result.

type Transaction

type Transaction struct {
	// contains filtered or unexported fields
}

Transaction is a model that describes a multi-step process that can fail during any given step.

func NewTransaction

func NewTransaction(steps ...Step) *Transaction

NewTransaction returns a new Transaction.

If an optional list of steps is provided, the resulting transaction includes those steps.

Otherwise, the transaction is a no-op and the Step method should be used to add functionality.

func (*Transaction) Call

func (transaction *Transaction) Call(value Value) Result

Call takes a Value, which is then propagated through the list of steps associated with the transaction.

The result of each step is passed as input to its subsequent step, and the result of the final step is returned.

If any step results in a Failure, that failure is immediately returned and steps after the failing step are skipped.

func (*Transaction) Step

func (transaction *Transaction) Step(step Step) *Transaction

Step takes a step function and adds it to the list of steps to perform when the associated transaction is called.

For the sake of those who prefer method chains to individual calls, the modified transaction is returned.

type Value

type Value interface{}

Value is an interface that describes literally any value at all.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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