MCPcopy Create free account
hub / github.com/Primetalk/goio

github.com/Primetalk/goio @v0.3.7

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.3.7 ↗ · + Follow
457 symbols 1,839 edges 83 files 297 documented · 65% updated 3y agov0.3.7 · 2023-03-03★ 906 open issues

Browse by type

Functions 426 Types & classes 31
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Implementation of IO, Stream, Fiber using go1.18 generics

Coverage Codacy Badge Go Reference GoDoc Go Report Card Version Badge Go codecov

This library is an attempt to fill the gap of a decent generics streaming libraries in Go lang. The existing alternatives do not yet use Go 1.18 generics to their full potential.

The design is inspired by awesome Scala libraries cats-effect and fs2.

Functions

This package provides a few general functions that are sometimes useful.

  • fun.Const[A any, B any](b B)func(A)B
  • fun.ConstUnit[B any](b B) func(Unit)B
  • fun.Identity[A any](a A) A - Identity function returns the given value unchanged.
  • fun.Swap[A any, B any, C any](f func(a A)func(b B)C) func(b B)func(a A)C
  • fun.Curry[A any, B any, C any](f func(a A, b B)C) func(a A)func(b B)C
  • fun.Compose[A any, B any, C any](f func(A) B, g func (B) C) func (A) C - Compose executes the given functions in sequence.
  • fun.Memoize[A comparable, B any](f func(a A) B) func(A) B - Memoize returns a function that will remember the original function in a map. It's thread safe, however, not super performant.

There are also basic data structures - Unit, Pair and Either.

  • fun.Unit - type that has only one instance
  • fun.Unit1 - the instance of the Unit type
  • fun.Pair[A any, B any] - type that represents both A and B.
  • fun.PairV1[A any, B any](p Pair[A, B]) A - PairV1 returns the first element of the pair.
  • fun.PairV2[A any, B any](p Pair[A, B]) B - PairV2 returns the second element of the pair.
  • fun.PairBoth[A any, B any](p Pair[A, B]) (A, B) - PairBoth returns both parts of the pair.
  • fun.PairSwap[A any, B any](p Pair[A, B]) Pair[B, A] - PairSwap returns a pair with swapped parts.

For debug purposes it's useful to convert arbitrary data to strings.

  • fun.ToString[A any](a A) string - converts the value to string using Sprintf %v.

For compatibility with interface {}:

  • fun.CastAsInterface[A any](a A) interface {} - CastAsInterface casts a value of an arbitrary type as interface {}.
  • fun.Cast[A any](i Any) (a A, err error) - Cast converts interface {} to ordinary type A. It'a simple operation i.(A) represented as a function. In case the conversion is not possible, returns an error.
  • fun.UnsafeCast[A any](i interface {}) A - UnsafeCast converts interface {} to ordinary type A. It'a simple operation i.(A) represented as a function. In case the conversion is not possible throws a panic.

Is there a way to obtain a value of an arbitrary type?

  • fun.Nothing[A any]() A - This function can be used anywhere where type A is needed. It'll always fail if invoked at runtime.

Predicates

Predicate is a function with a boolean result type.

type Predicate[A any] func(A) bool
  • fun.IsEqualTo[A comparable](a A) Predicate[A] - IsEqualTo compares two arguments for equality.
  • fun.Not[A any](p Predicate[A]) Predicate[A] - Not negates the given predicate.

Option

A convenient data structure Option[A] that provides safe mechanisms to work with a potentially empty value.

  • option.None[A any]() Option[A] - None constructs an option without value.
  • option.Some[A any](a A) Option[A] - Some constructs an option with value.
  • option.Map[A any, B any](oa Option[A], f func(A) B) Option[B] - Map applies a function to the value inside option if any.
  • option.Fold[A any, B any](oa Option[A], f func(A) B, g func() B) (b B) - Fold transforms all possible values of OptionA using two provided functions.
  • option.Filter[A any](oa Option[A], predicate func(A) bool) Option[A] - Filter leaves the value inside option only if predicate is true.
  • option.FlatMap[A any, B any](oa Option[A], f func(A) Option[B]) Option[B] - FlatMap converts an internal value if it is present using the provided function.
  • option.Flatten[A any](ooa Option[Option[A]]) Option[A] - Flatten simplifies option of option to just Option[A].
  • option.Get[A any](oa Option[A]) A - Get is an unsafe function that unwraps the value from the option.
  • option.ForEach[A any](oa Option[A], f func(A)) - ForEach runs the given function on the value if it's available.
  • option.IsDefined[A any](oa Option[A]) bool - IsDefined checks whether the option contains a value.
  • option.IsEmpty[A any](oa Option[A]) bool - IsEmpty checks whether the option is empty.

