HeinrichApfelmus / reactive-banana

Library for functional reactive programming in Haskell.
https://wiki.haskell.org/Reactive-banana
522 stars 70 forks source link

reactive banana light #30

Closed bluescreen303 closed 12 years ago

bluescreen303 commented 12 years ago

reactive-banana makes heavy use of extensions (GADTs, type families), preventing it from being compiled with the UHC compiler.

The semantics model itself (model.hs) is clean in this respect. While the model isn't meant for high-performance situations, it probably suffices for web applications (UHC's javascript target). It would be very nice if reactive-banana could have a smaller brother (reactive-banana-light) with the same semantics/API.

This way, code will be fairly portable between the light and normal versions.

HeinrichApfelmus commented 12 years ago

Actually, it appears to me that it's probably easiest to use #ifdefs to avoid code duplication.

I will change reactive-banana so that it can in principle be used with UHC, hoping that you guys will help out with nasty details.

On that note, I don't know much about UHC and the JS backend. Does it support Data.IORef or the Control.Monad.ST? What about dependencies like the transformers library?

bluescreen303 commented 12 years ago

On Thu, Mar 29, 2012 at 3:26 PM, Heinrich Apfelmus reply@reply.github.com wrote:

Actually, it appears to me that it's probably easiest to use #ifdefs to avoid code duplication.

I will change reactive-banana so that it can in principle be used with UHC, hoping that you guys will help out with nasty details.

On that note, I don't know much about UHC and the JS backend. Does it support Data.IORef or the Control.Monad.ST? What about dependencies like the transformers library?

IORef seems supported: https://subversion.cs.uu.nl/repos/project.UHC.pub/trunk/EHC/ehclib/uhcbase/Data/IORef.hs

Control.Monad.ST seems as well (at least the Lazy variant) https://subversion.cs.uu.nl/repos/project.UHC.pub/trunk/EHC/ehclib/base/Control/Monad/ST.hs

transformers works fine although it needs a small fix because of an unsupported CPP definition. bugreport here: http://code.google.com/p/uhc/issues/detail?id=49 I work around this by removing the offending lines, but I'm pretty sure this will be fixed soon.

mtl on the other hand won't work because of fundeps.

I haven't looked at containers/deepseq yet (Map/IntMap) but I think at least the somewhat older versions should work. Unordered containers/hashable, no clue. They should probably be mapped to native javascript objects for performance.

base version reported by uhc is 3.0 but I think quite some 4.0 additions are already there so it's hard to compare by number.

when looking at the reactive-banana dependency list, only fclabels scares me. it uses template haskell and mtl, which are both unsupported.

In general I think most of what's needed is supported. Things that aren't can generally be added quite easily or stubbed.

Please let me know if I need to try specific pieces of code.


Reply to this email directly or view it on GitHub: https://github.com/HeinrichApfelmus/reactive-banana/issues/30#issuecomment-4817218

HeinrichApfelmus commented 12 years ago

Ok, I have introduced a new flag UseExtensions. If you disable that, the library will now use the model implementation instead of the push-based implementation and thus won't use as many language extensions. The develop branches contains these latest developments.

Now it's your turn. :-) Disable the flag and beat the banana until it compiles with UHC. Let me know if you run into any problems.

bluescreen303 commented 12 years ago

On Sat, Mar 31, 2012 at 4:16 PM, Heinrich Apfelmus reply@reply.github.com wrote:

Ok, I have introduced a new flag UseExtensions. If you disable that, the library will now use the model implementation instead of the push-based implementation and thus won't use as many language extensions. The develop branches contains these latest developments.

Now it's your turn. :-) Disable the flag and beat the banana until it compiles with UHC. Let me know if you run into any problems.

Thanks a lot, I'm making good progress. Reactive.Banana.Combinators compiles out of the box. Reactive.Banana.Frameworks however, has more issues.

UHC's javascript backend hasn't implemented MVar yet. For now, I just replaced it with an IORef and need to think a bit about it. Javascript is single threaded. Of course by using callbacks/timeouts, multiple threads are simulated, but there's always at most 1 running at any time. Basically, if a thread reads something (takeMVar), and makes sure it puts the new state back (putMVar) in the same tick, the language guarantees no callbacks/timeouts get fired while you execute, so no-one will see the faulty state. I believe this is the case here, the automaton is read, takes a step, is put back, and only then new actions are executed. So probably I can just write an MVar stub.

Next thing I walked in to: HashMap has a lot of dependencies and is probably just a performance thingy. I replaced it with Data.Map for now and might write a simple HashMap that binds to javascript objects, which basically are native hashmaps.

Data.Unique -- quite recent base thing, not very portable. I can probably cook something up that just counts up from 0 (js single threaded, so should be easy to implement), but I'm not sure if this is effective. I also noticed you wrap it into a ReallyUnique. Need to investigate a bit, never saw Unique before.

and then, the killer dependency: Vault It uses GHC.Exts (Any), which is GHC only. It also uses Unique.

All in all, I think I can work around / implement these dependencies, but it will take time. Also, I'm a bit scared by all these dependencies. I should probably take a good look at the code and see if I can shortcut a bit. I'm not familiar with Vault and Unique, but the HashMap+Vault+Unique stuff can probably be handled by a little native javascript with some state in a closure.

Anyway, so far I'm quite happy that the model+combinators themselves seem to work fine. Basically I can just run an event graph step by step if I handle the statekeeping and input/output myself, but it would of course be nice to get the NetworkDescription stuff working too.

I'll keep you posted.


Reply to this email directly or view it on GitHub: https://github.com/HeinrichApfelmus/reactive-banana/issues/30#issuecomment-4858686

HeinrichApfelmus commented 12 years ago

UHC's javascript backend hasn't implemented MVar yet. For now, I just replaced it with an IORef and need to think a bit about it.

Yep, I'm confident that this will work.

Concerning HashMap, Unique, Vault they are actually all related.

It's fine to implement Unique as a global counter, that's more or less how GHC does it. The idea is just that each call to newUnique creates a new value that is not equal to all the others.

The ReallyUnique thing exists because I'm sometimes using unsafePerformIO on newUnique, that's where things may become dangerous. The problem is mainly with GHCi here: reloading a source file resets some CAFs but not others, which screws up the Unique counting because an old counter may still linger in a CAF while the global starts from 0 again. I think that this is only the case for the push-driven implementation, not the model implementation, though, it's probably possible to disable ReallyUnique with the flag and ignore it.

Unfortunately, the ReallyUnique thing also forces me to use HashMap instead of Data.Map, because the ReallyUnique are no longer members of the Ord class, but can be hashed to some integer. Getting rid of ReallyUnique means that you can use Data.Map again.

The Vault thing is actually a clever way to do some dynamic typing without Data.Typeable. Fortunately, it has a pure implementation as well, described here:

http://apfelmus.nfshost.com/blog/2011/09/04-vault.html

(Huh, these pure implementations sure turn out to be really useful.) It uses Any only for reasons of speed.

Furthermore, the Vault package does rely on Unique a lot, which should probably be ReallyUnique.

Also, I'm a bit scared by all these dependencies.

No worries, we got this. :)

I propose the following: Since I now have a good grasp of the problem areas, I will move the troublesome stuff into the vault package and then I will add a GHC-specific flag to it. You only need to implement some cheap Data.Unique on the JavaScript backend for now.

After that, the only remaining problem is ReallyUnique: it's only relevant for GHCi, but it also forces the HashMap dependency. In any case, it's best to put it in the vault package, too.

HeinrichApfelmus commented 12 years ago

Created a new issue for the vault package.

Does UHC have the ST monad? Strictly speaking, it's not needed for reactive-banana, but the vault package exports an ST interface. I think that ST can be implemented in terms of unsafePerformIO, though, so I think it's actually easy to implement for UHC as well and thus worth doing anyway.

bluescreen303 commented 12 years ago

Heinrich Apfelmus reply@reply.github.com writes:

Created a new issue for the vault package.

Does UHC have the ST monad? Strictly speaking, it's not needed for reactive-banana, but the vault package exports an ST interface. I think that ST can be implemented in terms of unsafePerformIO, though, so I think it's actually easy to implement for UHC as well and thus worth doing anyway.

Yes, it's there. And Control.Monad.ST.Lazy too, so should be safe to use.


Reply to this email directly or view it on GitHub: https://github.com/HeinrichApfelmus/reactive-banana/issues/30#issuecomment-4876546

bluescreen303 commented 12 years ago

Another issue (not a show stopper) is the ScopedTypeVariables extension, which UHC doesn't support. This, in combination with the sneaky type parameter, leads to uglier code.

With the extension you can:

example :: forall t. Event t (InEvent, TimeStamp) -> Event t OutEvent
example mainIn = ...
  where connectionEvent :: Event t (InEvent, TimeStamp)
        connectionEvent = filterE isConnEvent mainIn

here, the t in the type signature of connectionEvent refers to the t in example. Without the extension, this isn't possible and the only way to obtain t is by passing all dependencies like:

example :: forall t. Event t (InEvent, TimeStamp) -> Event t OutEvent
example mainIn = ... (connectionEvent mainIn) ...
  where connectionEvent :: forall t. Event t (InEvent, TimeStamp) -> Event t (InEvent, TimeStamp)
        connectionEvent = filterE isConnEvent

Of course now connectionEvent can move to the top level. Not a huge issue, it even forces you to make dependencies very clear, so that's positive. Just something to be aware of.

HeinrichApfelmus commented 12 years ago

Another issue (not a show stopper) is the ScopedTypeVariables extension, which UHC doesn't support.

><. I think that this extension is very important for making higher-rank polymorphism usable, so UHC ought to implement something like that. Maybe it already does? What happens if you turn on Rank2Types?

For instance, the flag Rank2Types will automatically turn on ScopedTypeVariables in GHC.

bluescreen303 commented 12 years ago

RankNTypes are supported and on by default, but ScopedTypeVariables don't seem to work.

I can nag the devs about it, but I think I'll wait until after GADT/fundep/TF are done :)

For now, just removing the type signatures of all nested stuff works, and I no longer have to pass extra arguments around to get hold of t. So my code is exactly the same as in GHC structurally, just without the signatures. UHC is able to infer everything correctly but there just isn't a way to specify the type yourself at the moment.

I just have them commented for now and in case of errors, I enable a few to have GHC produce more helpful error messages. Not ideal, but it's workable.

HeinrichApfelmus commented 12 years ago

@bluescreen303 Ok.

The latest commit on the vault package now uses the pure variant for non-GHC compilers. Furthermore, I was able to remove the dependency on unordered-containers when the UseExtensions flag is disabled. Try compiling reactive-banana on UHC again, it should be a lot easier now.

bluescreen303 commented 12 years ago

nice, more progress :)

vault compiles fine and I ran a few simple tests where I put haskell and JS objects into a vault and was able to retrieve them. Only thing to keep in mind for the future is that I use containers-0.4.1.0 (last one that doesn't need deepseq). Deepseq'ing itself isn't that hard, but the package contains references to almost every obscure GHC datatype. It should be easy to filter this a bit to get a deepseq-light, but I haven't needed it yet. If a future version of vault needs a higher version of containers, please let me know so I can have a look at deepseq-light.

reactive-banana itself still uses HashMap (Frameworks) and System/Mem/StableName (Internal/InputOutput) so it doesn't get much further. I think it was your plan to have all these things abstracted into vault right? I can see they are (conditionally) in Vault, but reactive-banana hasn't been updated to use that.

HeinrichApfelmus commented 12 years ago

Oops, I simply forgot to create and push a patch. The HashMap and StableName should be gone in the latest commit now.

bluescreen303 commented 12 years ago

You forgot one line (26) in InputOutput. Other than that... it compiles! Thanks a lot!

I haven't tried anything yet except for compiling, but I will probably do that this weekend and let you know. If all works well, I will try to build a simple example which includes all my stubs/workarounds, and contact the UHC devs about getting these stubs flow back into their codebase so that a (near) future release can support reactive-banana out of the box.

And then it's time for the nice work... reactive-bananana-html widget toolkit :)

bluescreen303 commented 12 years ago

I ported my current code to use Frameworks.hs. I'm able to build an event network, compile it, and run it. Input and output events work. My network includes normal (fromAddHandler) events and a fromPoll behavior. This all works great!

There is however, some stuff UHC should fix for this to work flawlessly.There is an outstanding bug http://code.google.com/p/uhc/issues/detail?id=53. I worked around that with a small patch: https://gist.github.com/2350944 I don't think reactive-banana should do anything to counter it, this patch works fine for now and will no longer be needed once the bug gets fixed.

Also, I found that the code uses Control.Exception (evaluate) in a few places. Exception handling is not/partially implemented in the JS backend, so evaluate was missing. Do you think

evaluate :: a -> IO a
evaluate x = (x `seq` return x) >>= return

is enough for now?

And lastly: I noticed 'actuate' doesn't block, so I can continue executing other stuff without thinking about starting new threads myself (which would be virtual of course, as JS is single threaded). It will probably also allow me to run networks in parallel (open systems, as we discussed in an email), so this is great. But is this the intended/expected behavior?

I think this is all there is to this issue. As far as I'm concerned this issue can be closed. Thanks again

HeinrichApfelmus commented 12 years ago

Great!

Concerning evaluate, that's indeed the implementation that the documentation claims to be right. I don't remember why I didn't use seq, I think I just got used to using evaluate whenever I want to force WHNF in the context of IO actions because it's a little friendlier for exceptions. None of that matters here.

Concerning actuate, it is indeed intended not to block. In fact, that's why I called it actuate ("to set into motion") and not run ("to set into motion and keep it running"). :-)

Let me know if anything else comes up and how your experiments with reactive-banana work out in the end. :-)