Coder Social home page Coder Social logo

sodiumfrp / sodium-typescript Goto Github PK

View Code? Open in Web Editor NEW
126.0 126.0 17.0 2.38 MB

Typescript/Javascript implementation of Sodium FRP (Functional Reactive Programming) library

TypeScript 95.87% JavaScript 3.73% HTML 0.30% CSS 0.10%
frp functional-programming functional-reactive-programming javascript lambda reactive sodium typescript

sodium-typescript's People

Contributors

clinuxrulz avatar dakom avatar dependabot[bot] avatar jam40jeff avatar martinkolarik avatar rixrix 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

sodium-typescript's Issues

WebAssembly

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.)

Deployed dist folder

I just released the new fuse-box powered build (thanks for the suggestion @graforlock !)

Couple followup questions:

  1. npm and browser loading work, but I'm unfamiliar with bower/yarn - should those automatically work if npm does?

  2. 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?

packaging uncompiled TypeScript as npm and bower module

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

Creating FRP from separate lib

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.

add Transaction.isActive()

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.

Is this up to date?

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? :)

Lib.ts does not export Lazy

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"

Stream.filterOptional missing

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?

Sinks without listeners?

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?

Jest on Linux Mint

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

Fantasy-land compatability for Cell

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:

  1. Is this okay to add in to the core library? If so, I can make a pull request sometime tomorrow hopefully.

  2. 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).

  3. What categories does Stream possibly satisfy?

Required listener for snapshot/loop?

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?

Timers and requestAnimationFrame

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?

Wiki

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?

The case of the disappearing listener

I added a failing test here:

//-------HERE'S WHERE IT GETS WEIRD!!! -------------

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"

unlisten is not a function

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:

  1. Listen is not an available module (so I cast to any)
  2. listener.unlisten() doesn't exist

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);
});

DiscreteCell?

Should DiscreteCell also become part of the Typescript version?

Replicating example from the book

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?

Curry the functions

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?

Package and tooling upgrades

  • Update Typescript version to preferably 2.0 or higher (2.0.8 is the latest stable).
  • NPM package updated to a version 0.9.1 which will include the new API changes and Typescript upgrade.
  • Same as above but for bower.

Memory Management Tips

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 ;)

higher order dependencies not being tracked too restrictive?

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...

Problem running tests in chrome

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?

Remove Comonad

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)

rixrix, can you release 1.0.0 to npm?

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)

Multiple Loop fail (send before listener error)

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...

v1.0.7 doesn't seem to work with webpack

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

WeakSet / WeakMap alternative sodium impl.

#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.

porting sequence()

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;
}

Failure to send

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());
});

Fork vs. local branch

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)

Updating cell that was created via stream.hold()

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);
});

npm deployment failed

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?

Fantasy-land compatibility for Stream

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?

  1. Stream([3])
  2. 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!

examples and test

It's not 100% clear to me what these folders are for:

  1. src/test
  2. examples/basic

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)

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.