sodiumfrp / sodium Goto Github PK
View Code? Open in Web Editor NEWSodium - Functional Reactive Programming (FRP) Library for multiple languages
Home Page: http://sodium.nz/
License: Other
Sodium - Functional Reactive Programming (FRP) Library for multiple languages
Home Page: http://sodium.nz/
License: Other
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!
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.
In the switchS
you pass suppressEarlierFirings=true
to listen
(Cell.java:426). Why?
Need to figure out how to do conditional compilation based on Java version.
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).
Since accum is implemented using hold it uses the old value of itself when applying the function inside the event.
I use split to put every event into its own transaction. Is there an easier fix?
In denotation document (page 4, Snapshot section)
Note: Snapshot is non-primitive. It can be defined in terms of
MapS
,Snapshot
andExecute
...
I think "defined in terms of MapS
, Sample
and Execute
" is what was intended here.
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?
Hi,
Just read the wiki and it seems out of date as it mentions the library is for C++ and is called Lithium?
https://github.com/SodiumFRP/sodium/wiki/Introduction
Not sure what to change the wiki to so thought I'd just flag this as a potential issue.
What if unsafeAddCleanup
being called from two or more threads on the same time. We have a risk of demaging ArrayList content.
It is a little uncomfortable to have other implementations on single repo.
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
The following code results in a runtime exception:
import FRP.Sodium
main :: IO ()
main =
do sync (mdo listen (snapshot (,) never v)
(const (putStrLn "Listen"))
v <-
accum 0 (fmap (const (+ 1)) never)
return ())
My expectation is that this is should run fine, yet it results in a "thread blocked" exception.
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/
Rewritten at 2nd of March, 2014
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:
merge
and switch
primitives;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.
When you click a trigger popup appears. There are duplicated triggers and close button inside popup.
To design UI logics we'll use Sodium. The idea adopted from Sodium's presentation:
In our case javascript click events on triggers represent button events, selected tabs represent channels and closed popup represents the fuzz.
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:
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 ofMaybe PopupTab
).
As mentioned in comments merged event stream reacts on that occurrence, actually pushing new behavior to selector (namelysel
). When all this will happen our UI behavior will get new value, which is current state.
Great! Now we have the first piece!
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).
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:
MVar
or Chan
holder for event 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.
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!
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;
}
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
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.
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
What's the purpose defer
ring and split
ting a Stream
?
What are the semantics of those operations?
It all reeks to me as a broken abstraction since every defer
red 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 Cell
s 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?
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?
This is a standard and convenient way to spread libraries.
Just follow this guide: http://central.sonatype.org/pages/ossrh-guide.html
Do you plan port this library to kotlin?
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
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.
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.
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?
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…
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.Event
s, 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).
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
?
Here some common errors I meet in my code:
addClanup
for the result of listen
- the computations graph will be GCed.send
inside listen
- Exceptiondefer
in the flatMap
example - the event will be lostsend
second time in transaction - ExceptionThe send
is most dangerous method in lib. Though must be the safest. We need to do something with it.
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...
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.
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)?
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:
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/
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
?
As an example take the signature of Stream.accum
:
final def accum[S](initState: S, f: (A, S) => S): Cell[S]
If this is changed to
final def accum[S](initState: S)(f: (A, S) => S): Cell[S]
users could omit the type argument, e.g.:
val ea = new StreamSink[Int]()
val out = new ArrayList[Int]()
val sum = ea.accum(100)((a, s) => a + s) // was `ea.accum[Int](100, (a, s) => a + s)`
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.
looks like they need some love. got onto chapter 2 and would be great to be able to run the examples.
thanks
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?
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.
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.
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.
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.
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?
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,
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? :)
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?
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
.
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.
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.
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.