-
Notifications
You must be signed in to change notification settings - Fork 604
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unify FreeC and Algebra types. #1610
Conversation
This commit unifies the two core ADT of fs2: FreeC and Algebra. FreeC is a free monad with catch and interruption. Algebra represents a set of "abstract instructions" that may output values of type O, or put a value of type R on a stack. A Stream[F, O] consists of a `FreeC[Algebra[F, O, ?], Unit]`. We have noted two interesting facts of this combination: - In fs2, a FreeC was only used when its F[_] type was an Algebra - The only place in which an Algebra object would appear in a FreeC was through the Eval class. Based on these facts, and to simplify and reduce memory consumption (as well as some complexity) of the Stream implementation, we unify FreeC and Algebra, but turning "Algebra" into a subset of FreeC: - We add to FreeC a third parameter, where the second parameter represents the output of a Stream. - We turn the "Eval" class of FreeC into an abstract trait, - We remove the Algebra trat, and turn all the former Algebra sub-classes into sub-classes of that Eval. This change causes other changes: - The `translate` function of the FreeC.Bind is no longer needed. This was needed to implement `mapOutput` as a natural transformation. Instead, we add the `mapOutput` function to the FreeC itself. - We introduce another middle sub-class of FreeC, for the results. This is just a mere convenience, to reduce code elsewhere. - Since classes and types are simplified, some factory methods or "smart constructors", needed to lift through types, are not needed. Such as Algebra.step, openScope, closeScope. - We are now able to replace the second parameter "Nothing" in the inner wrapped FreeC of Pull and Stream (the output) by the O
} | ||
} | ||
): FreeC[F, O, R] = | ||
Acquire(resource, release) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mpilquist For now, the branch has only unfolded/removed the factory methods that were private to this object. However, since the whole Algebra
is private to fs2
, would it make sense to open the visibility of the Algebra subclasses, and remove this method, as well as eval
and output
above, and getScope
below?
The attached file contains (compressed) a spreadsheet with the results of some more benchmarking results. I used the suite
The results of these runs were tabulated into this spreadsheet, in which I also add the cells to highlight the relative improvement (the The results indicate, across most benchmarks, a reduction in memory consumption (as measured by the For the environment:
|
@diesalbla seems promissing, thanks for this |
Looks good to me. Looking forward to unifying |
Continuation of #1598, which was automatically closed by Github due to the deletion of the
series/1.1
branch. In what follows is the original message, and some items from the conversation:This unifies the two ADT in the internal stream implementation:
FreeC[F[_], R]
, the Free monad with catch and interruption; andAlgebra[F[_], O, R]
, for the scope-handling and output instructions. These are combined using the approximate type aliae ofPullF, O, R]
asFreeC[Algebra[F, O, ?], R]
. Notably, an object of typeAlgebra[F, O, R]
can only appear in a FreeC through theFreeC.Eval
class. This allows us to unify these two ADTs by doing the following:O
(stream output) to theFreeC
ADT, and its subclasses; as well as to theViewL
trait (for unrolling).FreeC.Eval
into an abstract class that replaces the previousAlgebra
trait, so that all subclasses of Algebra become subclasses ofFreeC.Eval
. Because FreeC and Algebra ADTs are in separate files, we unseal it.ViewL
subclasses from containing aF[R]
to containing anEval[F, R]
.As a result, a Pull is no longer an alias for
FreeC[Algebra[F, O, ?], Unit]
, but forFreeC[F, O, R]
instead.With the previous 2-layered representation, there was a need to have in the
Algebra
object a few methods to lift simple data and fill the type parameters. With the new representations, those methods are not needed and we can remove them in favour of the object they build. That is the case of the private methodsopenScope
andstep
.Translate to mapOutput
Before this change, the implementation of Stream[F, A].map[B](f: A => B) had to work at two levels: one being a
FunctionK
between two type-instances of Algebra, which would differ on the output type. To apply this transformation through the Stream, theFreeC
ADT had atranslate
method. After the changes, now we do not need thatFunctionK
wrapper, and we can just replace the translate method of FreeC with a mapOutput method.Modularity
The new design keeps some of the modularity of the previous one: the
FreeC
main classes and operations are built without any reference to theAlgebra
classes. The major control-flow operations, such as theFreeC
methods and the loop unrolling ofViewL
, are also implemented without reference to theAlgebra
object. So, the separation of concerns still exists between these files, except that now it is connected through sub-classing rather than through a parametric type.Readability
The new representation has no nested types, no kind-projection ? symbol, fewer "smart constructors", and implements stream output
map
without using a FunctionK. Also, now theF[_]
parameter refers everywhere to the effect in which the stream is run. Against those upseides, there are some downsides in adding an extra type-parameter O, and the use of unsealed traits.Benchmarking
As an optimistic assesment, since this PR reduces the number of objects neeeded to represent each Algebra operation (by no longer needed the
FreeC.Eval
wrapper), this may significantly reduce memory consumption. An early benchmarking, as mentioned in the initial PR, suggested a reduction of 2.5% memory consumption.After-work: unify FreeC and Pull
Now that
FreeC
has two covariant parameters, the only major difference with the definition ofStream
andPull
is that theF[_]
parameter is covariant in those but invariant inFreeC
. I am experimenting with how to add that covariance toFreeC
, but at present it is causing not only some compile errors, but also compiler crashes. This PR, however, seems to be stable enough and carries enough changes to be dealt with separately.