sodiumfrp / sodium-typescript Goto Github PK
View Code? Open in Web Editor NEWTypescript/Javascript implementation of Sodium FRP (Functional Reactive Programming) library
Typescript/Javascript implementation of Sodium FRP (Functional Reactive Programming) library
There are numerous projects that cross-compile to WebAssembly: https://github.com/appcypher/awesome-wasm-langs
I reached out to Andreas Rossberg due to his comment here about Garbage Collection: http://lambda-the-ultimate.org/node/5349#comment-92777
He mentioned that several of the *->wasm projects out there include VM's with memory management. For example, he noted that the project at https://blogs.remobjects.com/2018/01/12/webassembly-swift-c-java-and-oxygene-in-the-browser/ uses a variant of the same Bacon & Rajan algorithm used here.
So - the question is, for the Typescript implementation, what do we think about having the core of SodiumFRP written in say Java for example, compiled to WebAssembly, and then focus the sodium-typescript
code to be glue between JS and WASM ?
I haven't looked into the implications in depth yet - but I do think it's worth giving this some thought.
(note: I realize we spoke about aspects of this before in various other threads, but it was usually in passing comments on other issues.)
I just released the new fuse-box powered build (thanks for the suggestion @graforlock !)
Couple followup questions:
npm and browser loading work, but I'm unfamiliar with bower/yarn - should those automatically work if npm does?
it would be nice if we could .gitignore the dist
folder in the master and simply have Travis build and deploy without it. From some googling it seems like the reason it's there is maybe having to do with bower. Can someone confirm or maybe suggest a strategy so we can keep the dist
folder out of master
yet still hit the deployment targets?
Hi,
There has been a lot of activities with this repo, maybe it's time to make an initial progress in publishing the uncompiled TS as NPM and bower module ?
If there are no takers then I'm keen on doing this. Let me know. Thanks
I ran into a quirky bug in the following scenario:
Core app imports 2 libs: sodium
and helpers
helpers
also imports sodium
, but uses rollup to exclude it on the build (e.g. when the app runs, the only instantiation of sodium itself should be via the one that's bundled into the core app)
However, if helpers
creates new primitives, like a new StreamSink
and sends it back to the core app for use - things get wonky.
Is this expected behavior? As a casual observation, it makes sense since Sodium does all the memory management, and an external lib is somehow still external - but I just wanted to kick the tires and see if maybe there's something wrong in the packaging system.
On a practical level, I changed the helpers to expect a pre-created sink as an argument - so it's not a big deal. Though we should document it if these are the genuine mechanics.
As discussed in SodiumFRP/sodium#106, we should add a Transaction.isActive() method that returns true if we are currently within a transaction. This is useful for integration into non-FRP GUI libraries.
Just reading the book now... and came here to check out the repo / start playing... though I see the last update was from 2016 whereas the main sodium repo is far more recent
Is that because it's just stable and working perfectly so there's nothing to do - or it's out of date / neglected? :)
I think Lazy
should be available for the end-user of the library to use.
workaround:
import { Lazy } from "sodiumjs/dist/typings/sodium/Lazy";
rather than
import { Lazy } from "sodiumjs"
Hi, I am learning Sodium by converting the sodium-java example from the functional reactive programming book to Typescript and the method filterOptional in Stream is missing. Before I try to implement it myself: is there a reason it is not implemented?
It seems that it is impossible to send a value to a sink, if nobody is listening to it.
Doing so results in the exception send() was invoked before listeners were registered
.
What is the reason for throwing this exception? Shouldn't the value just be ignored, just like with events that have no event handlers attached?
Having a little trouble running the tests. I'm in Linux Mint.
clinton@clinton-acer ~/GitHub/sodium-typescript $ npm run test
> [email protected] test /home/clinton/GitHub/sodium-typescript
> jest
โ Validation Error:
Module <rootDir>/node_modules/ts-jest/preprocessor.js in the transform option was not found.
Configuration Documentation:
https://facebook.github.io/jest/docs/configuration.html
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] test: `jest`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the [email protected] test script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
clinton@clinton-acer ~/GitHub/sodium-typescript $ node -v
v9.4.0
clinton@clinton-acer ~/GitHub/sodium-typescript $ npm -v
5.6.0
FYI
It seems to me that Cell fits into the category of "Applicative Functor" describe in the fantasy land docs: https://github.com/fantasyland/fantasy-land#applicative
By simply specifying a few prototype methods, this would allow Cell to be used interchangeably by libraries such as Ramda and Sanctuary. This is kindof awesome, since it means we get all sorts of functions for free (e.g. sequence()).
The only lines which must be included, afaik are:
Cell.prototype[FL.map] = Cell.prototype.map;
Cell.prototype[FL.ap] = function (val) {
return Cell.apply(val, this);
}
Cell[FL.of] = v => new Cell(v);
(this assumes the fantasy-land package is included as a dependency and named FL. The raw strings for keys could be used instead).
It would also be nice to have them defined without the fantasy-land-specific keys as well, so we can simply call Cell.of
for example.
I've included this and some tests in my "playground" project in a separate branch, and it currently passes on the practical level (using both Ramda and Sanctuary). See here: https://github.com/dakom/sodium-typescript-playground/blob/fantasy-land/src/tests/functional-libraries/FunctionalLibrariesTest.ts
This leaves a few questions:
Is this okay to add in to the core library? If so, I can make a pull request sometime tomorrow hopefully.
Is there something I missed? I didn't add in any tests for cells as a result of a sequence, or introducing time delays (though the tests are via listen).
What categories does Stream possibly satisfy?
Not sure if this is by design... the following code results in errors when sUpdate.listen() is commented out:
Transaction.run((): void => {
let stream = new StreamSink<number>();
//see https://github.com/SodiumFRP/sodium-typescript/blob/master/src/lib/TimerSystem.ts#L45
stream.listen(n => {});
let totalTime = new CellLoop<number>();
let sUpdate = stream.snapshot(totalTime, (a,b) => a+b);
totalTime.loop(sUpdate.hold(0));
//removing this breaks things, even if the above listener is kept
sUpdate.listen(n => {});
setInterval(() => stream.send(1), 100);
});
In this specific example the errors are about calling send() more than once in the console - though in a different test I believe it was about sending() before attaching a listener (even when explicitly attaching one to the stream, like the first listen() in the example here)
Is this by design?
The typescript code includes a couple timers for Seconds and Millisecond alarms.
Small question about those - can we switch to performance.now() instead of Date.now() ?
On a bigger topic - animation on the web is typically driven by the timestamp passed to requestAnimationFrame
callbacks. I'm not sure about the exact implementation details, but calculating deltatime from that timestamp results in smoother animation than calculating it from a new performance.now() snapshot when the callback is run.
Can we add a TimerSystem implementation based around requestAnimationFrame and the timestamp that gets passed to it? How does this gel with Sodium's approach to time in general?
There may of been a work around for this, so the compiled js does not have to be committed with type script. Just can not remember we finished at.
I was wondering how do we proceed on with wiki:
Since you cannot fork it and pull request it we can create a develop
branch on wiki locally and public repo sodium-typescript-wiki
in SodiumFRP
with default branch as develop
. We can then add another origin as develop
to point at sodium-typescript-wiki
, work there, and once its ready, just merge and push to original origin
.
Any thoughts?
I added a failing test here:
I'm not sure if this is a bug or intended behavior, but it sure feels weird! If it's not a bug, someone please explain what's going on... maybe I'll do a writeup on the wiki or something :)
Short version is - a dummy listener attached to a stream seems to disappear... or something like that. I'm not really sure what's happening... this came via reproducing via unit-test a problem I'm hitting in production code.
Slightly longer version is probably best explained by looking at the code/comments there (e.g. just causing a new empty array or just adding a new cell is fine, it's the combination of both that triggers it)
The unit test might seem a little bigger than necessary to hit the bug, but I wanted to mirror it as closely as possible to see if I was getting false positives, and anyway I think it's cool to have an example like that in the repo :)
If you can reproduce the same situation without involving the nested cells or whatever, to illustrate what's happening- please feel free!
By "weird" I mean that a listener is attached in a top-level scope to handle the empty list situation (see here- where it's fine), and is never explicitly unlistened, but it seems to be automatically de-registered somehow when the inner list is emptied, if and only if there was also something added before the emptying. The error is "send() was invoked before listeners were registered"
The comments for Cell.ts say that Cell.listen() returns a Listener that can be removed via unlisten().
However, when I try it in the code below I have two problems:
any
)Am I missing something? Is there a better way to delete a cell? (I assumed if I 'unlisten()' its listener and it goes out of scope - it's garbage collected... but maybe that's the wrong assumption?)
(note - it doesn't actually go out of scope here... this is just to demo the unlisten() issue. The book mentioned a cell switching itself out of existence - can someone supply an example of that based on the below, where holder
completely goes away?)
Transaction.run((): void => {
let counter = new StreamSink<number>();
let holder = counter.hold(Date.now());
let listener:any = holder.listen(console.log);
//how to destroy holder?
//this says unlisten is not a function
listener.unlisten();
setInterval(() => counter.send(Date.now()), 300);
});
Should DiscreteCell
also become part of the Typescript version?
Trying to replicate the book example from Java, porting to a typescript version.
Here's the code form main.ts:
import Button from './button';
import SLabel from './slabel';
import { CellLoop, Stream, transactionally } from 'sodiumjs';
class FRP
{
public static main()
{
transactionally(() =>
{
const value: CellLoop<number> = new CellLoop<number>();
const label: SLabel = new SLabel(value.map(i => i.toString()));
const btnPlus: Button = new Button("+");
const btnMinus: Button = new Button("-");
const sDelta: Stream<number> = btnPlus.sClicked.map(u => 1)
.orElse(btnMinus.sClicked.map(u => -1));
const sUpdate: Stream<number> = sDelta.snapshot(value, (delta, _value) =>
{
return delta + _value;
}).filter(n => n >= 0);
value.loop(sUpdate.hold(0));
});
}
}
FRP.main();
and slabel.ts:
import {Cell, Operational} from 'sodiumjs';
class SLabel
{
private label: HTMLElement;
private l: any;
constructor(text: Cell<string>)
{
this.l = Operational.updates(text).listen(t =>
{
this.setText(t);
});
this.label = document.createElement('h5');
this.render();
}
setText(t: string)
{
this.label.textContent = t;
}
render()
{
document.body.appendChild(this.label);
}
}
export default SLabel;
and button.ts (for completeness sake):
import {Unit, Stream, StreamSink} from 'sodiumjs';
class Button
{
private button: HTMLElement;
private sClickedSink: StreamSink<Unit>;
public sClicked: Stream<Unit>;
constructor(name: string)
{
this.button = document.createElement('button');
this.button.textContent = name;
this.sClickedSink = new StreamSink<Unit>();
this.sClicked = this.sClickedSink;
this.button.addEventListener('click', (event: Event) =>
{
this.sClickedSink.send(Unit.UNIT);
});
this.render();
}
render(): Button
{
document.body.appendChild(this.button);
return this;
}
}
export default Button;
The problem with this is that it never registers initial value 0 from the first value.loop(sUpdate.hold(0))
, so no label field value is passed in at the beginning of the transaction, resulting in no textContent.
What is the solution for the typescript version?
I think all the function arguments should be curried so that we can call them either as (a,b,c)
or a => b => c
Since we know the arity of all these functions, we can use an internal curry2, curry3
etc. rather than rely on inspecting the number of arguments etc.
Anyone opposed to this idea?
The Readme states:
You must declare any Sodium objects held inside closures using wrapper functions lambda1, lambda2 .. lambda6 - depending on the number of arguments that the closure has. These take a second argument, which is a list of the Sodium objects contained in the context of the closure.
I think it would be helpful to have a more in-depth explanation of this, maybe on the Wiki. For example, common symptoms and tips to hunt down where this might have been necessary and it was missed.
Specifically, I currently have some bug where adding more than ~10 Cells to my pipeline is causing the CPU to skyrocket, and I see it's the Sodium memory management doing something (e.g. lots of Vertex.markGrey type calls). I'm not sure yet if the cause is a missing lambda wrapper... I hope that's it! It'd be good to know if this is a specific symptom of that mistake, before I start just unwrapping/rewrapping everything that eventually comes down to a sodium mapper ;)
Hello @the-real-blackh
I think it isn't necessary, because we can use just anonymous version like (a, b) => a + b
I don't understand why u create this one https://github.com/SodiumFRP/sodium-typescript/blob/master/Lambda.ts ?
I started writing a simple program using React and Sodium, because I am not satisfied with the existing solutions (Redux or Flux feel like imperative state monad programming, the order of reducers determines the effect of an action, and data must be normalized, using identifiers everywhere, ackward and IMHO just as error prone as mutable objects)
The program is a best friend editor, you add persons to a list, and edit their first and last name, and can specify the best friend for each of these persons, by picking from the same list. I wanted something cyclic, a simple todo list is not representative IMO. When a person is removed from the list, all people referring to that person will loose their best friend.
I separated the FRP circuit from the React views, and for each property that can be edited, I added a CellSink. For each event that can occur, for example selecting a person in the list, I added a StreamSink.
I created a higher order lift function that converts every React component into a component that also accepts Cells as props. This works rather nicely, and is strongly typed using Typscript.
However, I use the FRP classes that hold the cells and streams as entities that identify a person, just as in OOP. This means I have a Person class with cell and streams in it, and for selecting a person I have a StreamSink<Person>
. The best friend property is a CellSink<Person>
Unfortunately according to the readme, it seems the Typescript version of Sodium does not seem to allow sending Sodium objects to sinks, because it breaks memory management.
Is this also the case when sending a container object holding Sodium objects? Or is it just the case for sending Sodium objects that are not rooted, i.e. Cells and Streams that are created in the fly, like sink.send(new Cell(123))?
It feels very restrictive if my case is not possible , it would mean that I have to introduce unique identifiers and cannot pass around cells and streams through sinks, although these are first class values in Sodium..
However my program does seem to work fine, so either it leaks memory, or I do not understand the limitations correctly.
What exactly are the restrictions regarding memory management in SodiumJS? What are the pitfalls?
PS: I will upload this demo program as soon as possible.
PS: It seems the GHCJS team is tackling this differently, they are actually simulating all GHC features, like threading, stable names, weak references, STM etc using JavaScript. Insane and slow, but it does give more freedom. And hopefully when webasm is stable, this will run fast...
Is there a difference between them - could the dependency be swapped out to immutable.js with little to no pain?
I tried running the tests by cloning the repo and opening sodium-typescript/examples/basic/suite.html
in chrome. The first few tests passed, but most failed as this one did:
mergeSimultaneous - FAIL:
suite.js:43 TypeError: Sodium_1.run is not a function
at suite.js:105
at test (suite.js:35)
at suite.js:102
Am I doing something wrong?
The only way to get a value out of a Cell is to sample
it.
Since calling sample
at different times, with the same Cell, will give different values - this makes extract
and extend
impure functions.
(evidently, the semantics would theoretically allow this, I think, since Time is an input to Cell... but for this implementation it doesn't work out that way)
discussion @ http://sodium.nz/t/lost-cells-in-switch/267
failing test @
Hi Richard, could you do your thing of releasing to npm again? Also, please tell me how you did it and give me the necessary keys, and so on. Thanks!
Incidentally, when I type 'npm run prerelease' I'm getting this error message:
ash: 80d441bc6cf3bfc10c15
Version: webpack 1.15.0
Time: 359ms
+ 1 hidden modules
ERROR in ./src/lib/Sodium.ts
Module build failed: TypeError: Cannot read property 'exclude' of undefined
at applyDefaults (/home/blackh/src/sodium-typescript/node_modules/awesome-typescript-loader/src/instance.ts:267:72)
at Object.ensureInstance (/home/blackh/src/sodium-typescript/node_modules/awesome-typescript-loader/src/instance.ts:146:5)
at compiler (/home/blackh/src/sodium-typescript/node_modules/awesome-typescript-loader/src/index.ts:39:20)
at Object.loader (/home/blackh/src/sodium-typescript/node_modules/awesome-typescript-loader/src/index.ts:20:18)
I added a new test here: https://github.com/SodiumFRP/sodium-typescript/blob/master/src/tests/unit/MultipleLoop.spec.ts
The last one fails with a "can't send() before listen" type of error as well as failing to clean up registrations...
Maybe it's my mistake? Please let me know... if it is I'll happily remove the test at next opportunity :)
If it shouldn't fail... any idea what's going on?
I feel like since the Cell is made up from the Stream, a listen on the Cell should cover the Stream - regardless of whether the Stream is sent inside or outside the Transaction...
add more tests either on Jasmine or Mocha
I had to install v1.0.4, otherwise I got the webpack error:
Critical dependency: require function is used in a way in which dependencies cannot be statically extracted
And the webpack loader couldn't find sodiumjs when running
#42 mentioned JavaScript supports WeakMap
Is there good browser support for either WeakSet / WeakMap these days? If so, then it may be possible for a TypeScript implementation that does not require a GC impl in the library.
Each node could have a strong ref back to its dependency and each dependency node use a WeakSet to point forward to what depends on it. The impl won't be exactly the same as the Java version, because we still don't have finalizers. Maybe a push-pull impl would work better, where nodes get marked as dirty at the right time, then at end of transaction the dirty nodes call their update methods to update there own values (in rank order of course)
Just a thought.
this will probably come at some stage and having CI kick in to run all the tests, or deployment would be great.
Is this the right way to port sequence() from the book?
If so - could it be introduced as a standard helper (maybe as a static method on Cell)?
function sequence<A>(cells:Array<Cell<A>>):Cell<Array<A>> {
let out = new Cell(new Array<A>());
for (let cell of cells) {
out = out.lift(cell, (list, a) => list.concat([a]));
}
return out;
}
This is more or less, just an experiment to see if we can pass values forward without having any forward references. It might be possible to escape implementing a GC for the TypeScript version, and still be memory safe.
https://github.com/clinuxrulz/sodium-ts-backfire/blob/master/src/lib/Vertex.ts
hook up TravisCI on publishing package update to npm or bower registry. There's a big community of NuGet users particularly those who are using Visual Studio so it'd be good sync to this registry as well. There was a discussion of delayed publishing to npm due to non-organization account, see #16
refs
This was fixed in the C# version. Make sure it's fixed in the Java version.
SodiumFRP/sodium@fb73c43
In the following test case, I get an error. Is this intentional?
test('sendToNested', (done) => {
const s1 = new StreamSink<any>();
const s2 = new StreamSink<any>();
const unlisteners = [];
//dummy listener
unlisteners.push(s2.listen(() => {
done();
}));
unlisteners.push(s1.listen(v => {
setTimeout(() => s2.send(3), 0); //Error: send() was invoked before listeners were registered
}));
console.log(unlisteners.length); // 2, e.g. at this point s2 has a listener...
s1.send("foo");
//cleanup
unlisteners.forEach(fn => fn());
});
Thanks for inviting me to the team @the-real-blackh !
For pushing new features, should I just work on a local branch and make PR requests from there? (i.e. then I can delete my fork)
Specifically for improving fantasy-land compatibility/tests and making contributions to #32 - should I just merge into master as long as all tests pass? (it won't be touching any of the real internals of sodium, just aliases and the testing framework at this point)
Another newbie question ;)
In the following code - why doesn't cell.sample() value change? I thought creating a cell from stream.hold() makes that cell update whenever the stream fires a new event?
output is like:
1497987742704 0
1497987743704 0
1497987744691 0
Transaction.run((): void => {
let stream = new StreamSink<number>();
let cell = stream.hold(0);
stream.listen(n => {
console.log(n, cell.sample());
});
setInterval(() => stream.send(Date.now()), 1000);
});
@the-real-blackh I think will be better to publish your Sodium to npm and make typescript and typescript-collection as dependencies.
The automatic npm deployment failed in the latest Travis push. Nothing was changed since the last release, though the npm message indicates that something changed on their side:
NPM API key format changed recently. If your deployment fails, check your API key in ~/.npmrc.
http://docs.travis-ci.com/user/deployment/npm/
~/.npmrc size: 48
npm ERR! publish Failed PUT 401
npm ERR! code E401
npm ERR! You must provide a one-time pass. : sodiumjs
npm ERR! A complete log of this run can be found in:
npm ERR! /home/travis/.npm/_logs/2017-10-11T05_08_51_308Z-debug.log
@rixrix is this something you can update/fix?
It seems Cell is working well but there's still an unsolved question about concat()
for Stream.
Picking up from the closed ticket at #30 -especially @the-real-blackh 's point that "if the other value is also a monoid, then you would use that as the merge argument"
...
Given:
a = Stream([3]);
b = Stream([4]);
const c = concat(a, b);
Which of the following should c
be?
Stream([3])
Stream([3,4])
Currently it's the former due to concat
not checking if the underlying value is also a Monoid, but perhaps it should be the latter? Here is as fiddle to show it in action right now - https://jsfiddle.net/dakom/ud7wq8f9/
If it should be the latter, then should a.send([6])
result in c
being Stream([6,4])
?
Thanks to James Forbes for bringing this to my attention!
We should add a deployment option to unpkg or cdnjs
It's not 100% clear to me what these folders are for:
They seem related as though examples/basic
is built from src/test
and are essentially unit tests... if that's right, maybe they should be moved alongside the other tests in spec/ ?
While not a huge deal, it's a bit confusing to see the different test folders and be unable to run one of them (I didn't see anything in package.json
that runs this 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.