An Algebraic Effect System for Golang.

More docs

+264 -166
+1 -1
abort/abort.go
··· 7 7 type AbortFx[E, V any] = fx.Fx[AbortAb[E], V] 8 8 9 9 func Abort[V, E any](e E) AbortFx[E, V] { 10 - return fx.Map(fx.Handle[AbortFn[E]](e), func(_ fx.Nil) V { 10 + return fx.Map(fx.Suspend[AbortFn[E]](e), func(_ fx.Nil) V { 11 11 panic("unhandled abort effect") 12 12 }) 13 13 }
+1 -1
abort/abort_test.go
··· 30 30 panic("BUG: mapping on aborted eff should be unreachable") 31 31 }) 32 32 // Another way of applying the abort handler. 33 - x := fx.ProvideLeft(fx.Handle[ResultHn[Ok, Err]](e), AbortHandler[Ok, Err]) 33 + x := fx.ProvideLeft(fx.Suspend[ResultHn[Ok, Err]](e), AbortHandler[Ok, Err]) 34 34 var r Result[Ok, Err] = fx.Eval(x) 35 35 val, err := r() 36 36 if *err != "ahhhh" {
+5 -1
book/src/SUMMARY.md
··· 3 3 - [Introduction](./introduction.md) 4 4 - [Concepts Tour](./concepts.md) 5 5 - [Basic Effects](./basics.md) 6 - - [And Requirements](./requirements.md) 6 + - [Effect Requirements](./requirements.md) 7 + - [Shuffling Ands](./requirements/shuffling.md) 8 + - [Providers](./requirements/providers.md) 9 + - [Context Requirements](./requirements/context.md) 10 + - [Functional Requirements](./requirements/apply.md) 7 11 - [Effect Requests](./requests.md) 8 12 - [Handlers](./handlers.md) 9 13 - [Example HTTP app](./http.md)
+8 -2
book/src/abort.md
··· 1 1 # Abort 2 2 3 3 `Abort[V, E](E)` aborts a computation of `V` with `E`. 4 - The abort Handler transforms these effects into `Result[V, E]`. 4 + 5 + When aborted, a computation is *halted*, meaning that any `Map` or `FlatMap` operation over it will not be executed. 5 6 6 - - Implementation [abort.go](https://github.com/vic/fx.go/blob/main/abort/abort.go) 7 + The `AbortHandler` replaces aborted effects (those computations where Abort was called) with a `Result` with `E`. Otherwise if the effect was never aborted, a `Result` with `V` is returned. 8 + 9 + 10 + - Implementation 11 + - [abort.go](https://github.com/vic/fx.go/blob/main/abort/abort.go) 12 + - [result.go](https://github.com/vic/fx.go/blob/main/abort/result.go) 7 13 - Tests [abort_test.go](https://github.com/vic/fx.go/blob/main/abort/abort_test.go)
+12 -14
book/src/basics.md
··· 6 6 7 7 The most basic of all possible effects are *Immediate* effects. These are effects of type `Fx[Nil, V]`, meaning that they have no ability requirements (`Nil`) and evaluate to a value (`V`). 8 8 9 - Immediate effects are created using the `Pure(*V) Fx[Nil, V]` function. The name `Pure` relates to the convential naming on effect systems for moving (lifting) a value `V` into the domain of `Fx`. 9 + Immediate effects are created using the `Pure(V) Fx[Nil, V]` function. 10 10 11 - As you can see, `Pure(*V)` takes a pointer to an *existing* value `V`, that means that a pure effect just holds a pointer to an already known value instead of trying to compute it. 11 + As you can see, `Pure(V)` takes an *existing* value `V`, that means that a pure effect just holds an already known value instead of trying to compute it. 12 12 13 - The pointer given to `Pure` can be retrieved by using `Eval(Fx[Nil, V]) *V`. Only effects that have no requirements (`Nil`) can be evaluated. 13 + The value given to `Pure` can be retrieved by using `Eval(Fx[Nil, V]) V`. Only effects that have no requirements (`Nil`) can be evaluated. 14 14 15 15 ```go 16 16 import ( fx "github.com/vic/fx.go" ) ··· 18 18 func PureExample() { 19 19 v := "Hello World" 20 20 // Code annotated with types for clarity 21 - var effect fx.Fx[fx.Nil, string] = fx.Pure(&v) 22 - var result *string = fx.Eval(effect) 23 - assert(*result == v) 21 + var effect fx.Fx[fx.Nil, string] = fx.Pure(v) 22 + var result string = fx.Eval(effect) 23 + assert(result == v) 24 24 } 25 25 ``` 26 26 ··· 41 41 // Code annotated with types for clarity 42 42 var effect fx.Fx[string, int] = fx.Func(LengthOfString) 43 43 var requirement string = "Hello World" 44 - var provided fx.Fx[fx.Nil, int] = fx.Provide(effect, &requirement) 45 - var result *int = fx.Eval(provided) 46 - assert(*result == len(requirement)) 44 + var provided fx.Fx[fx.Nil, int] = fx.Provide(effect, requirement) 45 + var result int = fx.Eval(provided) 46 + assert(result == len(requirement)) 47 47 } 48 48 ``` 49 49 50 50 From the code above: 51 51 52 52 - `Func(func (S) V)` produces a _pending_ effect of type `Fx[S, V]`. 53 - - `Provide(Fx[S, V], *S)` discharges the `S` requirement and returns `Fx[Nil, V]`. 53 + - `Provide(Fx[S, V], S)` discharges the `S` requirement and returns `Fx[Nil, V]`. 54 54 Note that *no computation* is performed in this step. `Fx[Nil, V]` is still a description of a program, and `V` has not been computed yet, nor any side-effect has been performed. 55 - - `Eval(Fx[Nil, V])` will actually perform the computation of `V`, since all `non-Nil` requirements have already , the computation can be performed.been provided. 55 + - `Eval(Fx[Nil, V])` will actually perform the computation of `V`. Since all `non-Nil` requirements have already been provided, the computation can be run. 56 56 57 57 58 - These two are the most basic effects in `Fx.go`, and using them we have shown the essential `fx.Provide` and `fx.Eval` APIs. 59 - 60 - More interesting effects will be presented as we explore the topics of Handlers and combinators. 58 + These two are the most basic effects in `Fx.go`. More interesting effects will be presented as we explore the topics of effect Rquests and Handlers.
-1
book/src/chapter_1.md
··· 1 - # Chapter 1
+2 -2
book/src/concepts.md
··· 17 17 18 18 ## Abilities 19 19 20 - In `Fx[S, V]`, `S` is said to be the *Ability* (sometimes also referred as the _set_ of *Abilities*, *Capabilities* or *Requirements*) that are needed for computing `V`. 20 + In `Fx[S, V]`, `S` is said to be the *Ability* (sometimes also referred as the _set_ of *Abilities*, *Capabilities*, *Effect Environment* or *Effect Requirements*) that are needed for computing `V`. 21 21 22 22 Abilities describe the external resources that would be needed, as well as the _side-effects_ that are possible while computing `V`. 23 23 ··· 35 35 36 36 A *Handler* for the `S` ability is a particular _interpretation_ of what `S` means. 37 37 38 - It is the Handler of `S` the only _side-effectful_ portion of your programs. It is possible and quite common to have different handlers (interpretations) for the same Ability, and each Handler decides _how/when_ to perform world-modifying _side-effects_. 38 + Handlers are the only _side-effectful_ portion of your programs. It is possible, and quite common, to have different handlers (interpretations) for the same Ability, and each Handler decides _how/when_ to perform world-modifying _side-effects_. 39 39 40 40 For example, for an _http-request_ ability you can have a *test-handler* that just mock responses to fixed values so that you can easily assert on known values on your tests. You could also have a *live-handler* that actually performs requests via the network for production runs.
+1
book/src/contributing.md
··· 1 1 # Contributing 2 2 3 + Yes, please. All PRs are welcome, documentation, code improvements, ideas or suggestions, ways to improve this book prose, etc.
+28 -24
book/src/handlers.md
··· 1 1 # Handlers 2 2 3 - A _Handler_ is an effect transformer function of type `func(Fx[R, U]) Fx[S, V]`. Handlers are free to change the effect requirements, they tipically reduce the requirement set, but they could also introduce new requirements. Also the result value can be changed or be the same. 3 + A _Handler_ is an effect transformation function of type `func(Fx[R, U]) Fx[S, V]`. 4 + 5 + Handlers are free to change the effect requirements, they tipically reduce the requirement set, but they could also introduce new requirements. They can also change or keep the result type. 4 6 5 7 ## Handling an effect 6 8 7 9 Lets re-write our previous "length of string" function as a Handler. 8 10 9 11 ```go 10 - type LenFn = func(string) fx.Fx[fx.Nil, int] 12 + type LenFn func(string) Fx[Nil, int] 11 13 12 14 // Code is type annotated for clarity 13 - var lenFx fx.Fx[fx.And[LenFn, fx.Nil], int] = fx.Suspend[LenFn]("hello") 15 + var lenFx Fx[And[LenFn, Nil], int] = fx.Suspend[LenFn]("hello") 14 16 15 - // type is not needed but just added for clarity. 16 - type LenHn = func(fx.Fx[fx.And[LenFn, fx.Nil], int]) fx.Fx[fx.Nil, int] 17 + // type of a handler. not needed but added for clarity. 18 + type LenHn func(Fx[And[LenFn, Nil], int]) Fx[Nil, int] 17 19 18 20 var handler LenHn = fx.Handler(func(s string) fx.Fx[fx.Nil, int] { 19 - truth := len(s) 20 - return fx.Pure(&truth) 21 + return fx.Pure(len(s)) 21 22 }) 22 23 23 - // apply the handler to lenFx 24 + // apply the handler directly to lenFx 24 25 var x *int = fx.Eval(handler(lenFx)) 25 26 assert(*x == 5) 26 27 ``` 27 28 28 - As you might guess, `fx.Handler` is just a wrapper for `ProvideLeft(Fx[And[Fn, S], O], *Fn) Fx[S, O]` where `Fn = func(I) Fx[S, O]`, an request-effect function. 29 + As you might guess, `fx.Handler` is just a wrapper for `ProvideLeft(Fx[And[Fn, S], O], Fn) Fx[S, O]` where `Fn = func(I) Fx[S, O]`, an request-effect function. 29 30 30 31 31 32 ## Requesting Handlers (effect-transformers) from the environment. 32 33 33 - Of course, you can also request that a particular effect transformer (Handler) be present as a requirement of some computation. In this way the handler is provided only once but can be applied anywhere it is needed inside the program. 34 + Of course, you can also request that a particular effect transformer (Handler) be present as a requirement of some computation. This way the handler is provided only once but can be applied anywhere it is needed inside the program. 34 35 35 36 ```go 36 37 // Same examples as above with some more types for clarity 37 - type LenFn = func(string) fx.Fx[fx.Nil, int] 38 - type LenFx = fx.Fx[fx.And[LenFn, fx.Nil], int] 39 - type LenHn = func(LenFx) fx.Fx[fx.Nil, int] 40 38 41 - var lenFx LenFx = fx.Suspend[LenFn]("hello") 39 + // effect-request function type. 40 + type LenFn func(string) Fx[Nil, int] 41 + // effect handler type 42 + type LenHn = func(Fx[And[LenFn, Nil], int]) Fx[Nil, int] 42 43 43 - // Request that an implementation of LenHn transformer 44 - // is available in the environment and it be applied to lenFx. 45 - var lenAp Fx[And[LenHn, Nil], int] = fx.Apply[LenHn](lenFx) 44 + // effect ability 45 + type LenAb = And[LenHn, Nil] 46 + // effect type producing V 47 + type LenFx[V any] = fx.Fx[LenAb, V] 48 + 49 + // Same as: Suspend[LenHn](Suspend[LenFn](input)) 50 + var lenFx LenFx = fx.Handle[LenHn]("hello") 46 51 47 52 var handler LenHn = fx.Handler(func(s string) fx.Fx[fx.Nil, int] { 48 - truth := len(s) 49 - return fx.Pure(&truth) 53 + return fx.Pure(len(s)) 50 54 }) 51 55 52 - // Now instead of applying the handler directly to each effect it 53 - // must handle, we provide it into the environment. 54 - var provided Fx[Nil, int] = fx.ProvideLeft(lenAp, &handler) 55 - val x *int = fx.Eval(provided) 56 - assert(*x == 5) 56 + // Now instead of applying the handler directly to each effect 57 + // we provide it into the environment. 58 + var provided Fx[Nil, int] = fx.ProvideLeft(lenFx, handler) 59 + val x int = fx.Eval(provided) 60 + assert(x == 5) 57 61 ```
+11 -12
book/src/http.md
··· 20 20 // For simplicity out response is just an string: The response body. 21 21 type HttpRs string 22 22 23 - // Type of the request-effect function. This will be implemented 24 - // by some handler to provide actual responses. 23 + // Type of the effect-request function. 24 + // This will be implemented by some handler to provide actual responses. 25 25 // 26 26 // fx.Nil in the result type indicates that our Http ability does 27 27 // not requires any other ability. 28 - type HttpFn = func(HttpRq) fx.Fx[fx.Nil, HttpRs] 28 + type HttpFn func(HttpRq) fx.Fx[fx.Nil, HttpRs] 29 29 30 - // Type of the Handler (effect transformer) that handles requests. 30 + // Type of the Http Handler that discharges effect requirements 31 31 type HttpHn = func(fx.Fx[fx.And[HttpFn, fx.Nil], HttpRs]) fx.Fx[fx.Nil, HttpRs] 32 32 33 - // Type of an effect ability (requires the handler) and additional abilities. 33 + // Type of the Http Ability: the handler and aditional abilities. 34 34 type HttpAb = fx.And[HttpHn, fx.Nil] 35 35 36 36 // An http effect that produces V ··· 38 38 39 39 // An effect of HTTP GET requests. 40 40 func Get(url HttpRq) HttpFx[HttpRs] { 41 - // Request[HttpHn](input) is the same as Apply[HttpHn](Suspend[HttpFn](input)) 42 - return fx.Request[HttpHn](url) 41 + return fx.Handle[HttpHn](url) 43 42 } 44 43 45 44 // A program that computes the respose length of https://example.org ··· 50 49 } 51 50 52 51 func TestProgram(t *testing.T) { 53 - httpHandler := fx.Handler(func(r HttpRq) fx.Fx[fx.Nil, HttpRs] { 52 + var httpHandler HttpHn = fx.Handler(func(r HttpRq) fx.Fx[fx.Nil, HttpRs] { 54 53 mock := HttpRs("example") 55 54 return fx.Pure(&mock) 56 55 }) 57 - var provided fx.Fx[fx.Nil, int] = fx.ProvideLeft(Program(), &httpHandler) 58 - var result *int = fx.Eval(provided) 59 - if *result != len("example") { 60 - t.Errorf("Unexpected result %v", *result) 56 + var provided fx.Fx[fx.Nil, int] = fx.ProvideLeft(Program(), httpHandler) 57 + var result int = fx.Eval(provided) 58 + if result != len("example") { 59 + t.Errorf("Unexpected result %v", result) 61 60 } 62 61 } 63 62
+10 -12
book/src/requests.md
··· 4 4 5 5 A function of type `func(I) Fx[S, O]` is said to take an _effect-request_ `I` and produce an *suspended* effect `Fx[S, O]`. 6 6 7 - For example, the function `func(HttpReq) Fx[HttpService, HttpRes]` states that given an `HttpReq` request you can obtain an `HttpRes` response, *provided* that an `HttpService` is available. 7 + An example, is the function `func(HttpReq) Fx[HttpService, HttpRes]` we saw in the [previous](./requirements/apply.html) chapter. 8 8 9 9 Using the "length of string" example of the previous chapters, we can use it to model an effect request: 10 10 ··· 20 20 Different implementations of `LenFn` can be provided to the `lenFx` effect. 21 21 22 22 ```go 23 - var bad LenFn = func(_ string) fx.Fx[fx.Nil, int] { 24 - lies := 42 25 - return fx.Pure(&lies) 23 + var lies LenFn = func(_ string) fx.Fx[fx.Nil, int] { 24 + return fx.Pure(42) 26 25 } 27 - var good LenFn = func(s string) fx.Fx[fx.Nil, int] { 28 - truth := len(s) 29 - return fx.Pure(&truth) 26 + var truth LenFn = func(s string) fx.Fx[fx.Nil, int] { 27 + return fx.Pure(len(s)) 30 28 } 31 29 32 - var x *int = fx.Eval(fx.ProvideLeft(lenFx, &bad)) 33 - assert(*x == 42) 30 + var x int = fx.Eval(fx.ProvideLeft(lenFx, lies)) 31 + assert(x == 42) 34 32 35 - var y *int = fx.Eval(fx.ProvideLeft(lenFx, &good)) 36 - assert(*y == 5) 33 + var y int = fx.Eval(fx.ProvideLeft(lenFx, truth)) 34 + assert(y == 5) 37 35 ``` 38 36 39 - Notice that by delaying which implementation of `LenFn` is used, the `lenFx` program description includes the effect-request `"hello"` and knows the general form of its response `Fx[Nil, int]`, but knows nothing about which particular interpretation of `LenFn` will be used. 37 + Notice that by delaying which implementation of `LenFn` is used, the `lenFx` program description includes the effect request `"hello"` and knows the general form of its response `Fx[Nil, int]`, but knows nothing about which particular interpretation of `LenFn` will be used.
+9 -64
book/src/requirements.md
··· 2 2 3 3 So far, we have seen that an effect `Fx[S, V]` can have `S` be `Nil` for effects that can be evaluated right away and non-`Nil` for those pending effects that still need to be provided some value. 4 4 5 - In this chapter we will talk about Composite requirement types using the `And` type. And how different types of functions/effects can represent the very same computation. We also look at similarities with higher-order functions in functional-programming and how rotating or re-arranging effect requirements is indifferent in `Fx.go`. Finally, we show some the`And*` and `Provide*` combinators that can help you reshape your effect requirements. 5 + In this chapter we will talk about aggregating requirements using the `And` type. And how different types of functions/effects can represent the very same computation. We also look at similarities with higher-order functions in functional-programming and how rotating or re-arranging effect requirements is indifferent in `Fx.go`. Finally, we show some the`And*` and `Provide*` combinators that can help you reshape your effect requirements. 6 6 7 - ## `And[A, B]` composed Requirement Types 7 + ## Composite requirements 8 8 9 9 Using the same "length of string" function from the previous chapters, we can describe it in different ways. 10 10 11 11 ```go 12 12 // This is an strange way of writing `func(string) int`. 13 - // But this shape is used to understand the types bellow. 13 + // But this shape can help to understand the types bellow. 14 14 // 15 - // Focus on what are the requirements needed to perform 16 - // a computation, more than the shape of the type. 15 + // Focus your attention on the requirements that are 16 + // needed for a computing a value. 17 17 // 18 18 // In particular, note that `Fx[Nil, V]` is like a `func() V` 19 - func LenghtOfString(s string) func() int { 19 + func LengthOfString(s string) func() int { 20 20 return func() int { return len(s) } 21 21 } 22 22 23 + // The type of LengthOfString expressed as an effect request. 23 24 type LenFn = func(string) Fx[Nil, int] 24 25 ``` 25 26 ··· 34 35 35 36 The last three examples represent nested effects and are equivalent to functions of arity > 1 or functions that return functions. 36 37 37 - `And[A, B]` is the requirement for both `A` and `B` abilities. Notice on the last two examples, that they have their components swapped, however, its important to note that in `Fx.go`, _the *order* of the abilities on the requirement does not matter_ and can be freely swapped/joined/unjoined. More on this when we talk about `And*` combinators. 38 + `And[A, B]` is the requirement for both `A` and `B` abilities. Notice in the last two examples, that their components are swapped. It is important to note that in `Fx.go`, _the *order* of the abilities on And requirements does not matter_ and they can be freely swapped/joined/unjoined. More on this when we talk about `And*` combinators. 38 39 39 40 Also, note that `And[A, Nil]` is equivalent to just `A`. All of these types represent the same type of computation and an effect can be transformed to any of those types freely. 40 41 ··· 59 60 - `Fx[int, Fx[string, int]]` 60 61 - `Fx[And[int, string], int]` 61 62 62 - An important thing to note is that in `Fx`, the *requirements are identified by their type* and not by their name, so they can be freely re-arranged or provided in any order. Note that `And[X, X]` is equivalent to just a single `X` requirement, and that `And[And[X, Y], And[Y, X]]` is also equivalent to `And[X, Y]`. 63 - 63 + Note that `And[X, X]` is equivalent to just a single `X` requirement, and that `And[And[X, Y], And[Y, X]]` is also equivalent to `And[X, Y]`. 64 64 65 - ## `And*` Combinators. 66 - 67 - There are some functions (more will be added as they are found useful) that help you re-arrange `And`ed effect requirements: 68 - 69 - ```go 70 - // Since `And[A, A]` is equivalent to just `A`. 71 - // Used to collapse Nil requirements just before evaluation. 72 - func AndCollapse(Fx[And[A, A], V]) Fx[A, V] 73 - 74 - // Ands S with Nil in the effect requirements 75 - func AndNil(Fx[S, V]) Fx[And[S, Nil], V] 76 - 77 - // Swaps A and B. Note: this has no impact on how computation is 78 - // performed, since requirements can be freely re-arranged. 79 - func AndSwap(Fx[And[A, B], V]) Fx[And[B, A], V] 80 - 81 - 82 - // FlatMaps the inner effect into the outer by 83 - // Anding their requirements. 84 - func AndJoin(Fx[A, Fx[B, V]]) Fx[And[A, B], V] 85 - 86 - // Reverse of Join 87 - func AndDisjoin(Fx[And[A, B], V]) Fx[A, Fx[B, V]] 88 - 89 - ``` 90 - 91 - ## `Provide*` Combinators. 92 - 93 - These functions are used to provide requirements into effects. The result is another effect (no computation has been performed yet) but with less requirements. 94 - 95 - ```go 96 - // Discharges the single S requirement. 97 - func Provide(Fx[S, V], *S) Fx[Nil, V] 98 - 99 - 100 - // Discharges the requirement of A by providing it. 101 - func ProvideLeft(Fx[And[A, B], V], *A) Fx[B, V] 102 - 103 - // Discharges the requirement of B by providing it. 104 - func ProvideRight(Fx[And[A, B], V], *B) Fx[A, V] 105 - 106 - // Discharges both A and B 107 - func ProvideBoth(Fx[And[A, B], V], *A, *B) Fx[Nil, V] 108 - 109 - 110 - 111 - // Provides A, the `CAAR` of the requirements list. 112 - func ProvideA(Fx[And[And[A, C], And[B, D]], V], *A) Fx[And[C, And[B, D]], V] 113 - 114 - // Provides B, the `CADR` of the requirements list. 115 - func ProvideB(Fx[And[And[A, C], And[B, D]], V], *B) Fx[And[And[A, C], D], V] 116 - 117 - // Provides A and B, the `CAAR` and `CADR` of the requirements list. 118 - func ProvideAB(Fx[And[And[A, C], And[B, D]], V], *A, *B) Fx[And[C, D], V] 119 - ``` 120 65
+51
book/src/requirements/apply.md
··· 1 + # Functional Requirements 2 + 3 + As we have seen in the previous [Context](context.html) chapter, you can ask for any type to be part of an effect's environment. 4 + 5 + You can also ask for functions of some type to be present and use them in your program descriptions without knowing their exact implementation. 6 + 7 + For example, suppose we ask for a function that takes an integer. And then apply it to a value. 8 + 9 + ```go 10 + // The type of the function from int to int 11 + type OnInt func(int) nit 12 + 13 + // Apply OnInt(12) 14 + var a Fx[OnInt, int] = Map(Ctx[OnInt](), func(f OnInt) int { 15 + return f(12) 16 + }) 17 + 18 + // fx.Apply is just an abbreviation of the previous code: 19 + // It takes the type of a unary function and the value to apply. 20 + var b Fx[OnInt, int] = Apply[OnInt](12) 21 + ``` 22 + 23 + Because `OnInt` is part of the environment, we only know its signature (its request and response type), but not the actual implementation of it. This way, different implementations of `OnInt` can be provided later. 24 + 25 + 26 + Now, suppose you have the following code: 27 + 28 + ```go 29 + type HttpRq string 30 + var url = HttpRq("https://example.org") 31 + 32 + // `Http` is the type of an *effect request* function. 33 + // it takes a plain value and creates an effect. 34 + type Http func(HttpRq) Fx[HttpService, HttpRs] 35 + 36 + // NOTE: applying to Http, creates a nested effect 37 + var a Fx[Http, Fx[HttpService, HttpRs]] = Apply[Http](url) 38 + 39 + // Flatenning creates an Anded environment. 40 + var b Fx[And[Http, HttpService], HttpRs] = AndJoin(a) 41 + 42 + 43 + // Same as previous two lines: 44 + var x Fx[And[Http, HttpService], HttpRs] = Suspend[Http](url) 45 + ``` 46 + 47 + `fx.Suspend` is an abbreviation of `AndJoin(Apply[Http](url))` for *effect requests* (functions returning an effect) 48 + 49 + 50 + This is the principle behind *suspended effect application* in `Fx.go` and is a fundamental part when we talk about effect [Requests](../requests.html) and [Handlers](../handlers.html). 51 +
+24
book/src/requirements/context.md
··· 1 + # Context Requirements 2 + 3 + The `Ctx[V]() Fx[V, V]` function creates an effect that requires some value `V` as part of the environment and evaluates to `V`. 4 + 5 + It can be used to request the presence of some services (interfaces or collections of methods that produce related effects) or more generally, an evidence that a value `V` is part of the environment. 6 + 7 + In the following example we simply map over an string to compute its length. 8 + 9 + ```go 10 + var eff Fx[string, int] = Map(Ctx[string](), LengthOfString) 11 + var result int = Eval(Provide(eff, "hello")) 12 + assert(result == len("hello")) 13 + ``` 14 + 15 + Calling the `Const[S](V) Fx[S, V]` is like maping a context over a constant function. 16 + As you might have guessed, `Pure` is defined in terms of `Const`. 17 + 18 + ```go 19 + var a Fx[string, int] = Map(Ctx[string](), func(_ string) int { return 42 }) 20 + var b Fx[string, int] = Const[string](42) 21 + 22 + var c Fx[Nil, int] = Pure(22) 23 + var d Fx[Nil, int] = Const[Nil](22) 24 + ```
+36
book/src/requirements/providers.md
··· 1 + # Providing Requirements 2 + 3 + *Effect requirements can be provided in any order* with no impact in the evaluation of the program. 4 + 5 + 6 + ## `Provide*` Combinators. 7 + 8 + These functions are used to eliminate requirements from effects. Only when `Nil` is the only remaining requirement, the effect can be evaluated. 9 + 10 + The result of provide functions will always be another effect, meaning that no computation has been performed yet, nor any side-effect produced. The result is just another effect but with less requirements. 11 + 12 + ```go 13 + // Discharges the single S requirement. 14 + func Provide(Fx[S, V], S) Fx[Nil, V] 15 + 16 + 17 + // Discharges the requirement of A by providing it. 18 + func ProvideLeft(Fx[And[A, B], V], A) Fx[B, V] 19 + 20 + // Discharges the requirement of B by providing it. 21 + func ProvideRight(Fx[And[A, B], V], B) Fx[A, V] 22 + 23 + // Discharges both A and B 24 + func ProvideBoth(Fx[And[A, B], V], A, B) Fx[Nil, V] 25 + 26 + 27 + 28 + // Provides A, the first part of the left And. 29 + func ProvideFirstLeft(Fx[And[And[A, C], And[B, D]], V], A) Fx[And[C, And[B, D]], V] 30 + 31 + // Provides B, the first part of the right And. 32 + func ProvideFirstRight(Fx[And[And[A, C], And[B, D]], V], B) Fx[And[And[A, C], D], V] 33 + 34 + // Provides A and B, the first part of both Ands. 35 + func ProvideFirsts(Fx[And[And[A, C], And[B, D]], V], A, B) Fx[And[C, D], V] 36 + ```
+34
book/src/requirements/shuffling.md
··· 1 + # Shuffling `And`ed requirements. 2 + 3 + An important thing to note is that in `Fx.go`, *requirements are identified by their type* and not by name or position as in regular functions. 4 + 5 + *Effect requirements can be freely re-arranged* with no impact in the meaning of the program. 6 + 7 + 8 + ## `And*` combinators. 9 + 10 + There are some functions (more will be added as they are found useful) that help you re-arrange `And`ed effect requirements. 11 + 12 + Shuffling And requirements is useful because you might want to provide some requirements first than others even if they are not in the head position of the requirement list. 13 + 14 + ```go 15 + // Since `And[A, A]` is equivalent to just `A`. 16 + // Used to collapse Nil requirements just before evaluation. 17 + func AndCollapse(Fx[And[A, A], V]) Fx[A, V] 18 + 19 + // Ands S with Nil in the effect requirements 20 + func AndNil(Fx[S, V]) Fx[And[S, Nil], V] 21 + 22 + // Swaps A and B. Note: this has no impact on how computation is 23 + // performed, since requirements can be freely re-arranged. 24 + func AndSwap(Fx[And[A, B], V]) Fx[And[B, A], V] 25 + 26 + 27 + // FlatMaps the inner effect into the outer by 28 + // Anding their requirements. 29 + func AndJoin(Fx[A, Fx[B, V]]) Fx[And[A, B], V] 30 + 31 + // Reverse of Join 32 + func AndDisjoin(Fx[And[A, B], V]) Fx[A, Fx[B, V]] 33 + 34 + ```
+5 -2
book/src/rw.md
··· 2 2 3 3 A `Reader[T]` allows to read values of type `T` from the environment while `Writer[T]` allows to set them. 4 4 5 - - Implementation [rw.go](https://github.com/vic/fx.go/blob/main/rw/rw_test.go) 5 + - Implementation 6 + - [read.go](https://github.com/vic/fx.go/blob/main/rw/read.go) 7 + - [write.go](https://github.com/vic/fx.go/blob/main/rw/write.go) 8 + 6 9 - Tests [rw_test.go](https://github.com/vic/fx.go/blob/main/rw/rw_test.go) 7 10 8 - Read and Write Handlers take an effectful operation that can modify the external world. See `rh` and `wh` in `TestReadWrite`. 11 + Read and Write Handlers take an effectful operation that can modify the external world. See `TestReadWrite`.
+22 -26
fx.go
··· 14 14 var PureNil FxNil = Pure(PNil) 15 15 16 16 func Pure[V any](v V) FxPure[V] { 17 - return value[Nil](v) 18 - } 19 - 20 - func value[S, V any](v V) Fx[S, V] { 21 - return func() (immediate[V], suspended[S, V]) { 22 - return func() V { return v }, nil 23 - } 17 + return Const[Nil](v) 24 18 } 25 19 26 20 func Func[S, V any](f func(S) V) Fx[S, V] { ··· 51 45 } 52 46 imm, sus := x() 53 47 if imm != nil { 54 - return value[S](imm()) 48 + return Const[S](imm()) 55 49 } 56 50 return func() (immediate[V], suspended[S, V]) { 57 51 return nil, func(s S) Fx[S, V] { ··· 87 81 } 88 82 imm, sus := e() 89 83 if imm != nil { 90 - return value[R](imm()) 84 + return Const[R](imm()) 91 85 } 92 86 return func() (immediate[V], suspended[R, V]) { 93 87 return nil, func(r R) Fx[R, V] { ··· 103 97 return cont(id, func(u immediate[U]) Fx[S, V] { 104 98 v := f(u()) 105 99 return cont(id, func(v immediate[V]) Fx[S, V] { 106 - return value[S](v()) 100 + return Const[S](v()) 107 101 })(v) 108 102 }) 109 103 } ··· 111 105 func MapT[S, V, U any](f func(V) U) func(Fx[S, V]) Fx[S, U] { 112 106 return MapM(func(v V) Fx[S, U] { 113 107 u := f(v) 114 - return value[S](u) 108 + return Const[S](u) 115 109 }) 116 110 } 117 111 ··· 134 128 } 135 129 return cont(a, func(u immediate[U]) Fx[And[A, B], V] { 136 130 return cont(b, func(v immediate[V]) Fx[And[A, B], V] { 137 - return value[And[A, B]](v()) 131 + return Const[And[A, B]](v()) 138 132 })(f(u())) 139 133 }) 140 134 } ··· 145 139 return s 146 140 } 147 141 return cont(fst, func(v immediate[V]) Fx[And[S, Nil], V] { 148 - return value[And[S, Nil]](v()) 142 + return Const[And[S, Nil]](v()) 149 143 })(e) 150 144 } 151 145 ··· 158 152 return ab 159 153 } 160 154 return cont(swp, func(v immediate[V]) Fx[And[B, A], V] { 161 - return value[And[B, A]](v()) 155 + return Const[And[B, A]](v()) 162 156 })(e) 163 157 } 164 158 ··· 170 164 return func() (immediate[Fx[B, V]], suspended[A, Fx[B, V]]) { 171 165 return nil, func(a A) Fx[A, Fx[B, V]] { 172 166 x := ProvideLeft(e, a) 173 - return value[A](x) 167 + return Const[A](x) 174 168 } 175 169 } 176 170 } ··· 183 177 } 184 178 } 185 179 186 - func ProvideA[A, B, C, V any](e Fx[And[And[A, C], B], V], a A) Fx[And[C, B], V] { 180 + func ProvideFirstLeft[A, B, C, V any](e Fx[And[And[A, C], B], V], a A) Fx[And[C, B], V] { 187 181 return AndJoin(ProvideLeft(AndDisjoin(e), a)) 188 182 } 189 183 190 - func ProvideB[A, B, D, V any](e Fx[And[A, And[B, D]], V], b B) Fx[And[A, D], V] { 191 - return AndSwap(ProvideA(AndSwap(e), b)) 184 + func ProvideFirstRight[A, B, D, V any](e Fx[And[A, And[B, D]], V], b B) Fx[And[A, D], V] { 185 + return AndSwap(ProvideFirstLeft(AndSwap(e), b)) 192 186 } 193 187 194 - func ProvideAB[A, B, C, D, V any](e Fx[And[And[A, C], And[B, D]], V], a A, b B) Fx[And[C, D], V] { 195 - return ProvideB(ProvideA(e, a), b) 188 + func ProvideFirsts[A, B, C, D, V any](e Fx[And[And[A, C], And[B, D]], V], a A, b B) Fx[And[C, D], V] { 189 + return ProvideFirstRight(ProvideFirstLeft(e, a), b) 196 190 } 197 191 198 192 func Provide[S, V any](e Fx[S, V], s S) Fx[Nil, V] { ··· 213 207 } 214 208 imm, sus := e() 215 209 if imm != nil { 216 - return value[B](imm()) 210 + return Const[B](imm()) 217 211 } 218 212 return func() (immediate[V], suspended[B, V]) { 219 213 return nil, func(b B) Fx[B, V] { ··· 225 219 } 226 220 imm, sus = e() 227 221 if imm != nil { 228 - return value[B](imm()) 222 + return Const[B](imm()) 229 223 } 230 224 } 231 225 } ··· 237 231 } 238 232 239 233 func Const[S, V any](v V) Fx[S, V] { 240 - return Map(Ctx[S](), func(_ S) V { return v }) 234 + return func() (immediate[V], suspended[S, V]) { 235 + return func() V { return v }, nil 236 + } 241 237 } 242 238 243 - func Handle[A ~func(I) Fx[B, O], B, I, O any](i I) Fx[And[A, B], O] { 239 + func Suspend[A ~func(I) Fx[B, O], B, I, O any](i I) Fx[And[A, B], O] { 244 240 return AndJoin(Apply[A](i)) 245 241 } 246 242 ··· 248 244 return func(e Fx[And[A, B], O]) Fx[B, O] { return ProvideLeft(e, a) } 249 245 } 250 246 251 - func Suspend[F ~func(Fx[And[A, B], O]) Fx[B, O], A ~func(I) Fx[B, O], B, I, O any](i I) Fx[And[F, B], O] { 252 - return Handle[F](Handle[A](i)) 247 + func Handle[F ~func(Fx[And[A, B], O]) Fx[B, O], A ~func(I) Fx[B, O], B, I, O any](i I) Fx[And[F, B], O] { 248 + return Suspend[F](Suspend[A](i)) 253 249 } 254 250 255 251 func Eval[V any](e Fx[Nil, V]) V {
+1 -1
fx_test.go
··· 47 47 type printFn = func(string) FxPure[int] 48 48 49 49 func PrintLn(line string) Fx[And[printFn, Nil], int] { 50 - return Handle[printFn](line) 50 + return Suspend[printFn](line) 51 51 } 52 52 53 53 func printLn(line string) FxPure[int] {
+1 -1
rw/read.go
··· 9 9 type ReadFx[T, V any] = fx.Fx[ReadAb[T], V] 10 10 11 11 func Read[T any]() ReadFx[T, *T] { 12 - return fx.Handle[ReadFn[T]](fx.PNil) 12 + return fx.Suspend[ReadFn[T]](fx.PNil) 13 13 } 14 14 15 15 func ReadService[T any](r Reader[T]) ReadFn[T] {
+1 -1
rw/rw_test.go
··· 19 19 st := &S{"hello"} 20 20 rh := ReadService(func() *S { return st }) 21 21 wh := WriteService(func(s *S) { st = s }) 22 - x := fx.AndCollapse(fx.ProvideAB(e, rh, wh)) 22 + x := fx.AndCollapse(fx.ProvideFirsts(e, rh, wh)) 23 23 fx.Eval(x) 24 24 25 25 s := strings.Join(*st, " ")
+1 -1
rw/write.go
··· 11 11 type WriteFx[T, V any] = fx.Fx[WriteAb[T], V] 12 12 13 13 func Write[T any](v *T) WriteFx[T, fx.Nil] { 14 - return fx.Handle[WriteFn[T]](v) 14 + return fx.Suspend[WriteFn[T]](v) 15 15 } 16 16 17 17 func WriteService[T any](w Writer[T]) WriteFn[T] {