forsyde / forsyde-shallow

ForSyDe's Haskell-embedded Domain Specific Language
https://forsyde.github.io/forsyde-shallow/
BSD 3-Clause "New" or "Revised" License
12 stars 10 forks source link

Specification of SDF actors with multiple outputs #11

Closed ugeorge closed 5 years ago

ugeorge commented 6 years ago

Currently SDF actors with multiple outputs take functions that yield lists of tuples of lists, e.g.

actor12SDF :: Int -> (Int, Int) -> ([a] -> [([b], [c])])
           -> Signal a -> (Signal b, Signal c)

That is because, among others, actors are implemented internally with map/zipWith, which need to yield lists in order to recreate signals. Although just a "type fix", this leaves way for design mistakes, e.g.:

actor12SDF 1 (2,1) ( \ [x] -> [ ([x+1,x-2], [x/2]) , ([x+3,x-2], [x]) ] )

How can the previous actor instance be interpreted? It is wrong. Situations like these need to be avoided simply by restricting the yields to tuples rather than lists of tuples. This is one by modifying map, zipWiths and unzips accordingly. In this way the constructors do not comply to SDF any more, but this is not an issue, since they are not exported anyway. Only actors need to be exported. The rest should be regarded as utilities, and nothing more.

ugeorge commented 6 years ago

The solution can even be generalized considering how type traversals behave, and understanding that "unzipping" at its core is just a kind of transpose. While signal is a traversable type itself, tuple is not. It is still possible to create generic "traversable-like" rules for specific tuple instances as well, by following some patterns like in the implementation of "unzip" (|<) utilities in ForSyDe-Atom.

For the scope of ForSyDe-Shallow, though, simple specialized solutions per each MoC should suffice.

ricardobonna commented 6 years ago

George, I've found a simple way to solve the issue without the need to change the zipwhiths and unzips. So follow me on this one: We want the type signature of the function inside the actor12SDF (for instance) to be like f1 :: [a] -> ([b], [c]) but instead, we are using f2 :: [a] -> [([b], [c])] because of the maps and zipwiths, as you said. We can make an actor12SDF with f1 in the type signature, just as we want, and inside de definition of the actor we can transform f1 into f2 by doing (\ a -> [f1 a]) and keep the zipwiths, unzips and all the rest of the function definition intact. Therefore we do not need to change anything else. And we can do this with all the other actors as well. I don't think there is going to be a more simple/fast solution for this issue than that. I did something similar to CSDF and SADF and it is working fine.

Nice example, btw!

ugeorge commented 6 years ago

When you say fast solution are you referring to the amount of our work, or to performance? Because when it comes to performance, unnecessary type constructors, wrappings and unwrappings do take their toll. Let me try something out tonight and we will discuss our solutions tomorrow.

My plan is to define zipWithSDF for example as:

zipWithSDF :: .... -> ([a] -> [b] -> c) -> ....

And introduce an aditional utility for re-creating signals from streams of partitions (basically what the current unzipSDF are doing). I believe this is also your approach for detectors, so that you do not constrain the return type. Sure, this means a bit more work, but in the long run it is worth it.

ugeorge commented 6 years ago

Check this commit. The only thing exported are the actors and delays (i.e. initial tokens), because this is what the SDF MoC actually defines, and nothing more. One would have to come up with a really good case to defend map, zipWith and unzip as part of any MoC, because I'm not buying it...

ugeorge commented 5 years ago

@ricardobonna , it seems that your approach is the safe one after all. The utilities I wrote seem to cause infinite loops in feedback systems (see issue above).