Either

Data structure that models sum type. It can contain either A or B.

  • either.Either[A any, B any] - type that represents either A or B.

For Either there are a few helper functions:

  • either.Left[A any, B any](a A) Either[A, B]
  • either.Right[A any, B any](b B) Either[A, B]
  • either.IsLeft[A any, B any](eab Either[A, B]) bool
  • either.IsRight[A any, B any](eab Either[A, B]) bool
  • either.Fold[A any, B any, C any](eab Either[A, B], left func(A)C, right func(B)C) C - Fold pattern matches Either with two given pattern match handlers.
  • either.GetLeft[A any, B any](eab Either[A, B]) option.Option[A] - GetLeft returns left if it's defined.
  • either.GetRight[A any, B any](eab Either[A, B]) option.Option[B] - GetRight returns left if it's defined.

IO

IO encapsulates a calculation and provides a mechanism to compose a few calculations (flat map or bind).

Error handling

An arbitrary calculation may either return good result that one might expect, or fail with an error or even panic. In Go a recommended pattern is to represent failure explicitly in the function signature by returning both result and an error. There is a convention that says that when an error is not nil, the result should not be used.

While the requirement to explicitly deal with errors helps implementing robust systems a lot, it is often very verbose and it advocates the bad practice of explicit control flow via return:

a, err = foo()
if err != nil {
    return
}

Here a single semantically important action (foo) requires 4 lines of code, a branch and a return statement.

This style is also not very friendly to function composition. If you need to pass the result of foo further to bar, you'll have to first bow to error handling ceremony.

The composition of two consequtive calculations is fundamental to programming. There is even a mathematical model that studies the properties of composition of calculations.

From error handling perspective IO[A] provides the following features: - encapsulates a calculation that may return A or might fail; - it never panics, all panics are wrapped into errors and presented for handling; - provides convenient mechanisms for composing consequtive calculations (io.Map, io.FlatMap).

Interaction with outer world vs simple (pure) functions/calculations.

From compiler's perspective things that happen in the program can be either ordinary pure computations or modification of some state outside of the function. Pure computation is special, because it has the following benefits: - one can execute the same computation and receive exactly the same results; - except obtaining the result of the computation nothing is changed elsewhere; - it's much easier to reason about what is happening in the program made of pure computations (because nothing is happening apart from the computation itself).

The ability to understand and reason about programs is crucial to the ability of creation of somewhat complex programs.

Unfortunately all these nice and desired properties break when there are so called "side effects" - change of state, outer world interaction, ... - all things that make the computation to produce a different effect (and probably return different function results) even being called with the same arguments.

IO[A] provides a mechanism to arrange these side-effectful computations in such a way that it's easier to predict what is happening in the program. The main feature is the delay of actual effect execution until the late moment possible. A typical IO-based program does not perform any action until it is executed. It's often possible to construct the whole large computation for a complex program and only after that perform the execution.

Construction

To construct an IO one may use the following functions:

  • io.Lift[A any](a A) IO[A] - lifts a plain value to IO
  • io.LiftFunc[A any, B any](f func(A) B) func(A) IO[B] - LiftFunc wraps the result of function into IO.
  • io.Fail[A any](err error) IO[A] - lifts an error to IO
  • io.FromConstantGoResult[A any](gr GoResult[A]) IO[A] - FromConstantGoResult converts an existing GoResult value into an IO. Important! This is not for normal delayed IO execution. It cannot provide any guarantee for the moment when this go result was evaluated in the first place. This is just a combination of Lift and Fail.
  • io.Eval[A any](func () (A, error)) IO[A] - lifts an arbitrary computation. Panics are handled and represented as errors.
  • io.FromPureEffect(f func())IO[fun.Unit] - FromPureEffect constructs IO from the simplest function signature.
  • io.Delay[A any](f func()IO[A]) IO[A] - represents a function as a plain IO
  • io.Fold[A any, B any](io IO[A], f func(a A)IO[B], recover func (error)IO[B]) IO[B] - handles both happy and sad paths.
  • io.Recover[A any](io IO[A], recover func(err error)IO[A])IO[A] - handle only sad path and recover some errors to happy path.
  • io.OnError[A any](io IO[A], onError func(err error) IO[fun.Unit]) IO[A] - OnError executes a side effect when there is an error.
  • io.Retry[A any, S any](ioa IO[A], strategy func(s S, err error) IO[option.Option[S]], zero S) IO[A] - Retry performs the same operation a few times based on the retry strategy.
  • io.RetryS[A any, S any](ioa IO[A], strategy func(s S, err error) IO[option.Option[S]], zero S) IO[fun.Pair[A, S]] - RetryS performs the same operation a few times based on the retry strategy. Also returns the last state of the error-handling strategy.
  • io.RetryStrategyMaxCount(substring string) func(s int, err error) IO[option.Option[int]] - RetryStrategyMaxCount is a strategy that retries n times immediately.

