sidnt / zionotes

⸮ 🔴zio notes | zio nursery 🔵?
0 stars 0 forks source link

zipWith semantics #5

Open sidnt opened 4 years ago

sidnt commented 4 years ago

its documentation reads interesting:

Sequentially zips this effect with the specified effect using the specified combiner function.

but what do we understand by a sequential zip? it means, a) the effect returned from running effectA.zipWith(effectB), will itself be an effectC, ie, just another description.

that description, will read very much like:

(smell a for comprehension?)

so far so good for types ABC. but what about RE R1E1?

sealed trait ZIO[-R, +E, +A] extends Serializable { self =>
/** ... */
final def zipWith[R1 <: R, E1 >: E, B, C](that: => ZIO[R1, E1, B])(f: (A, B) => C): ZIO[R1, E1, C] = 
/** ... */
}

this tells a lot of story, as we shall see. let's pay attention to types.

the current zio instance, already has it's -R+E+A types. if we look at the signature of zipWith, it uses the REA types, so any such zipWith invocation would be aware of its REA types. based on that, we encode some constraints on the arguments to this method, particularly, on their types, ie, on the types of that and f. we express these constraints in the type bounds in the [ ] next to zipWith, ie,

[R1 <: R, E1 >: E, B, C]

we don't want any subcomponent of our program, either depending on something the parent couldn't/didn't provide, or return something which the parent program doesn't know how to handle. these are constraints on what it can return back to handle, or what it can require from us to work.

our first requirement is, now zipWith is given the freedom to return a new effect. but since we are using zio model of types telling us everything about an instance, we expect the REA are the finally inferred types, that tell the entire scope, width and breadth of the zio instance. that to run it, we need to provide R, on running, it might fail with an e:E, or succeed with an a:A.

if we imagine the root fuse of a typical webserver, R will be a rich type, telling us a lot about the configuration and tuning parameters for the given effect, but E and A might as well be Nothing. meaning we have programmed our effect so well, that it will not fail, and keep running forever, so neither it will return any value.

but this is not always the case for smaller components outer in the wild. we see deviations from Nothing. especially, with smaller effects wired all over the place in the root effect, because these smaller effects need to be explicit and declarative about the types; what exactly do they need to function properly, and what exactly will they give us back to handle; when communicating with other effects, by exchanging information, in kicking off subsequent effects, or dealing with return values from others, they called into earlier.

we don't normally observe this, but the new R1E1BC types, are also parameters to this function, taken from the types of that and f.

whatever R1E1BC type is, it must fulfil the [ ] encoded constraint, which is

[R1 <: R, E1 >: E, B, C]

there are no bounds on B and C, they can be any arbitrary type. basically the f: (A, B) => C

sidnt commented 3 years ago

R1 is a subtype of R since R1 is meant to be fed to the resultant ZIO, it should cater to the needs of R as well. Ie, it should be treatable as R. Which is established when we constrain R1 <: R. And then after this, R1 can contain its own characteristics that would to cater to the requirements of the that effect.

The resultant effect can fail with E1, ie, it can be an error from either the this effect or the that effect. But for both, we are getting the same E1 type. ie, E1 needs to represent both the errors from this and that effects. So