You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I am trying to implement zero-cost recursion schemes in Dart.
I'm using a technique that, I believe, is formally known as type defunctionalization. The theory seems to be much more complex than what's necessary in practice, but I'll be referring to that technique within the context of Dart here as TD.
Using TD we can be more general than what the static type system currently supports and implement first class functors (functors in the functional programming sense).
Furthermore, using TD we can also implement a fixpoint type that allows us to generalize over recursive structures.
In addition to that, TD requires us to have a marker interface for the root of our recursive structures so that we can refer to them on the simulated kind level.
This is enough to implement recursion schemes. Recursion schemes are a way to generalize traversals over recursive structures. The benefits of expressing a problem in terms of recursion schemes are that the implementation becomes much more maintainable because any explicit recursion is removed and, among other things, it becomes clear which traversals can be merged to reduce N separate traversals to just one.
There are three categories of recursion schemes (folds, unfolds and refolds) and each can be expanded to support different problem domains. For simplicity, the code below implements a fold that is known as a catamorphism, but others such as anamorphisms, hylomorphisms and even more involved ones such as futumorphisms and histomorphisms are implementable in Dart as well.
The main issue with the implementation below is that we are forced to introduce a level of indirection via Fix when referring to an Expression that contains members of its own hierarchy. I was hoping that inline classes could help here, but it is not intended (dart-lang/sdk#51564 (comment)) for them to support implements clauses and so they will not be able to implement Fix to make the Fix here zero-cost.
I have tried other things, such as making the members of the expression hierarchy implement Fix themselves, but I always ran into some limitations.
This example code evaluates a simple expression hierarchy by using a catamorphism. Notice how the argument to invoke needs to wrap all expressions in a Fix which I would like to avoid.
My goal is to eventually add support for recursion schemes to custom code generators and to investigate how I can make using the theory behind recursion scheme fusion practical, but the overhead of the Fix wrapper makes it hard for me to commit to such a solution, because I would like for it to be at least as performant as code that doesn't make use of recursion schemes.
Edit: below is an updated version that uses new features to simplify and make things safer (sealed classes, exhaustive matching, and class modifiers):
This is cool! @modulovalue, thanks for building these bridges between the functional programming community and Dart! (I haven't had a chance to look into the details, I just wanted to respond to the fact that these things are brought up ;-)
I have updated the issue description to include an implementation that uses new Dart features. (Class modifiers make recursion schemes via simulated higher kinded types much safer, and sealed classes allow some boilerplate code to be removed.)
I also wanted to leave a pointer to a discussion around monomorphization (dart-lang/site-www#4998 & dart-lang/sdk#52722), because It looks like that topic is important to making such recursion schemes more efficient.
(If it is true that mixins can help with monomorphization, then the implementation in the issue description should use them. However, i think that injecting functions via constructors allows for a clearer presentation, so I chose not to do that.)
I am trying to implement zero-cost recursion schemes in Dart.
I'm using a technique that, I believe, is formally known as type defunctionalization. The theory seems to be much more complex than what's necessary in practice, but I'll be referring to that technique within the context of Dart here as TD.
Using TD we can be more general than what the static type system currently supports and implement first class functors (functors in the functional programming sense).
Furthermore, using TD we can also implement a fixpoint type that allows us to generalize over recursive structures.
In addition to that, TD requires us to have a marker interface for the root of our recursive structures so that we can refer to them on the simulated kind level.
This is enough to implement recursion schemes. Recursion schemes are a way to generalize traversals over recursive structures. The benefits of expressing a problem in terms of recursion schemes are that the implementation becomes much more maintainable because any explicit recursion is removed and, among other things, it becomes clear which traversals can be merged to reduce N separate traversals to just one.
There are three categories of recursion schemes (folds, unfolds and refolds) and each can be expanded to support different problem domains. For simplicity, the code below implements a fold that is known as a catamorphism, but others such as anamorphisms, hylomorphisms and even more involved ones such as futumorphisms and histomorphisms are implementable in Dart as well.
The main issue with the implementation below is that we are forced to introduce a level of indirection via Fix when referring to an Expression that contains members of its own hierarchy. I was hoping that inline classes could help here, but it is not intended (dart-lang/sdk#51564 (comment)) for them to support implements clauses and so they will not be able to implement Fix to make the Fix here zero-cost.
I have tried other things, such as making the members of the expression hierarchy implement Fix themselves, but I always ran into some limitations.
This example code evaluates a simple expression hierarchy by using a catamorphism. Notice how the argument to
invoke
needs to wrap all expressions in a Fix which I would like to avoid.Catamorphism 1
My goal is to eventually add support for recursion schemes to custom code generators and to investigate how I can make using the theory behind recursion scheme fusion practical, but the overhead of the Fix wrapper makes it hard for me to commit to such a solution, because I would like for it to be at least as performant as code that doesn't make use of recursion schemes.
Edit: below is an updated version that uses new features to simplify and make things safer (sealed classes, exhaustive matching, and class modifiers):
Catamorphism 2
The text was updated successfully, but these errors were encountered: