Git Product home page Git Product logo

sodium's People

Contributors

antmd avatar butlermh avatar ca11ado avatar clinuxrulz avatar conklech avatar danbernier avatar fhitchen avatar gelisam avatar geraldus avatar icodeit avatar jam40jeff avatar jamescasbon avatar jhegedus42 avatar jkhoogland avatar krzysztofwos avatar luite avatar mwotton avatar newca12 avatar notlion avatar ownwaterloo avatar pxqr avatar pyrtsa avatar trin-cz avatar vertexcite avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sodium's Issues

Merge primitive seems to be buggy

Here is simple code, it reads line from user and should print is that string short, medium or long.

import Control.Applicative( pure )
import Control.Monad( forever, liftM )
import FRP.Sodium

type EventReactive a = (Event a, a -> Reactive ())

mkReactiveTest :: [EventReactive ()] -> Reactive( Behavior String )
mkReactiveTest es = do
    let bs  = map pure ["Short string", "Medium string", "Long string"]
    let es' = map fst es
    let ebs = zipWith (\b e -> fmap (const b) e) bs es'
    let em  = foldl1 merge ebs
    sel <- hold (head bs) em
    switch sel

test :: [EventReactive ()] -> String -> IO ()
test es s = do
    let a = snd $ head es
        b = snd $ head $ tail es
        c = snd $ last es
    sync (if length s < 3
          then a ()
          else (if length s < 6 then b () else c ()))

main :: IO ()
main = do
    es <- liftM (replicate 3) (sync newEvent)
    r  <- sync $ mkReactiveTest es
    l  <- sync $ listen (value r) putStrLn
    forever $ do
        s <- getLine
        test es s
    return ()

In fact, despite of input it always prints "Medium string".
Stephen, please have a look!

Scala & Java version both use same package

Both the Java and Scala versions of the API replicate class and package names, leading to potential naming conflicts. For example, if a Java library that relies upon the Java flavor of the library is employed by a Scala application that uses the Scala flavor of the library, then there will be two conflicting sets of elements sharing the sodium package required by the application.

These libraries, and any other JVM flavors of the library, should have unique package names to avoid such conflicts.

What contributes to the slowness of send?

Hi!
I did some simple benchmarks on single-threaded version of the library. I tried to send 1 million integers into behavior_sink and it took almost 2 seconds. When it's wrapped in transaction, it's well over 2 seconds. Could you please write (if you know) why it is such a slow operation?

P.S. I didn't expect to find that sending the same int propagates update. Could you please explain for what reasons it's made like this? I expected that sending the same value (not changing the behavior) wouldn't result in firing updates() event of that behavior (and all dependent behaviors).

A mistake in note about snapshot

In denotation document (page 4, Snapshot section)

Note: Snapshot is non-primitive. It can be defined in terms of MapS, Snapshot and Execute ...

I think "defined in terms of MapS, Sample and Execute" is what was intended here.

Support base 4.8

Hi, Steven!
I've added sodium to Stackage recently. Now it's going to support GHC 7.10 RC1, and base have fixed 4.8 version there. Thus, sodium could not be built, because it depends on base < 4.8. Can you bump upper bound of dependency?
I could test build myself, but I need to learn how to write instructions for Travis first (I think this is the easiest way). Also I can try to find docker image with GHC 7.10 RC1 and test build with that.
What will you say?

Windows/MingW Support?

I was interested in this project and attempted to compile it in windows. I do not know if windows via mingw is even a target that the maintainer is interested in supporting (if not then just ignore this)

My setup: Windows 7 with gcc (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 4.8.3

I generated build files with cmake, this went fine, I just had to tell it where my boost libs were.

Then I attempted to kick off the build.

in sodium\lock_pool.h it complained at "#error This architecture is not supported"

I guess this is due to mingw 64bits not definining __WORDSIZE. So I added:
#ifndef __WORDSIZE
#define __WORDSIZE 64
#endif
At the top of the file and then that went away.

Then the second problem:
in sodium\transaction.h
The compiler cannot find #include <pthread/pthread.h>
This is off course due to me not having pthreads installed (Edit: spoiler alert, I was wrong about this, see next post). I would recommend stating that pthreads is a requirement for building the project.Alternatively (and I prefer this solution, but I have no idea if it is viable), replace pthreads with boost threads since they are multiplatform and a dependency on boost already exists.

This is where I am at currently, just wanted to let you know. Back to googling for pthreads in mingw.

BR/ Christoffer

GC problems with GHCJS

I just recently compiled the ghcjs-examples (https://github.com/ghcjs/ghcjs-examples/), and for example the mouse example only works for 2 seconds. http://www.viizio.com/~joco/weblog/mouse/mouse.jsexe/ and any example that uses Sodium works only for 2 seconds. Examples that do not use Sodium work OK. Luite said on irc that this might be some kind of GC issue.

This compilation can be reproduced by using this docker image : https://registry.hub.docker.com/u/jhegedus42/mouse-example-works-for-2seconds/

Tutorial. GHCJS and Sodium

Rewritten at 2nd of March, 2014

Authors preface

This is first the edition of my tutorial. Please excuse me my poor English. I'll be happy if you can help make my text more readable. Also the code itself could be affected by imperative style. Please leave your comments and recommendations if you want.

This tutorial shows:

  • how to use Sodium's merge and switch primitives;
  • how to listen "external" javascript events using GHCJS;
  • how to combine GHCJS and Sodium to create functional reactive ui in browser.

Overview

We'll see a simple web application, which consist of single HTML page. There are page header, some content, hidden popup window and triggers which opens that popup.

2014-02-23 19 24 24

When you click a trigger popup appears. There are duplicated triggers and close button inside popup.

2014-02-23 19 31 06

To design UI logics we'll use Sodium. The idea adopted from Sodium's presentation:

2014-02-23 19 40 51
2014-02-23 19 41 04
2014-02-23 19 41 25

In our case javascript click events on triggers represent button events, selected tabs represent channels and closed popup represents the fuzz.

Data Types

Let's design data types:

data DomElement_ = DomElement_
type DomElement  = JSRef DomElement_

data PopupTab    = PopupTab { exTrigger :: DomElement
                            , inTrigger :: DomElement
                            , content   :: DomElement
                            }

data PopupWidget = PopupWidget { window :: DomElement
                               , closeB :: DomElement
                               , tabs   :: [PopupTab]
                               }

PopupTab consist of external trigger (which is outside the popup window), internal trigger and element with content. PopupWidget is consist of window which is widget itself, closeB which is just a quick reference to button that closes popup, and tabs which is a quick reference to widget tabs.

Here is HTML code of widget:

<div id="popup" class="Dn Pos-A Plate">
     <div id="ppp-close" class="trigger">Close</div>

     <div id="ppp-nav">
          <div id="item-about" class="trigger">About Us</div>
          <div id="item-contacts" class="trigger">Contacts</div>
          <div id="item-service" class="trigger">Centers of Service</div>
          <div id="item-book-measure" class="trigger">Book Measure</div>
     </div>

     <div id="ppp-content">
          <div id="content-about" class="content">Lorem ipsum dolor sit amet.</div>
          <div id="content-contacts" class="content">
               Tel: +1-234-567-890
               </br>
           Address: Ap.01, Reactive Street, UI Town, Sodium.
          </div>
          <div id="content-service" class="content">Aliquam tortor dolor, sodales sed suscipit eget, porta non nulla.</div>
          <div id="content-book-measure" class="content">Donec sodales tortor quis ante mattis, at aliquet magna pretium.</div>
     </div>
</div>

Thus, we need a list of evens with pushing functions, a list of behaviors of PopupTab and our fuzz analogue. We'll use Maybe PopupTab type to represent our channels and fuzz, e.g. for closed popup window the value would be Nothing and for selected tab it will be Just value with concrete tab inside.

We'll split our final goal into four tasks:

  • design reactive logic;
  • find DOM elements on the page;
  • bind handlers to real javascript click events;
  • render changes.

Reactive Logic

To separate reactive code from IO let's design a function which takes a list of events, its pushing functions and popup widget and which returns a behavior of type Behavior (Maybe PopupTab). Then we'll listen value of that behavior and render our UI depending on its value.
Sodium's newEvent function returns an event paired with it's pushing function it's so we'll use one list of pairs, e.g. [(Event a, a -> Reactive ())].
Here is our reactive code (please read comments):

mkEvents :: Int -> IO [(Event a, a -> Reactive ())]
mkEvents n = sync $ replicateM n newEvent

mkReactiveUi :: PopupWidget
               -> [(Event a, a -> Reactive ())]
               -> Reactive (Behavior (Maybe PopupTab))
mkReactiveUi pppw es = do
    let closeBhv = pure (Nothing :: Maybe PopupTab)
    items' <- mapM (\t -> newBehavior (Just t) >>= return . fst) $ tabs pppw

    -- `items` is a list of behaviors which constantly holds Maybe values for hidden state
    -- of popup and all states when concrete tab is selected. 
    let items = closeBhv:items'

    -- Fetching only events itself from pairs. They are click events.
    events <- mapM (return . fst) es

    -- Now we map each state to events (clicks).
    -- The result is events of type `Event (Bahavior (Maybe PopupTab))`.
    -- These are state events.
    -- When we'll push new value to concrete stream of click events we'll have
    -- a new value in resulting stream of state events holding corresponding behavior. 
    let eItems = zipWith (\i e -> fmap (const i) e) items events

    -- Then we creating a new behavior which will hold concrete state of our widget.
    -- We'll use special event stream for this behavior, which is merged streams
    -- of state events.
    -- This means that when we have a new value in any of state streams of
    -- merged stream immediately gets this value too.
    sel <- hold closeBhv $ mergeFold eItems

    -- Now we have a Behavior of Behavior, and all we need is to unwrap the last one!
    -- Let's name it "selector".
    ui <- switch sel
    return ui
  where mergeFold :: [Event a] -> Event a
        mergeFold es = foldl1 merge es

So, we have a click events (of type Event ()). Our reactive logic will work this way:

When javascript registers click on the trigger it pushes new value to corresponding click stream.
Corresponding state stream reacts to new click event occurrence, and new event with corresponding state behavior occurs in state stream (this behavior constantly holds value of Maybe PopupTab).
As mentioned in comments merged event stream reacts on that occurrence, actually pushing new behavior to selector (namely sel). When all this will happen our UI behavior will get new value, which is current state.

Great! Now we have the first piece!

Finding DOM Elements

This task is quite easy, and I think detailed explanations are unnecessarily. (However if you'll ask I can explain it a bit.)
The code is quite obvious itself:

foreign import javascript unsafe "document.getElementById($1)"
    js_getElementById :: JSString -> IO DomElement
getElementById :: String -> IO DomElement
getElementById = js_getElementById . toJSString

foreign import javascript unsafe "$1.getElementsByClassName($2)"
    js_getChilrenByClass :: DomElement -> JSString -> IO (JSArray DomElement_)
getChildrenByClass :: DomElement -> String -> IO (JSArray DomElement_)
getChildrenByClass e c = js_getChilrenByClass e $ toJSString c

foreign import javascript unsafe "$1.getAttribute('id')"
    js_elId :: DomElement -> IO JSString
elId :: DomElement -> IO String
elId e = js_elId e >>= return . fromJSString

bldWidget :: IO PopupWidget
bldWidget = do
    pppWnd <- getElementById "popup"
    pppCls <- getElementById "ppp-close"
    tabs   <- pppWnd `getChildrenByClass` "content" >>= fromArray
    pppTbs <- gatherTabs tabs
    return $ PopupWidget pppWnd pppCls pppTbs
    where gatherTabs :: [DomElement] -> IO [PopupTab]
          gatherTabs tbs
                     | null tbs = return [] 
                     | otherwise = mapM (\t -> elId t >>= bldTab . (drop 8)) tbs
                                                                 -- ^ cut "content-" from id
          bldTab eId = do
              exT <- getEl' "ppp-show-" eId
              inT <- getEl' "item-"     eId
              cnt <- getEl' "content-"  eId
              return $ PopupTab exT inT cnt
          getEl' prefix idStr = getElementById $ prefix ++ idStr

One thing I should mention about is that we are ignoring possible failures when searching elements on page, assuming that all target elements are presented on page for sure (for sake of simplicity of example).

Javascript Events Handling

So, now we have two tasks remaining: to bind actual clicks with reactive logic and render changes.
There are few techniques to handle javascript events.
Each technique requires to import foreign javascript function, which actually can bind some handler function to event occurrences, the difference is in what to use as a handler and how to use it. Let's take a quick look at each of them.

First possibility: using GHCJS' javascript callback primitives. The idea is to send to javascript as a handler Haskell function of type JSFun a, which is Haskell IO code wrapped by JSFun.
We can create such functions using following GHCJS primitives:

  • asyncCallback
  • asyncCallback1
  • asyncCallback2
  • syncCallback
  • syncCallback1
  • syncCallback2

These functions names are pretty self explaining, functions without a tailing number in name are functions that take no arguments; tailing 1 means that functions take one argument and 2 means functions that take 2 arguments. Sync callbacks takes two boolean arguments and Haskell function which should return a value of type IO a, and async callbacks takes one boolean argument and Haskell function. For now I can't explain what these boolean arguments mean.

Here is an example:

foreign import javascript unsafe "$1.addEventListener($2, $3);"
    js_JSFunListener :: DomElement -> JSString -> (JSFun (IO ())) -> IO ()
jsFunListener :: DomElement -> String -> (JSFun (IO ())) -> IO ()
jsFunListener el et cb = do
    js_JSFunListener el (toJSString et) cb
-- ...
-- pushClose is a function which pushes values to stream of close button click events.
callback <- asyncCallback False (sync $ pushClose ())
         -- syncCallback True False (sync $ pushClose())
jsFunListener (closeB w) "click" callback

The second opportunity is to use GHCJS shim, which provides us javascript function h$makeMVarListener().
This functions takes 4 arguments, first is a reference to MVar, last three are to configure event's propagation, event's immediate propagation and event's default behavior (you can look at its source code yourself).
This technique is used in GHCJS-JQuery.

In this case instead of passing Haskell code directly to javascript, we creating an MVar first, which will hold javascript events. h$makeMVarListener produces function, which are bound as a handler to event occurrences. When event will happen javascript will write new event value to MVar. All we need is to monitor that MVar and read values when they arrives. This could be done using forkIO. Here is an example:

foreign import javascript unsafe "$1.addEventListener($2, h$makeMVarListener($3, false, false, false));"
    js_MVarListener :: DomElement -> JSString -> JSObject (MVar JSEvent) -> IO ()
mvarListener :: DomElement
             -> String
             -> (JSEvent -> IO())
             -> IO ()
mvarListener el et hnd = do
    mv <- newEmptyMVar :: IO (MVar JSEvent)
    forkIO (forever $ takeMVar mv >>= hnd)
    js_MVarListener el (toJSString et) (mvarRef mv)
-- ...
mvarListener (closeB w) "click" (\_ -> sync $ pushClose ())

And the last opportunity is combination of previous techniques:

  • create an MVar or Chan holder for event values,
  • create a Haskell function which will write values in holder,
  • bind this function as javascript event handler,
  • fork a new thread to read new values.

Here is an example with Chan:

foreign import javascript unsafe "$1.addEventListener($2, $3);"
    js_JSFunListener1 :: DomElement -> JSString -> (JSFun (JSEvent -> IO ())) -> IO ()

chanListener :: DomElement -> String -> (JSEvent -> IO ()) -> IO ()
chanListener el et cb = do
    ch <- newChan :: IO (Chan JSEvent)
    forkIO (forever $ readChan ch >>= cb)
    callback <- syncCallback1 False True (\e -> writeChan ch e)
    js_JSFunListener1 el (toJSString et) callback

-- ...
chanListener (closeB w) "click" (\_ -> sync $ pushClose ())

In our final code we'll use the second technique.

Rendering Changes

So, we have a reactive behavior of type Behavior (Maybe PopupTab) which we will listen for values. Remember that Just values represent opened tab and Nothing value represents closed popup window.
When tab is selected both its triggers should be marked. We'll mark them by class .hold. We should make popup window and corresponding div block to be visible. We'll control visibility by changing display property. Thus, if value is Nothing we should remove all marks from triggers and make popup hidden, in all other cases we should make popup visible, mark corresponding triggers, and make proper content to be visible.

Here is the code:

foreign import javascript unsafe "$1 == $2"
    js_eq_elements :: DomElement -> DomElement -> IO Bool

foreign import javascript unsafe "$1.classList.add($2);"
    js_classListAdd :: DomElement -> JSString -> IO ()
addClass :: DomElement -> String -> IO ()
addClass el cl = js_classListAdd el $ toJSString cl

foreign import javascript unsafe "$1.classList.remove($2);"
    js_classListRemove :: DomElement -> JSString -> IO ()
removeClass :: DomElement -> String -> IO ()
removeClass el cl = js_classListRemove el $ toJSString cl

render :: PopupWidget -> Maybe PopupTab -> IO ()
render w Nothing  = (popupClose w) >> (releaseTabs w)
render w (Just t) = (popupOpen w) >> (renderTabs w t)

popupClose :: PopupWidget -> IO ()
popupClose w = window w `addClass` "Dn"

popupOpen  :: PopupWidget -> IO ()
popupOpen  w = window w `removeClass` "Dn"

releaseTabs :: PopupWidget -> IO ()
releaseTabs w = mapM_ (\t -> do
                          (exTrigger t) `removeClass` "hold"
                          (inTrigger t) `removeClass` "hold"
                          ) (tabs w)

renderTabs :: PopupWidget -> PopupTab -> IO ()
renderTabs w t = mapM_ (\t' -> do 
                            eq <- js_eq_elements (content t') (content t)
                            elId (content t') >>= putStrLn
                            print eq
                            elId (content t)  >>= putStrLn
                            if eq
                               then do
                                 (content t') `removeClass` "Dn"
                                 (exTrigger t') `addClass`  "hold"
                                 (inTrigger t') `addClass`  "hold"
                               else do
                                 (content t') `addClass`      "Dn"
                                 (exTrigger t') `removeClass` "hold"
                                 (inTrigger t') `removeClass` "hold"
                               ) (tabs w)

Great! Looks like have all pieces of puzzle now!

Full Code

Here is the final Haskell code:

{-# LANGUAGE JavaScriptFFI #-}

module Main where

import Control.Applicative( pure )
import Control.Concurrent( forkIO )
import Control.Concurrent.Chan
import Control.Concurrent.MVar
import Control.Monad( foldM, forever, liftM, mapM, replicateM, when )

import GHCJS.Foreign
import GHCJS.Types
import FRP.Sodium

data DomElement_ = DomElement_
type DomElement  = JSRef DomElement_

data JSEvent_ = JSEvent_
type JSEvent  = JSRef JSEvent_

data PopupTab    = PopupTab { exTrigger :: DomElement
                            , inTrigger :: DomElement
                            , content   :: DomElement
                            }

data PopupWidget = PopupWidget { window :: DomElement
                               , closeB :: DomElement
                               , tabs   :: [PopupTab]
                               }

-- | Assert presence of ui elements.
asserted :: IO Bool
asserted = return True

foreign import javascript unsafe "document.getElementById($1)"
    js_getElementById :: JSString -> IO DomElement
getElementById :: String -> IO DomElement
getElementById = js_getElementById . toJSString

foreign import javascript unsafe "$1.getElementsByClassName($2)"
    js_getChilrenByClass :: DomElement -> JSString -> IO (JSArray DomElement_)
getChildrenByClass :: DomElement -> String -> IO (JSArray DomElement_)
getChildrenByClass e c = js_getChilrenByClass e $ toJSString c

foreign import javascript unsafe "$1.getAttribute('id')"
    js_elId :: DomElement -> IO JSString
elId :: DomElement -> IO String
elId e = js_elId e >>= return . fromJSString

foreign import javascript unsafe "$1 == $2"
    js_eq_elements :: DomElement -> DomElement -> IO Bool

foreign import javascript unsafe "$1.classList.add($2);"
    js_classListAdd :: DomElement -> JSString -> IO ()
addClass :: DomElement -> String -> IO ()
addClass el cl = js_classListAdd el $ toJSString cl

foreign import javascript unsafe "$1.classList.remove($2);"
    js_classListRemove :: DomElement -> JSString -> IO ()
removeClass :: DomElement -> String -> IO ()
removeClass el cl = js_classListRemove el $ toJSString cl

foreign import javascript unsafe "$1.addEventListener($2, h$makeMVarListener($3, false, false, false));"
    js_addEventListener :: DomElement -> JSString -> JSObject (MVar JSEvent) -> IO ()

eventListener :: DomElement
              -> String
              -> (JSEvent -> IO())
              -> IO ()
eventListener el et hnd = do
    mv <- newEmptyMVar :: IO (MVar JSEvent)
    forkIO (forever $ takeMVar mv >>= hnd)
    js_addEventListener el (toJSString et) (mvarRef mv)


foreign import javascript unsafe "$1.style.border = '1px solid green';"
    mark :: DomElement -> IO ()

bldWidget :: IO PopupWidget
bldWidget = do
    pppWnd <- getElementById "popup"
    pppCls <- getElementById "ppp-close"
    tabs   <- pppWnd `getChildrenByClass` "content" >>= fromArray
    pppTbs <- gatherTabs tabs
    return $ PopupWidget pppWnd pppCls pppTbs
    where gatherTabs :: [DomElement] -> IO [PopupTab]
          gatherTabs tbs
                     | null tbs = return []
                     | otherwise = mapM (\t -> elId t >>= bldTab . (drop 8)) tbs
          bldTab eId = do
              exT <- getEl' "ppp-show-" eId
              inT <- getEl' "item-"     eId
              cnt <- getEl' "content-"  eId
              return $ PopupTab exT inT cnt
          getEl' prefix idStr = getElementById $ prefix ++ idStr

mkEvents :: Int -> IO [(Event a, a -> Reactive ())]
mkEvents n = sync $ replicateM n newEvent

mkReactiveUi :: PopupWidget
               -> [(Event a, a -> Reactive ())]
               -> Reactive (Behavior (Maybe PopupTab))
mkReactiveUi pppw es = do
    let closeBhv = pure (Nothing :: Maybe PopupTab)
    items' <- mapM (\t -> newBehavior (Just t) >>= return . fst) $ tabs pppw
    let items = closeBhv:items'
    events <- mapM (return . fst) es
    let eItems = zipWith (\i e -> fmap (const i) e) items events
    sel    <- hold closeBhv $ mergeFold eItems
    switch sel
  where mergeFold :: [Event a] -> Event a
        mergeFold (e:es) = foldl merge e es

render :: PopupWidget -> Maybe PopupTab -> IO ()
render w Nothing  = (popupClose w) >> (releaseTabs w)
render w (Just t) = (popupOpen w) >> (renderTabs w t)

popupClose :: PopupWidget -> IO ()
popupClose w = window w `addClass` "Dn"

popupOpen  :: PopupWidget -> IO ()
popupOpen  w = window w `removeClass` "Dn"

releaseTabs :: PopupWidget -> IO ()
releaseTabs w = mapM_ (\t -> do
                          (exTrigger t) `removeClass` "hold"
                          (inTrigger t) `removeClass` "hold"
                          ) (tabs w)

renderTabs :: PopupWidget -> PopupTab -> IO ()
renderTabs w t = mapM_ (\t' -> do 
                            eq <- js_eq_elements (content t') (content t)
                            elId (content t') >>= putStrLn
                            print eq
                            elId (content t)  >>= putStrLn
                            if eq
                               then do
                                 (content t') `removeClass` "Dn"
                                 (exTrigger t') `addClass`  "hold"
                                 (inTrigger t') `addClass`  "hold"
                               else do
                                 (content t') `addClass`      "Dn"
                                 (exTrigger t') `removeClass` "hold"
                                 (inTrigger t') `removeClass` "hold"
                               ) (tabs w)

initUi :: IO ()
initUi = do
    w    <- bldWidget
    es   <- mkEvents $ length (tabs w) + 1
    rui  <- sync $ mkReactiveUi w es
    kill <- sync $ listen (value rui) (render w)

    mapM_ (\(t,(_,f)) -> do
              eventListener (exTrigger t) "click" (\_ -> sync $ f ())
              eventListener (inTrigger t) "click" (\_ -> sync $ f ())
              ) $ zip (tabs w) (tail es)

    let pushClose = snd (head es)
    eventListener (closeB w) "click" (\_ -> sync $ pushClose ())

    putStrLn "Initialized!"

main :: IO ()
main = asserted >>= flip when initUi

Markup:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title> GHCJS | Sodium Reactive UI </title>
    <meta charset="utf-8">
    <script type="text/javascript" language="javascript" src="lib.js"></script>
    <script type="text/javascript" language="javascript" src="rts.js"></script>
    <script type="text/javascript" language="javascript" src="lib1.js"></script>
    <script type="text/javascript" language="javascript" src="out.js"></script>
    <link rel="stylesheet" type="text/css" href="style.css">
  </head>

  <body lang="ru">

    <div id="page" class="Pos">
         <h1>Header</h1>
         <p>Please select entity</p>
         <div id="ppp-select">
              <p id="ppp-show-about" class="trigger">About Us</p>
              <p id="ppp-show-contacts" class="trigger">Contact Us</p>
              <p id="ppp-show-service" class="trigger">Centers of Service</p>
              <p id="ppp-show-book-measure" class="trigger">Book measure</p>
         </div>
         <div>
           Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras sed eros nisi. Pellentesque mi lectus, molestie viverra suscipit vel, ullamcorper pretium leo. Quisque et lorem arcu. Mauris auctor porttitor adipiscing. Quisque pretium ante ac magna porttitor ornare eget eu dui. Vestibulum vel commodo nunc. Phasellus eu eros tellus. Fusce sed nibh et neque venenatis euismod ut ac leo. Morbi mi velit, tempor et consequat eu, consequat ullamcorper urna. Proin eu elementum mi, non accumsan neque. Nunc sit amet ullamcorper odio, quis sagittis nibh. Ut at facilisis tellus.
         </div>

    </div>

    <div id="popup" class="Dn Pos-A Plate">
         <div id="ppp-close" class="trigger">Close</div>

         <div id="ppp-nav">
              <div id="item-about" class="trigger">About Us</div>
              <div id="item-contacts" class="trigger">Contacts</div>
              <div id="item-service" class="trigger">Centers of Service</div>
              <div id="item-book-measure" class="trigger">Book Measure</div>
         </div>

         <div id="ppp-content">
              <div id="content-about" class="content">Lorem ipsum dolor sit amet.</div>
              <div id="content-contacts" class="content">
                   Tel: +1-234-567-890
                   </br>
           Address: Ap.01, Reactive Street, UI Town, Sodium.
              </div>
              <div id="content-service" class="content">Aliquam tortor dolor, sodales sed suscipit eget, porta non nulla.</div>
              <div id="content-book-measure" class="content">Donec sodales tortor quis ante mattis, at aliquet magna pretium.</div>
         </div>
    </div>

  </body>
  <script type="text/javascript" language="javascript">

h$main(h$mainZCMainzimain);

  </script>
</html>

Styles:

.Dn { display: none; }

.Pos   { position: relative; }
.Pos-A { position: absolute; }

.Plate {
  background-color: white;
  border: 1px solid #aaa;
}

.trigger {
  display: inline-block;
  text-decoration: underline;
  color: #555;
  padding: 5px;
}
.trigger:hover {
  cursor: pointer;
  background-color: #CCC2B2;
  color: #000;
}
.trigger.hold {
  background-color: #B2ACA1;
  text-decoration: none;
  color: #333;
}

#page {
  width: 700px;
  margin: 0 auto;
}

#popup {
  left: 50px; right: 50px;
  top: 170px; bottom: 50px;
  padding: 10px;
}

  #popup .trigger  { font-size: 80%; }
  #ppp-content     { margin: 25px; }
  #ppp-close       { color: #E36B49; }
  #ppp-close:hover { 
    color: #964730;
    background-color: transparent;
  }

BODY {
  background-color: #F5FCF8;
  font-family: sans-serif;
}

Splitting the libraries into their own repositories

While having everything under the umbrella of SodiumFRP is maybe nice it makes little sense for me, as a C++ developer, to download the source in Java, Haskell, C#, Scala, Rust, etc.

I could download what I need and delete the rest but I prefer to handle my dependencies in Git via submodules. I have got a short distance on that via help at StackOverflow[1] but not found the rest of the way yet.

So any chance of seeing sodium++, jsodium, iron-sodium, etc.?

[1] http://stackoverflow.com/questions/29099736/checkout-a-git-subdirectory-into-a-particular-folder

‘execute’ seems to cache

See http://lpaste.net/5885517651349864448 . Behaviour occurs on an i5 running Linux (NixOS) with GHC 7.8.3 and

Loading package transformers-0.3.0.0 ... linking ... done.
Loading package array-0.5.0.0 ... linking ... done.
Loading package deepseq-1.3.0.2 ... linking ... done.
Loading package old-locale-1.0.0.6 ... linking ... done.
Loading package time-1.4.2 ... linking ... done.
Loading package containers-0.5.5.1 ... linking ... done.
Loading package mtl-2.1.2 ... linking ... done.
Loading package sodium-0.10.0.2 ... linking ... done.

Is this a bug? I was certainly confused by it.

Sodium generates warnings with -Wshadow

Hi,

I use -Wshadow in my build, and sodium generates quite a few warnings with that flag switched on.

Given the complexity of the code, I can see the benefit of reducing shadowed variables to make the code easier to read.

I fixed the warnings in my branch: antmd/sodium@10b47a1

The downside is using slightly ugly, renamed variables...what do you think?

Best Regards
--Anthony

Why defer and split?

What's the purpose deferring and splitting a Stream?

What are the semantics of those operations?

It all reeks to me as a broken abstraction since every deferred Stream creates its own transaction in some (undocumented) order:

StreamSink<String> sink = new StreamSink();
Stream<String> lower = sink.map(x -> x.toLowerCase());
Stream<String> both = sink.defer().merge(lower.defer(), (a, b) -> a + b);
List<String> out = new ArrayList();
both.listen(x -> out.add(x));
sink.send("A");
sink.send("B");
sink.send("C");
System.out.println(out);

That code prints [A, a, B, b, C, c], which implies that deferred transactions see inconsistent intermediate states when deferred snapshotting and updating of Cells is involved.

Why not [Aa, Bb, Cc], i.e. make every singly deferred Stream firing happen in the same deferred transaction? Or indeed, why have defer in the first place? Does it have a use case?

Port to Swift: Questions

I think I'm going to try and port the Sodium library to the Swift programming language. Are you aware of anyone else doing this right now?

I am doing this because it looks like fun, but also because I am new to FRP and I think this would be an excellent way to learn more about it.

Because I am new to it, I may have a lot of n00b-type questions as I go along. Are you aware of any good forums for asking those kinds of FRP questions?

Build library for C++

Hi, I've added a CMakeLists.txt to the c++ directory on my fork ('Xcode' branch) to build a 'libsodium'. Are build files something you want to keep out of the source tree, or something worth adding to the official repo?

Here's the tail of the change: antmd/sodium@2d36ff2

Why Cell must follow non-detectability of steps rule?

In the book there is only one mention of this rule. I dont understand why it is important for cell? In leatest commits you removed Cell.collect because of it, but how about Cell.listen? It violates this rule too.

PS: M.b. you should move collect to Operational? It is useful method for making FSM. And listen too.

C++ build fails against boost 1.54

Looks like it's trying to pass a const T& to a const intrusive_ptr<T>&:

In file included from sodium/c++/sodium/sodium.cpp:7:
sodium/c++/sodium/../sodium/sodium.h:393:25: error: no matching constructor for initialization of 'impl::event_'
                : impl::event_(listen, new impl::event_::sample_now_func(NULL)) {}
                        ^      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
sodium/c++/sodium/../sodium/sodium.h:71:13: note: candidate constructor not viable: no known conversion from 'const impl::listen_impl_func<impl::H_EVENT>' to 'const boost::intrusive_ptr<listen_impl_func<H_EVENT> >' for 1st argument
            event_(const boost::intrusive_ptr<listen_impl_func<H_EVENT>>& p_listen_impl,
            ^
sodium/c++/sodium/../sodium/sodium.h:68:13: note: candidate constructor not viable: no known conversion from 'const impl::listen_impl_func<impl::H_EVENT>' to 'const boost::intrusive_ptr<listen_impl_func<H_EVENT> >' for 1st argument
            event_(const boost::intrusive_ptr<listen_impl_func<H_EVENT>>& p_listen_impl,
            ^
sodium/c++/sodium/../sodium/sodium.h:67:13: note: candidate constructor not viable: requires 0 arguments, but 2 were provided
            event_();
            ^
sodium/c++/sodium/../sodium/sodium.h:42:15: note: candidate constructor (the implicit move constructor) not viable: requires 1 argument, but 2 were provided
        class event_ {
              ^
sodium/c++/sodium/../sodium/sodium.h:42:15: note: candidate constructor (the implicit copy constructor) not viable: requires 1 argument, but 2 were provided
1 error generated.

A question on hold

Hi,

I'm working with the Java part of the library and am trying to run this.

public void testComplicatedStuff() {
    StreamSink<Integer> flowOfData = new StreamSink<>();
    Cell<String> value = flowOfData
            .filter(number -> number != 1)
            .hold(3)
            .updates()
            .map(number -> number.toString())
            .filter(string -> !string.equals("1"))//Even when this line isn't here
            .hold("1");//This should fail.

    assertNotSame("1", value.sample());//<--- should not be the same.
    assertEquals(3, value.sample());

    flowOfData.send(new Integer(2));
    assertEquals("2", value.sample());//These two lines of code work.
}

Currently .hold("1"); lets value start with "1" but I was expecting .filter(string -> !string.equals("1")) to prevent this initial value. So either the initial value should be null or 3?
Am I using the library wrong or is this an issue at all?

Additionally if I removed the line .filter(string -> !string.equals("1")) would it be correct to expect .filter(number -> number != 1) to prevent "1" from being used?

Why support many firings of one Event in a transaction?

I'm not sure if the idea of the same Event firing many times "simultaneously" has that much real use. Doesn't it make the model harder to reason about? If there's an Event that could yield many values at once, why not reflect that in the type signature, e.g. Event [a]? (Omitting the r type parameter here for simplification.)

If the semantics of Event was simplified to exclude multiple simultaneous firings…

  • The push action returned by newEvent, in cooperation with sync, should take care of discarding subsequent actions within the same transaction. Doing so could e.g. emit a warning while debugging, or even just crash the program.
  • merge and coalesce would no longer be needed.
  • For composing multiple Events, mergeWith would work just fine, and the functionality of merge could be replaced (in user code) with alternatives like:
mergeAsList :: Event a -> Event a -> Event [a]
mergeAsList x y = mergeWith (++) ((:[]) <$> x) ((:[]) <$> y)
  • Event a would no longer be a Monoid but we could still have the following Monoid instance:
instance Monoid a => Monoid (Event a) where
    mempty = never
    mappend = mergeWith mappend

P.S. I'm not sure how it works right now, but I'd also define sync such that no changes caused by the transaction itself are observable within the transaction. That has to do with the semantics of listen and execute as well. But maybe that's a topic worth another discussion (was thinking about this some time ago, but I'm forgetting the details right now).

`value beh` seems to spuriously skip new values

To demonstrate how accum breaks down in case of coincident Event firings, I noticed a strange issue when trying to listen to the value of a Behavior. The following code:

{-# LANGUAGE ScopedTypeVariables #-}

import FRP.Sodium

main :: IO ()
main = do
    (ints :: Event Int, fireInt) <- sync newEvent
    sums <- sync $ accum 0 $ (+) <$> ints
    unreg <- sync $ do
        un1 <- listen ints         (\x -> putStrLn $ "add:   " ++ show x)
        un2 <- listen (value sums) (\x -> putStrLn $ "total: " ++ show x)
        return $ un1 >> un2
    sync $ fireInt 10
    sync $ fireInt 20
    sync $ fireInt 30 >> fireInt 40
    sync $ fireInt 100 >> fireInt 1000000 >> fireInt 0
    unreg

is supposed (with the current denotational design) to print the following lines:

total: 0
add:   10
total: 10
add:   20
total: 30
add:   30
add:   40
total: 70 -- (this obviously would be 100 if accum got it right, but that's not the issue here)
add:   100
add:   1000000
add:   0
total: 70

However, two subsequent runs of the same script got different (!) results on my GHCI:

λ> main
total: 0
add:   10
total: 10
add:   20 -- missing "total: 30" here!
add:   30
add:   40 -- missing "total: 70" here!
add:   100
add:   1000000
add:   0 -- missing final "total: 70" here!
λ> main
total: 0
add:   10
total: 10
add:   20
total: 30
add:   30
add:   40
total: 70
add:   100
add:   1000000
add:   0 -- missing final "total: 70" here!

Changing (value sums) into (updates sums) correctly made the initial total: 0 go away and fixed the inconsistency at least in my test runs:

λ> main'
add:   10
total: 10
add:   20
total: 30
add:   30
add:   40
total: 70
add:   100
add:   1000000
add:   0
total: 70

What could be happening here? Something odd in the definition of value?

Java: The Sodium API is too dangerous.

Here some common errors I meet in my code:

  1. forget to call addClanup for the result of listen - the computations graph will be GCed.
  2. send inside listen - Exception
  3. forget to call defer in the flatMap example - the event will be lost
  4. call send second time in transaction - Exception

The send is most dangerous method in lib. Though must be the safest. We need to do something with it.

Behaviour propagation

Hello,
I recently discovered Sodium and it's amazing what it's possible to do with it! However, I'm facing a problem at the moment that makes me think that I still don't quite understand how it really works.

I'm trying to implement a "reactive cube" where for instance the width of the cube is controlled by a Behaviour. My cube is represented like this:

data RCube = RCube
    { cubeSize :: Behaviour Float
    , changeCubeSize :: Float -> Reactive ()
    , mesh :: RMesh 
    }

And I create one with this function:

newRCube :: Point -> Float -> App RCube
newRCube p s = do
    (eSize, pushSize) <- io $ sync $ newBehaviour s
    mesh <- io $ newRMesh LineStrip
    io $ sync $ listenTrans (updates eSize) $ \s ->
        scaleMesh mesh $ scaleMatrix $ Vec3 s s s

    --io $ sync $ listen (updates eSize) print
    return $ RCube eSize pushSize mesh

And for the RMesh:

data RMesh = RMesh
    { mode :: GL.PrimitiveMode
    , model :: Behaviour Mat4
    , scaleMesh :: Mat4 -> Reactive ()
    ...
    }

newRMesh :: GL.PrimitiveMode -> IO RMesh
newRMesh mode = do
    (eScale, pushScale) <- sync $ newBehaviour idMatrix
    --idem eRotation, eTranslation
    let eModel = (multmm) <$> ((multmm) <$> eScale <*> eRotation) <*> eTranslation
    --sync $ listen (updates eScale) print
    ...
    return $ Mesh mode eModel pushScale ...

Now with this I try to change the size of the cube with respect to time like this:

main :: IO ()
main = runApp app

app :: App ()
app = do
    -- OpenGL stuff
    cube <- newRCube (Point 0 0 0) 500

    io $ sync $ changeCubeSize cube 300
    io $ sync $ changeCubeSize cube 400
    io $ sync $ changeCubeSize cube 200

    forever $ do 
        t <- io $ getElapsedTime
        let s = realToFrac $ t * 100
        io $ sync $ changeCubeSize cube s
        draw cube
                ...

What I'm trying to do is to propagate the change of the cube size to the mesh scale. The behaviour of my program is inconsistent and I have absolutely no clue of where it can come from. If I call changeCubeSize before the forever in my app function, the cube size does change but not every time: if I call changeCubeSize mutliple times the last calls won't do anything. And the call in the forever doesn't do anything. I suppose there is a better way to do what I'm trying to do but I can't see it...

Using linked-list for finalizers.

I suggest to use of Single-Linked list for addCleanup and finalize. It thread-safe and near-zero overhead.

I implemented it like this (in kotlin):

    private val finalizers = AtomicReference<ListenerImpl>()

    override fun addCleanup(cleanup: Listener): StreamImpl<A> {
        (cleanup as ListenerImpl).next = finalizers.getAndSet(cleanup) // atomic operation
        return this
    }

    protected fun finalize() {
        var current = finalizers.getAndSet(null)
        while (current != null) {
            current.unlisten()
            current = current.next
        }
    }

abstract class ListenerImpl : Listener {
    var next: ListenerImpl? = null
}

It works, but I not tested it fully.

Clarification to the semantics of `value` vs. `updates`

The documentation says, after its initial value has fired, value "thereafter behaves like updates". It seems to me, however, that value only fires the last update from the transaction whereas updates fires all of them.

Why is this? And which one should be changed, the documentation (i.e. similar but not alike) or the implementation (i.e. either fire all updates in both, or only fire the last one in both)?

Debugging.

For practical use library needs strong debug functionality. If something goes wrong it is hard to understand the reason. Other libs has interesting tools for it.

For example: https://github.com/jaredly/rxvision

Tools we need:

  1. Memory leak detector.
  2. Tree structure viewer.
  3. Runtime operations printer.

For my implementation I workong on Tree Viewer. Single function dump(s: Stream<*>) prints to log tree structure in dot format.

For example this structure of function Stream.collect from testCollect:

digraph G {
{node [shape=box;style=filled;color=lightgrey;]"Node:19651F3";"Node:157696F";"Node:67C67E";"Node:BC8AB5";"Node:C1670B";"Node:1B21B69";"Node:7080DC";}
"Node:19651F3" [label="map - StreamImpl.kt:192\nrank=2"];
"Node:157696F" [label="snapshot - StreamImpl.kt:188\nrank=1"];
"Node:67C67E" [label="Node:67C67E\nrank=9223372036854775807"];
"Node:BC8AB5" [label="<init> - StreamImpl.kt:186\nrank=3"];
"Node:C1670B" [label="<init> - Sodium.kt:14\nrank=0"];
"Node:1B21B69" [label="map - StreamImpl.kt:189\nrank=2"];
"Node:7080DC" [label="lastFiringOnly - CellImpl.kt:23\nrank=4"];
"Node:C1670B" -> "Target:18D30F7"
"Target:18D30F7" -> "Node:157696F"
"Target:18D30F7" -> "action:1D2A7E8"
"Node:157696F" -> "Target:57CD70"
"Target:57CD70" -> "Node:1B21B69"
"Target:57CD70" -> "action:9273A8"
"Node:1B21B69" -> "Target:7D1D39"
"Target:7D1D39" -> "Node:67C67E"
"Target:7D1D39" -> "action:A7B76D"
"Node:157696F" -> "Target:1A7504C"
"Target:1A7504C" -> "Node:19651F3"
"Target:1A7504C" -> "action:BDB505"
"Node:19651F3" -> "Target:39AC6"
"Target:39AC6" -> "Node:BC8AB5"
"Target:39AC6" -> "action:E6A65D"
"Node:BC8AB5" -> "Target:16E852B"
"Target:16E852B" -> "Node:7080DC"
"Target:16E852B" -> "action:100955A"
"Node:7080DC" -> "Target:D2FA64"
"Target:D2FA64" -> "Node:67C67E"
"Target:D2FA64" -> "action:874448"
}

You can visualize this code in online tool: http://www.webgraphviz.com/

Java: flatMap

Library lacks primitive flatMap. It has strong practical usage. But I dont know how to implement it in terms of FRP.

Example:

public fun request(): Stream<String> {
    // perform http request.
}

public val refresh: StreamSink<Unit> = Sodium.streamSink<Unit>()
public val valueFromServer: Cell<String> = refresh.flatMap { request() }.hold("")

public fun <A, B> Stream<A>.flatMap(transform: (Event<A>) -> Stream<B>): Stream<B> {
    // how to implement?
}

Or is there a way to do it without flatMap?

StreamWithSend.send throw java.util.ConcurrentModificationException

When I run the example code dynamic.java under book/zombicus directroy, the program throws the following exception randomly:
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at sodium.StreamWithSend.send(StreamWithSend.java:20)
at sodium.Cell$13$1ApplyHandler$1.run(Cell.java:313)
at sodium.Cell$13$1ApplyHandler$1.run(Cell.java:1)
at sodium.Transaction.close(Transaction.java:251)
at sodium.Transaction.run(Transaction.java:129)
at sodium.StreamSink.send(StreamSink.java:42)
at Animate.animate(Animate.java:120)
at dynamic.main(dynamic.java:159)

another running may throw with similar stack traces:
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at sodium.StreamWithSend.send(StreamWithSend.java:20)
at sodium.Stream$12.run(Stream.java:282)
at sodium.StreamWithSend$2.run(StreamWithSend.java:29)
at sodium.StreamWithSend$2.run(StreamWithSend.java:1)
at sodium.Transaction.close(Transaction.java:251)
at sodium.Transaction.run(Transaction.java:129)
at sodium.StreamSink.send(StreamSink.java:42)
at Animate.animate(Animate.java:120)
at dynamic.main(dynamic.java:159)

the relevant code of StreamWithSend is:

  HashSet<Node.Target> listeners;
    synchronized (Transaction.listenersLock) {
        listeners = new HashSet<Node.Target>(node.listeners);
    }
    for (final Node.Target target : node.listeners) {
        trans.prioritized(target.node, new Handler<Transaction>() {
            public void run(Transaction trans2) {
                Transaction.inCallback++;
                try {  // Don't allow transactions to interfere with Sodium
                       // internals.
                    // Dereference the weak reference

// TransactionHandler uta = target.action.get();
// if (uta != null) // If it hasn't been gc'ed..., call it
// ((TransactionHandler)uta).run(trans2, a);
@SuppressWarnings("unchecked")
TransactionHandler uta = (TransactionHandler)target.action.get();
if (uta != null) // If it hasn't been gc'ed..., call it
uta.run(trans2, a);
} catch (Throwable t) {
t.printStackTrace();
}
finally {
Transaction.inCallback--;
}
}
});

it seems that the loop should use the copy listeners instead of node.listeners, but I don't understand the inter working of sodium, so I am not sure about it. I modify my local code and it seems the problem disappear.

Idea: defer(Executor)

I implemented the function that sends message to another thread. I called it onExecutor. Later, I noticed that it is very similar to defer with Executor parameter. This function is widely used in practice. May be you add it to Sodium?

Kind errors in Plain.hs, will not build

The problem appears to be: type aliases were made that take one parameter, and the code was refactored. The old qualified names were replaced by the new aliases, but in the declarations on lines 63, 65, and 72, the phantom Plain type is still getting passed like they had to be for the old names, causing a kind error.

FRP code is write-only

The problem: FRP (and m.b. functional) code is hard to read. Even in simple constructions. For example:

FRP code:

    val sink = Sodium.streamSink<Int>()
    val cell = Sodium.tx {
        val cellLoop = Sodium.cellLoop<Int>()
        val cell = sink.filter {
            it.value > cellLoop.sample().value
        }.hold(0)
        cellLoop.loop(cell)
        cell
    }

It is impossible to tell at a glance what it code does. For this we need to understand what does each primitive.

Compare it with imperative code:

    var cellVar: Int = 0

    public fun send(value: Int) {
        if (value > cellVar) {
            cellVar = value
            onCellVarChanged(value)
        }
    }

    private fun onCellVarChanged(value: Int) {

    }

Are there any empirical rules for reading code like this? Or may be writing more clear FRP code?

The only way I se here (for now) is to write comment lines for all FRP constructions. But what if code has bug in it? Comments little helps in such cases.

Generalisation of Events and Behaviours

I wrote up a proof-of-concept for the generalisation of Events and Behaviours as a small library I called Lithium (link).

The key property I used is that Reactive r (Behaviour r (Maybe a)) is isomorphic to Reactive r (Event r a), allowing me to rewrite all the core functions only using the type Reactive r (Behaviour r a).

I am not intimately familiar with Sodium so there may be semantic or runtime behaviour problems with this generalisation. To address the discreteness of events, the setter function returned by Sodium's newEvent is modified to only pulse a value; that is, to fire Just value followed by Nothing on the same transaction (line 150).

There is currently an issue with multiple firings for Lithium's behaviours on the same transaction. Particularly, only the last firing seems to occur. I am not sure if this is a bug in Sodium, my misunderstanding of how it works, or a bug in what I have done. Most critically, this problem prevents pulse from working, and thus Lithium's events are broken.

Hopefully this work helps give new insight. Critique and corrections are appreciated.

My thoughts about single events in stream

I agree with author of issue #39, but found some disadvantages.

I dont like the idea of loosing events in StreamSink and merge. The events contains important internal data. You can not just throw them away. For example this logic will be broken:

val sink = streamSink<Command>()
val state= cellLoop<State>()

val anotherCommands = sink.snapshot(state).filter {
   // need to produce another command
}

state loop sink.merge(anotherCommands).accum(...)

Also I think that the transaction complements things like merge and you no longer need transaction without multiple events in stream.

This example becomes error:

val sink = streamSink<Command>()

Sodium.tx {
   sink.send(BeginCommand())
   sink.send(SomeCommand())
   sink.send(EndCommand())
}

You can rewrite it using streamSink<List<Command>>, but Sodum.tx now becomes useless. Even more, now it is dangerous.

License?

Hi,
I'm thinking of using SodiumFRP in my Major Project (the Java implementation mainly) for my degree but I can't find any license for your library.
Am I ok to just go ahead and use SodiumFRP with my Major Project?

The name of filterNotNull

Hi,

filterNotNull is a handy method, but the name sounds like the opposite of its actual behaviour.
filterNull sounds more appropriate to me for this method. How do you think?

Thanks,

Help to dive in

Hello awesome people!
I'm new to Haskell, still studying (almost finished) "Learn You a Haskell", thus some things are still a bit tricky for me. I'm very excited by reactive programming at all, because only Haskell showed me existence of FRP, so it is another big shift in way of thinking (first one if FP itself). So I decide to look at examples and try to implement simple process - falling ball in FRP style.
And now I stuck a bit, there is a function kill in memory tests, but Hoogle can't find it. And I failed to find its implementation in Sodium source, what is kill?
And is there Sodium IRC channel somewhere? :)

The other way to make loops.

For me it was hard to undertand why we use CellLoop and StreamLoop. Now I understood, but I think the api is inconvenient. Sodium api builds graph of hierarhical nodes, used to produce computations. For building the graph there is several ways. I suggest this:

Impossible code:

            val sink = streamSink<Boolean>()
            val gated = sink.gate(held)
            val held = gated.hold(true)

Here we build gated stream. It passes boolean values until meets false. It is impossible because of held needs reference to gated and gated references to held. To eliminate this problem is not necessary to use StreamLoop. We can simply pass the StreamSink to gate.

            val sink = streamSink<Boolean>()
            val gated = streamSink<Boolean>()
            val held = gated.hold(true)
            sink.gate(gated, held)

It produces shorter graph of nodes compared to StreamLoop (3 nodes vs 4 nodes). And we no longer need in Loop classes.

What do you think?

Java: easy explaination of `loop`

I found that it is easier to understand what the loop is: invert it signature.

The a.loop(b) is just stream forwarding events from b to a. Something like this. I think it would be nice to add this definition to the book. Also need to specify that there is no difference between the subscription to a and b.

Recent change in the Haskell version of `updates` breaks old program.

I've noticed that the following change to updates, 20e5428 by @pyrtsa , causes a program of mine to become unresponsive.

For some time now, I've simply constrained away the latest sodium package (0.11.0.3) in my cabal file -- thereby avoiding the above change. However, as GHC 7.10.1 doesn't build sodium-0.11.0.2, it's perhaps time to deal with this in a more direct way.

I wonder: is this change necessary, and if so is it possible to get the old behaviour back somehow? Cheers.

Minimum Java Compilation Version

As quoted from the Java README:
"sodium.jar can be built with Java 5 or higher..."
"At the time of writing, you need to use the Project Lambda version of Java 8..."

Does this conflicting statement need correcting?

My goal is to play around with Sodium in the Android environment, which is limited to Java 1.7.
I have tried to compile the Android Project(modifying app.build compileOptions to 1.8) with JDK 1.8, only to get:
com.android.dx.cf.iface.ParseException: bad class file magic (cafebabe) or version (0034.0000)

Trying to compile sodium with 1.7 yields the keyword Optional not to be found.

Is it safe to say/change the documentation, removing the note about less than JDK 1.8 compatibility?
P.S. A workaround for 1.7 could use Guava's optional as a replacement in the Sodium framework.

Java tests do not build

Due to the Java version of Sodium switching to calling things "Stream" and "Cell", while the tests use "Event" and "Behavior", they no longer build.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.