Closed ziriax closed 8 years ago
I do like this idea. I'm not sure how it would work for StreamLoop
, but it'd be nice to have a similar construct. Maybe it would just be a less safe version of the same thing, since the Stream.feedback
lambda would need to return a Stream
, opening the door for users to accidentally return the same stream that was passed to the lambda.
This also would mean that the CellLoop
class (as well as the StreamLoop
class if it was implemented also) could be made internal
.
Actually, I believe the Cell.feedback
lambda would need to return a Cell
rather than a Stream
, as there are cases where the CellLoop
needs to be looped over the result of a Cell.lift
.
I believe the problem could be solved by the type system (introduce a base classes of Cell
, maybe CellBase
or NonLoopableCell
, that cannot be looped, and only primitives like hold
would produce the loopable Cell
), but I'm not sure it's worth it for the complexity it would add to the API.
Another overload of Cell.feedback could have no initial value, and a lambda that must return a cell. Maybe that method should be called Cell.loop instead of Cell.feedback, making the intent more clear.
So Cell.feedback, Cell.loop and Stream.loop
I considered doing it this way, but the problem is that in Java (and this may not be so true in C#), you're fighting both the way people customarily do things, and the language. The language, because you often need to output a number of values from inside the loop. If you're using a lambda, this is problematic. In Java you would either need to define an array and poke them into the array (!), or have some mechanism where extra values can be returned from the lambda.
So I did this to make the interface as simple as possible, sacrificing the semantic cleanness that you would like. Of course if you can find a nicer way to do it in C#, then go for it.
EDIT: I should add that I am trying to make this library appeal to and not seem difficult to non-functional programmers. Every extra method that is added is a potential source of confusion and gives the appearance of a complex API. Many FRP/FRP-like systems suffer from this sort of off-putting feature bloat.
Yes, you're right. I didn't think about that. The same problem would exist in C#.
Even in a functional language with Tuple decomposition, it would be risky, as any streams or cells with the same type could easily be tuple in one order and untupled in another.
Point taken, very good argument! It indeed keeps the API simple.
Now as far as I understand, one must nest all the loops in a Transaction.Run
statement, and that method only returns a single value anyway. So the problem also exists here (but might be less intrusive)
PS: David Sankel seems to have taken a similar but not exactly the same approach with his 'Wormhole': https://github.com/camio/sbase/blob/master/include/sfrp/wormhole.hpp
Yeah, I noticed David's wormhole was similar. Yes, Transaction.run() is a bit inconsistent with this thinking. The reason why I chose to use a lambda for Transaction.run() is because the consequences of forgetting to close the transaction are a bit more severe because with a lot of them, it could be difficult to debug, and also because the need to return multiple values is generally not as great. Of course arguments could be made against this.
It all makes sense. IMO you are a great API designer, it is a delicate balance to get that right.
:)
Then we'll call this closed, but feel free to open the issue if a good solution comes up.
I did implement loops this way in the F# version, but it works much better with the F# syntax than it would with Java or C#.
I think that's the right thing to do (design the API to be natural in each language).
I can't wait to play with the F# version, thanks a lot!
I find the CellLoop construction very handy, but a bit ugly, as one could forget the Loop call, and it feels imperative. And maybe it allows infinite cycles, not sure.
Would the following construction be an improvement?
var loop = Cell.feedback(initialValue, cell => ...)
So
Cell.feedback
creates a CellLoop, passes that as aCell
to the lambda argument, and expects that lambda to return a Stream. Then it calls Loop with the resulting stream that starts with the initial value using Hold.Overloads could be made that create more than a single cell loop, to avoid nested calls.
A transaction is automatically started by the
feedback
method.