Manipulation

The following functions could be used to manipulate computations:

  • io.FlatMap[A any, B any](ioa IO[A], f func(A)IO[B]) IO[B]
  • io.AndThen[A any, B any](ioa IO[A], iob IO[B]) IO[B] - AndThen runs the first IO, ignores it's result and then runs the second one.
  • io.Map[A any, B any](ioA IO[A], f func(a A) B) IO[B]
  • io.MapErr[A any, B any](ioA IO[A], f func(a A) (B, error)) IO[B]
  • io.MapConst[A any, B any](ioA IO[A], b B) IO[B] - MapConst ignores the result and replaces it with the given constant.
  • io.Sequence[A any](ioas []IO[A]) (res IO[[]A])
  • io.SequenceUnit(ious []IO[Unit]) (res IOUnit)
  • io.Unptr[A any](ptra *A) IO[A] - retrieves the value at pointer. Fails if nil
  • io.Wrapf[A any](io IO[A], format string, args...interface{}) IO[A] - wraps an error with additional context information
  • io.Finally[A any](io IO[A], finalizer IO[fun.Unit]) IO[A] - Finally runs the finalizer regardless of the success of the IO. In case finalizer fails as well, the second error is printed to log.
  • io.Ignore[A any](ioa IO[A]) IOUnit - Ignore throws away the result of IO.
  • io.MapSlice[A any, B any](ioas IO[[]A], f func(a A) B) IO[[]B] - MapSlice converts each element of the slice inside IO[[]A] using the provided function that cannot fail.

To and from GoResult - allows to handle both good value and an error:

  • io.FoldToGoResult[A any](io IO[A]) IO[GoResult[A]] - FoldToGoResult converts either value or error to go result. It should never fail.
  • io.UnfoldGoResult[A any](iogr IO[GoResult[A]]) IO[A] - UnfoldGoResult represents GoResult back to ordinary IO.

Sometimes there is a need to perform some sideeffectful operation on a value. This can be achieved with Consumer[A].

// Consumer can receive an instance of A and perform some operation on it.
type Consumer[A any] func(A) IOUnit
  • io.CoMap[A any, B any](ca Consumer[A], f func(b B) A) Consumer[B] - CoMap changes the input argument of the consumer.

Execution

To finally run all constructed computations one may use UnsafeRunSync or ForEach:

  • io.UnsafeRunSync[A any](ioa IO[A])
  • io.ForEach[A any](io IO[A], cb func(a A))IO[fun.Unit] - ForEach calls the provided callback after IO is completed.
  • io.RunSync[A any](io IO[A]) GoResult[A] - RunSync is the same as UnsafeRunSync but returns GoResult.

Auxiliary functions

  • io.Memoize[A comparable, B any](f func(a A) IO[B]) func(A) IO[B] - Memoize returns a function that will remember the original function in a map. It's thread safe, however, not super performant.

Implementation details

IO might be implemented in various ways. Here we implement IO using continuations. A simple step in the constructed IO program might either complete (returning a result or an error), or return a continuation - another execution of the same kind. In order to obtain result we should execute the returned function. Continuations help avoiding deeply nested stack tr

Extension points exported contracts — how you extend this code

Core symbols most depended-on inside this repo

Shape

Function 402
Method 24
Struct 14
FuncType 7
Interface 5
TypeAlias 5

Languages

Go100%

Modules by API surface

stream/stream.go33 symbols
slice/slice.go33 symbols
io/io.go26 symbols
slice/slice_test.go22 symbols
stream/stream_test.go17 symbols
io/fiber.go15 symbols
stream/construct.go13 symbols
stream/pool.go12 symbols
stream/backpressure_channels.go12 symbols
resource/resource.go12 symbols
option/option.go12 symbols
stream/execution.go11 symbols

For agents

$ claude mcp add goio \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page