crockjs and apply functions need a function as value - javascript

I'm actually learning functional programming, and I'm trying to learn & use crockjs
For now, I'm trying to implement the "monadster" program described in https://fsharpforfunandprofit.com/monadster/ .
Here's what I'm having for now (just the beginning...)
const State = require("crocks/State");
const LivingPart = (unitOfForce, deadThing) => ({ unitOfForce, deadThing });
// Creating the potential living thing
const makeLiveThingM = deadThing => {
const becomeAlive = vitalForce => {
const unitOfForce = 1;
const remaining = vitalForce - unitOfForce;
return { part: LivingPart(unitOfForce, deadThing), remaining };
};
return State.get(becomeAlive);
};
// Using containers
const deadLegM = makeLiveThingM("deadLeg");
const deadArmM = makeLiveThingM("deadArm");
const livingThings = deadLegM.ap(deadArmM).evalWith(1);
console.log(livingThings);
My problem is that it throws the following error:
/Users/pc/Soft/experiments/functional/crocks/node_modules/crocks/State/index.js:101
throw new TypeError('State.ap: Source value must be a function')
^
TypeError: State.ap: Source value must be a function
From what I see there, it's probably because I don't understand the apply function, or the way State.get is running. For me it accepts a function as its internal value in my code, but it doesn't seem so.
Can anybody explains and show me what I'm doing wrong here ?
Thanks for your help

Welcome to functional programming in JS and thank you for giving crocks a shot.
In looking at that article, one of the things to note is that the author is presenting how the mechanics work inside of the State ADT, and not really how to use an existing State ADT.
I will provide an explanation on how to handle the State transactions manually, which is close to what you have in your implementation. Then I will give a brief example of how the construction helpers (like get and modify could be used to decrement the VitalForce) are used to handle the and build State transactions.
Also I will give a brief explanation of using Applicatives.
So to start lets bring in a couple ADTs from crocks
const Pair = require('crocks/Pair')
const State = require('crocks/State')
We need the State constructor to take in a function that will return a Pair (the tuple that the author mentions in the post). How the construction function works can be found here.
Before we can discuss the State function, we need that LivingPart function:
// LivingPart :: (Integer, String) -> Object
const LivingPart = (unitOfForce, part) =>
({ [part]: { unitOfForce } })
I have changed the structure from what you originally had so we could merge any given Part together with another.
Now with that bit in the mix we can implement makeLiveThing. You pretty much had it in your implementation. The only real difference here is we need to construct the State ADT with the function and return the Pair. Notice that we still bring in the String, BUT return a State ADT that will be executed when runWith is called. Remember that the current state will be passed into the function that the State instance wraps (in this case, vitalForce is our state):
// makeLiveThing :: String -> State Integer Object
const makeLiveThing = part => State(
vitalForce => {
const unitOfForce = 1
const remaining = vitalForce - unitOfForce
return Pair(
LivingPart(unitOfForce, part),
remaining
)
}
)
Now that we have a means to create a LivingPart and handle our state transaction (decrementing by 1), we can create a couple parts:
// rightLeg :: State Integer Object
const rightLeg =
makeLiveThing('right-leg')
// leftArm :: State Integer Object
const leftArm =
makeLiveThing('left-arm')
Now comes the task of joining these State instances. You were right to think to use apply, as when an ADT has both an ap method and an of method it is called an Applicative. When we have an Applicative we can think of the type as being able to combine (2) independent instances that do not depend on the result of the other one. We just need to provide a way to tell the type how to combine it.
Typically that is done with a function that can act on the types contained in the ADT. In our case it is an Object, so one way to combine (2) objects is with Object.assign. crocks provides a helper called assign that can do just that, so lets bring it in:
const assign = require('crocks/helpers/assign')
Now that we have a way to combine the internal values, we need to "lift" this function into our State type, crocks also has a function that can be used on Applicatives to lift and apply the internal values of ADT (2) instances called liftA2. Which means "lift a function into an applicative with 2 instances"
So lets bring that in as well and then create a function that will be used to join (2) Parts:
const liftA2 = require('crocks/helpers/liftA2')
// joinParts :: Applicative m => m Object -> m Object -> m Object
const joinParts =
liftA2(assign)
Now with this function we can lift and join those parts and run the result with our VitalForce:
joinParts(rightLeg, leftArm)
.runWith(10)
//=> Pair( { left-arm: { unitOfForce: 1 }, right-leg: { unitOfForce: 1 } }, 8 )
Notice the result has the resultant in the left (the combined living parts) and the state in the right (the remaining VitalForce).
Here are some references to that above:
assign function
liftA2 function
State ADT
Pair ADT
egghead course on the State API
Now I am going to show a brief example of how we can set up a single State transaction for taking VitalForce from our pool. I am not going to explain here in detail, but you should be able to glean some information between this example and the State documentation:
const State = require('crocks/State')
const constant = require('crocks/combinators/constant')
const mapProps = require('crocks/helpers/mapProps')
const { modify } = State
// decrementBy :: Integer -> Integer -> Integer
const decrementBy =
x => y => y - x
// VitalForce :: { units: Integer }
// decUnitsBy :: Integer -> VitalForce -> VitalForce
const decUnitsBy = units =>
mapProps({ units: decrementBy(units) })
// getVitalForce :: Integer -> State VitalForce VitalForce
const getVitalForce = units =>
modify(decUnitsBy(units))
.map(constant({ units }))
getVitalForce(3)
.runWith({ units: 10 })
//=> Pair( { units: 3 }, { units: 7 } )
Here are some docs for those included functions:
constant function
mapProps function
So as a side note, I do a LiveCode broadcast on this channel, I am going to go over this blog post and talk about how to implement this in crocks, if that is something you would be interested in.
Hope this helps!!

Related

Flattening a nested Lazy List that may or not have children with FP-TS or Ramda?

I just learned about lift and applicatives and on my quest to trying to understand these structures I am trying to implement a real use case.
I have a List (array) that is lazy, meaning that I can't get the count of items or their children until I load it. getting the nodes and loading it is async, same for its nested children (if any).
so if I would have the structure below:
[{title:"test1",children:[]},{title:"test2",children:[{title:"test2_1",children:[]}]}]
For each one of the children I don't know if they have children until I load the node and check the children count.
How could I check the entire list with FP (regardless of how nested it can go) either:
-By loading and checking each node at the time. and stop when we find a match or run out of nodes.
or
-By loading all nodes then (probably nesting it into Rights() and Lefts()), flattening into a single list, then foldmapping for a item by title with a predicate just like in the example below.
This is what I know works for the first match of a element in a non nested array:
[{title:"test1"},{title:"test2"},{title:"test3"}] //structure we are loading
const find= (l,f)=>l.foldMap(x=>First(f(x)?Right(x):Left()),First.empty())
const nodes = await getNodes() //not gonna even put in a type just to illustrate that getting and loading the nodes is async.
const list = List(await load(nodes)) //not gonna even put in a type just to illustrate that getting and loading the nodes is async.
console.log(find(list,x=>x.title==='test3').fold(x=>x).fold(console.error,x=>x))
Edit: current imperative working code:
This is a sharepoint code that will get all nested navigation nodes from the globalNav. The important thing is I want to understand how I can turn this into a FP implementation preferable using applicatives.
GetNavigationNodeChildren = node => node.get_children();
GetNavigationNodeRoot = spCtx => spCtx.get_web()
.get_navigation().get_topNavigationBar();
ExecQuery = spCtx => resource => {
return new Promise((res, rej) => spCtx.executeQueryAsync(
() => res(resource),
(s, a) => rej({ s, a }),
));
};
LoadResource = spCtx => resource => (spCtx.load(resource) ? resource : resource);
LoadAndExec = spCtx => async resource => {
LoadResource(spCtx)(resource )
await ExecQuery(spCtx)(resource )
return resource
}
getAll = spCtx=> async resource=> {
return {node:await LoadAndExec(spCtx)(resource),children:await hasChildren(c)(resource.get_children())}
}
hasChildren = spCtx => async resource => {
LoadResource(spCtx)(resource )
await ExecQuery(spCtx)(resource )
return Promise.all(resource.get_count()>0?resource.get_objectData().$1G_0.map(await getAll(spCtx)):[])
}
c=new SP.ClientContext()
root=GetNavigationNodeRoot(c)
await LoadAndExec(c)(root)
all=await hasChildren(c)(root)
PS: Look... The idea is to learn and understand FP, please if all you have to say is that I don't need to change the code/I am making it complicated... That is really not the point of the question.
It seems like you want to retrieve all the children of a nested root navigation node:
import * as T from 'fp-ts/Task'
// Something like this
interface NavigationNode {
readonly title: string
}
declare const getNavChildren: (node: NavigationNode) => T.Task<NavigationNode[]>
const flattenNavNode = (node: NavigationNode): T.Task<readonly NavigationNode[]> => {
// ... do something with getNavChildren to flatten the node
}
A Task<A> is simply () => Promise<A>, that is, a function that returns a Promise. This represents an asynchronous side effect.
One way you could implement flattenNavNode is like this:
import * as RA from 'fp-ts/ReadonlyArray'
import {flow, pipe} from 'fp-ts/function'
const flattenNavNode = (node: NavigationNode): T.Task<readonly NavigationNode[]> =>
pipe(
getNavChildren(node), // 1
T.chain(T.traverseArray(flattenNavNode)), // 2
T.map(flow(RA.flatten, RA.prepend(node))) // 3
)
pipe pipes a value through multiple functions (pipe(a, ab, bc) is equivalent to bc(ab(a))). Let’s go through this function pipeline (I’ve omitted some readonlys for brevity):
Get the children of the navigation node. We now have a Task<NavigationNode[]>.
This is the recursive part. We want to get all the children at all depths, so we must flatten out each one of the children from the original node.
Getting the children of the children is going be asynchronous and return some Task, but the array of children is wrapped up in a Task already from step 1, so we use chain:
declare const chain: <A, B>(f: (a: A) => Task<B>) => (ma: Task<A>) => Task<B>
This is analogous to using flatMap on an array.
Inside the T.chain we have a NavigationNode[]. You might think of using RA.map(flattenNavNode) to get the children of each node, but that would result in a Task<NavigationNode[]>[] (Array<Task<...>>) and we need to return a Task directly from chain.
T.traverseArray allows us to return a Task<NavigationNode[][]> (Task<Array<...>>) instead:
declare const traverseArray: <A, B>(f: (a: A) => Task<B>) => (as: readonly A[]) => Task<readonly B[]>
This executes the Tasks in parallel (T.traverseArray(f)(xs) is analogous to Promise.all(xs.map(f))), which is the default in fp-ts. T.traverseSeqArray will traverse the array sequentially, which is probably not what you want.
This is a specialised and more efficient variant of traverse, which comes from Traversable.
It looks like we’re almost done — we need a Task<NavigationNode[]>, and we have a Task<NavigationNode[][]>. However, we haven’t included the original node in this array, and we also need to flatten the result.
First, we use T.map to work on the NavigationNode[][] inside the Task. Using flow, which is left-to-right function composition, we first RA.flatten the array of arrays, and then RA.prepend the original node to the flattened array.
A different way of going about it is to use a (rose) Tree.
type Forest<A> = Array<Tree<A>>
interface Tree<A> {
readonly value: A
readonly forest: Forest<A>
}
fp-ts even comes with unfoldTreeM that allows you to build a tree in the context of a monad:
import {pipe} from 'fp-ts/function'
import type {Tree} from 'fp-ts/Tree'
const navNodeToTree = (node: NavigationNode): T.Task<Tree<NavigationNode>> =>
unfoldTreeM(
T.Monad // use the Task monad
)(
node, // root node
// Type annotations not necessary but I added them for clarity
(node: NavigationNode): T.Task<[value: A, forest: A[]]> =>
pipe(
getChildren(node),
T.map(children => [node, children])
)
)
Then you could flatten the tree:
import * as A from 'fp-ts/Array'
import * as RA from 'fp-ts/ReadonlyArray'
import {flow} from 'fp-ts/function'
import type {Forest} from 'fp-ts/Tree'
const flattenForest: <A>(forest: Forest<A>) => readonly A[] = RA.chain(
({value, forest}) => [value, ...flattenForest(forest)]
)
// (node: NavigationNode) => T.Task<readonly NavigationNode[]>
const flattenNavNode = flow(
// make the tree
navNodeToTree,
T.map(
flow(
// make a forest out of the tree
// equivalent to x => [x]
// I’m using the Array instead of the ReadonlyArray module because
// Forest is defined as a mutable, not readonly, array for some reason
A.of,
// flatten the forest
flattenForest
)
)
)

Recursive Observable calls returning no data

I need some help from RxJS professionals :)
I try to recursively load data from a REST API via http request.
Recursive calls are working fine, however when I susbscribe to the final Observable (returned by GetTemperatures), no data is returned within subscribe.
Seems like no data is passed back in the call chain.
Whats going wrong here?
GetTemperatures().subscribe((data: MeasureData) => {
// add data to a chart, etc...
})
GetTemperatures(): Observable<MeasureData> {
const l_startDate = new Date(2019, 0, 1);
var l_httpParams = new HttpParams()
.set('device_id', this._deviceId)
.set('module_id', this._moduleId)
.set('scale', '1hour')
.set('type', 'Temperature')
.set('date_begin', Math.floor(l_startDate.getTime() / 1000).toString())
.set('real_time', 'true')
.set('optimize', 'true');
return this._http.post<MeasureDataInternal>(this._getMeasureUrl, l_httpParams)
.pipe(
map((data: MeasureDataInternal): MeasureData => this.transformMeasureData(data)),
flatMap((data: MeasureData) => {
return this.recursiveLoadData(data);
})
);
}
recursiveLoadData(data: MeasureData): Observable<MeasureData> {
// search until now minus 1,5 hours
const endDate = new Date(Date.now() - (1.5 * 60 * 60 * 1000));
console.error('RECURSIVE begin: ' + data.value[0].date + ' end: ' + data.value[data.value.length - 1].date);
// check if complete
if (data.value[data.value.length - 1].date.getTime() >= endDate.getTime()) {
console.error('recursive ENDs here');
return EMPTY;
}
var l_httpParams = new HttpParams()
.set('device_id', this._deviceId)
.set('module_id', this._moduleId)
.set('scale', '1hour')
.set('type', 'Temperature')
.set('date_begin', Math.floor(data.value[data.value.length - 1].date.getTime() / 1000).toString())
.set('real_time', 'true')
.set('optimize', 'true');
return this._http.post<MeasureDataInternal>(this._getMeasureUrl, l_httpParams)
.pipe(
map((data2: MeasureDataInternal): MeasureData => this.transformMeasureData(data2)),
flatMap((data2: MeasureData) => {
return this.recursiveLoadData(data2);
})
)
}
I have no idea what you're really trying to accomplish, but each new step in your recursion doesn't do anything other than bringing you to the next step. So you'll want to include what you're hoping each step does.
This isn't specific to streams, this is also true of general recursion.
General Recursion
This really isn't any different from how a regular recursive function works. Say you're recursively adding up the numbers in an array, you need to add the tail of the array to the first value. If you just keep recursing on a smaller array without adding up the numbers you've popped off, you'd get the base-case value back.
This returns the last value of the array (The last value of the array is the base-case):
recursiveAdd(array){
if(array.length === 1) return array[0];
return recursiveAdd(array.shift());
}
This adds the array:
recursiveAdd(array){
if(array.length === 1) return array[0];
return array[0] + recursiveAdd(array.shift());
}
In this simple case, the + operand is doing the work at each step of the recursion. Without it, the array isn't summed up. And, of course, I could do anything. Subtract the array from 1000, average the numbers in the array, build an object from the values. Anything.
Before you make a recursive call, you have to do something. Unless what you're after is the value of the base-case (In your case, an empty stream)
Recursion with Streams
When you mergeMap a value into a stream, you don't also pass forward that value.
from([69,70,71]).pipe(
mergeMap(val => from([
String.fromCharCode(val),
String.fromCharCode(val),
String.fromCharCode(val)
]))
).subscribe(console.log);
output
e e e f f f g g g
Notice how the output doesn't include any numbers? When you mergeMap, you map values into streams. If you want the values you're mapping to be part of the stream, you must include them somehow. This is the same as with general recursion.
So, here are two examples that both include your data in the returned stream. They're very basic, but hopefully, you can take some understanding from them and apply that.
This transforms the returned steam to include your data as its first value (recursively, of course)
return this._http.post<MeasureDataInternal>(this._getMeasureUrl, l_httpParams)
.pipe(
map((data: MeasureDataInternal): MeasureData =>
this.transformMeasureData(data)
),
mergeMap((data: MeasureData) =>
this.recursiveLoadData(data).pipe(
startWith(data)
)
)
);
This creates a stream of your data, a stream of your recursive call, and merges the two streams together.
return this._http.post<MeasureDataInternal>(this._getMeasureUrl, l_httpParams)
.pipe(
map((data: MeasureDataInternal): MeasureData =>
this.transformMeasureData(data)
),
mergeMap((data: MeasureData) =>
merge (
of(data),
this.recursiveLoadData(data)
)
)
);

RxJS Filter / Search Subject, Observable, or BehaviorSubject

I just started learning RxJS. One thing I have tried to do without much luck is, figuring out how to search/filter a Subject, or create an observed array that I can search on.
I've tried piping and filtering a Subject and BehaviorSubject, but the values in the predicate are RxJS specific.
From what I've read on various posts, the way is to observe an array to use a Subject.
I can easily have two variables, one array and the Subject, and search the array. But I'd like to accomplish this one variable.
In Knockout its possible to search an observed array.
Is this possible in RxJS?
Thanks in advance.
Example:
layers: Rx.Subject<any> = new Rx.Subject<any>();
toggleLayer (layerId, visible) {
//find layer we need to toggle
// How do I search the subject to get the values added in next()?
// tried from(this.layers), pipe does not fire
const source = of(this.layers);
const example = source.pipe(filter((val, index) => {
//val is subject, and only iterates once, even if more than one value in subject
// tslint:disable-next-line:no-debugger
debugger;
return false;
}));
const sub = example.subscribe((val) => {
// tslint:disable-next-line:no-debugger
debugger;
});
}
private addLayer = (layerName, layerObj, layerType) => {
// component class is subscribed to layers subject. Update UI when layer is added
this.layers.next({
layerId: this.layerId,
name: `${layerName}_${this.layerId}`,
layerObj: layerObj,
visible: true,
layerType: layerType
});
}
I'm not 100% clear on the specifics of your ask, but maybe this example will help you.
const filterSubject = new BehaviorSubject<string>('b');
const dataSubject = new BehaviorSubject<string[]>(['foo', 'bar', 'baz', 'bat']);
const dataObservable = combineLatest(filterSubject, dataSubject).pipe(
// given an array of values in the order the observables were presented
map(([filterVal, data]) => data.filter(d => d.indexOf(filterVal) >= 0))
);
dataObservable.subscribe(arr => console.log(arr.join(',')));
// bar, baz, bat
Using combineLatest, you can have the value in dataObservable updated whenever either your filter value or your data array changes.

How can I apply timed back pressure in RxJS5?

Imagine I have the following code:
let a = Rx.Observable.of(1, 2, 3)
let b = Observable.zip(a, a, (a, b) => a + b)
b.forEach(t => console.log(t))
This immediately outputs the results. Now, how do I put a timed delay between each message as a way of back-pressure (note that I don't want a buffer; instead, I want a and b to become Cold Observables), like:
b.takeEvery(1000).forEach(t => console.log(t))
And have the exact same answer:
<wait 1s>
2
<wait 1s>
4
<wait 1s>
6
Alternative: If backpressure (ou pull mechanisms for some observables) is something not supported in RxJS, then how could one create an infinite generator without running out of resources?
Alternative 2: Other JS frameworks that support both pull and push mechanisms?
In case of RxJS 5.x back pressure is not support, but there is for example pausable operator in 4.x version. It works only with hot observables. More info on back pressure in case of 4.x and here (especially take a loot at the bottom and RxJS related description).
This Erik Meijer's tweet may be bit controversial but relevant: https://twitter.com/headinthebox/status/774635475071934464
For your own implementation of back pressure mechanism you need to have 2-way communication channel, which can be fairly easily created with 2 subjects - one for each end. Basically use next for sending messages and .subscribe for listing to the other end.
Creating a generator is doable as well - again using a subject to bridge between push- and pull-based worlds. Below an exemplary implementation for generating Fibonacci numbers.
const fib = () => {
const n = new Rx.Subject()
const f = n
.scan(c => ({ a: c.b, b: c.b + c.a }), { a: 0, b: 1 })
.map(c => c.a)
return {
$: f,
next: () => n.next()
}
}
const f = fib()
f.$.subscribe(n => document.querySelector('#r').innerHTML = n)
Rx.Observable.fromEvent(document.querySelector('#f'), 'click')
.do(f.next)
.subscribe()
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.js"></script>
<button id='f'>NEXT FIBONACCI</button>
<div id='r'>_?_<div>
Another js library which may be of interest for you is https://github.com/ubolonton/js-csp - did not use it, so not sure how it deals with back pressure.
the idea is to queue the time wait one after the other when the previous one finishes execution Fiddle
let a = Rx.Observable.of(1, 2, 3);
let b = Rx.Observable.zip(a, a, (a, b) => a + b);
// getting values into array
var x = [];
b.forEach(t => x.push(t));
var takeEvery = function(msec,items,action,index=0){
if(typeof(action) == "function")
if(index<items.length)
setTimeout(
function(item,ind){
action(item);
takeEvery(msec,items,action,ind);
},msec, items[index],++index);
};
// queueing over time
takeEvery(1000,x, function(item){
console.log(item);
});

How to manage state without using Subject or imperative manipulation in a simple RxJS example?

I have been experimenting with RxJS for two weeks now, and although I love it in principle I just cannot seem to find and implement the correct pattern for managing state. All articles and questions appear to agree:
Subject should be avoided where possible in favor of just pushing state through via transformations;
.getValue() should be deprecated entirely; and
.do should perhaps be avoided except for DOM manipulation?
The problem with all such suggestions is that none of the literature appears to directly say what you should be using instead, besides "you'll learn the Rx way and stop using Subject".
But I cannot find a direct example anywhere that specifically indicates the correct way to perform both additions and removals to a single stream/object, as the consequence of multiple other stream inputs, in a stateless and functional manner.
Before I get pointed in the same directions again, problems with uncovered literature are:
The Introduction to Reactive Programming You've been missing: great starting text, but does not specifically address these questions.
The TODO example for RxJS comes with React and involves explicit manipulation of Subjects as proxies for React Stores.
http://blog.edanschwartz.com/2015/09/18/dead-simple-rxjs-todo-list/ : explicitly uses a state object for addition and removal of items.
My perhaps 10th rewrite of the standard TODO follows - My prior iterations covered include:
starting with a mutable 'items' array - bad as state is explicit and imperatively managed
using scan to concatenate new items to an addedItems$ stream, then branching another stream where the removed items were deleted - bad as the addedItems$ stream would grow indefinitely.
discovering BehaviorSubjectand using that - seemed bad since for each new updatedList$.next() emission, it requires the previous value to iterate, meaning that Subject.getValue() is essential.
trying to stream the result of the inputEnter$ addition events into filtered removal events - but then every new stream creates a new list, and then feeding that into the toggleItem$ and toggleAll$ streams means that each new stream is dependent on the previous, and so causing one of the 4 actions (add, remove, toggle item or toggle all) requires the whole chain to be unnecessarily run through again.
Now I have come full circle, where I am back to using both Subject (and just how is it supposed to be successively iterated upon in any way without using getValue()?) and do, as show below. Myself and my colleague agree this is the clearest way, yet it of course seems the least reactive and most imperative. Any clear suggestions on the correct way for this would be much appreciated!
import Rx from 'rxjs/Rx';
import h from 'virtual-dom/h';
import diff from 'virtual-dom/diff';
import patch from 'virtual-dom/patch';
const todoListContainer = document.querySelector('#todo-items-container');
const newTodoInput = document.querySelector('#new-todo');
const todoMain = document.querySelector('#main');
const todoFooter = document.querySelector('#footer');
const inputToggleAll = document.querySelector('#toggle-all');
const ENTER_KEY = 13;
// INTENTS
const inputEnter$ = Rx.Observable.fromEvent(newTodoInput, 'keyup')
.filter(event => event.keyCode === ENTER_KEY)
.map(event => event.target.value)
.filter(value => value.trim().length)
.map(value => {
return { label: value, completed: false };
});
const inputItemClick$ = Rx.Observable.fromEvent(todoListContainer, 'click');
const inputToggleAll$ = Rx.Observable.fromEvent(inputToggleAll, 'click')
.map(event => event.target.checked);
const inputToggleItem$ = inputItemClick$
.filter(event => event.target.classList.contains('toggle'))
.map((event) => {
return {
label: event.target.nextElementSibling.innerText.trim(),
completed: event.target.checked,
};
})
const inputDoubleClick$ = Rx.Observable.fromEvent(todoListContainer, 'dblclick')
.filter(event => event.target.tagName === 'LABEL')
.do((event) => {
event.target.parentElement.classList.toggle('editing');
})
.map(event => event.target.innerText.trim());
const inputClickDelete$ = inputItemClick$
.filter(event => event.target.classList.contains('destroy'))
.map((event) => {
return { label: event.target.previousElementSibling.innerText.trim(), completed: false };
});
const list$ = new Rx.BehaviorSubject([]);
// MODEL / OPERATIONS
const addItem$ = inputEnter$
.do((item) => {
inputToggleAll.checked = false;
list$.next(list$.getValue().concat(item));
});
const removeItem$ = inputClickDelete$
.do((removeItem) => {
list$.next(list$.getValue().filter(item => item.label !== removeItem.label));
});
const toggleAll$ = inputToggleAll$
.do((allComplete) => {
list$.next(toggleAllComplete(list$.getValue(), allComplete));
});
function toggleAllComplete(arr, allComplete) {
inputToggleAll.checked = allComplete;
return arr.map((item) =>
({ label: item.label, completed: allComplete }));
}
const toggleItem$ = inputToggleItem$
.do((toggleItem) => {
let allComplete = toggleItem.completed;
let noneComplete = !toggleItem.completed;
const list = list$.getValue().map(item => {
if (item.label === toggleItem.label) {
item.completed = toggleItem.completed;
}
if (allComplete && !item.completed) {
allComplete = false;
}
if (noneComplete && item.completed) {
noneComplete = false;
}
return item;
});
if (allComplete) {
list$.next(toggleAllComplete(list, true));
return;
}
if (noneComplete) {
list$.next(toggleAllComplete(list, false));
return;
}
list$.next(list);
});
// subscribe to all the events that cause the proxy list$ subject array to be updated
Rx.Observable.merge(addItem$, removeItem$, toggleAll$, toggleItem$).subscribe();
list$.subscribe((list) => {
// DOM side-effects based on list size
todoFooter.style.visibility = todoMain.style.visibility =
(list.length) ? 'visible' : 'hidden';
newTodoInput.value = '';
});
// RENDERING
const tree$ = list$
.map(newList => renderList(newList));
const patches$ = tree$
.bufferCount(2, 1)
.map(([oldTree, newTree]) => diff(oldTree, newTree));
const todoList$ = patches$.startWith(document.querySelector('#todo-list'))
.scan((rootNode, patches) => patch(rootNode, patches));
todoList$.subscribe();
function renderList(arr, allComplete) {
return h('ul#todo-list', arr.map(val =>
h('li', {
className: (val.completed) ? 'completed' : null,
}, [h('input', {
className: 'toggle',
type: 'checkbox',
checked: val.completed,
}), h('label', val.label),
h('button', { className: 'destroy' }),
])));
}
Edit
In relation to #user3743222 very helpful answer, I can see how representing state as an additional input can make a function pure and thus scan is the best way to represent a collection evolving over time, with a snapshot of its previous state up to that point as an additional function parameter.
However, this was already how I approached my second attempt, with addedItems$ being a scanned stream of inputs:
// this list will now grow infinitely, because nothing is ever removed from it at the same time as concatenation?
const listWithItemsAdded$ = inputEnter$
.startWith([])
.scan((list, addItem) => list.concat(addItem));
const listWithItemsAddedAndRemoved$ = inputClickDelete$.withLatestFrom(listWithItemsAdded$)
.scan((list, removeItem) => list.filter(item => item !== removeItem));
// Now I have to always work from the previous list, to get the incorporated amendments...
const listWithItemsAddedAndRemovedAndToggled$ = inputToggleItem$.withLatestFrom(listWithItemsAddedAndRemoved$)
.map((item, list) => {
if (item.checked === true) {
//etc
}
})
// ... and have the event triggering a bunch of previous inputs it may have nothing to do with.
// and so if I have 400 inputs it appears at this stage to still run all the previous functions every time -any- input
// changes, even if I just want to change one small part of state
const n$ = nminus1$.scan...
The obvious solution would be to just have items = [], and manipulate it directly, or const items = new BehaviorSubject([]) - but then the only way to iterate on it appears to be using getValue to expose the previous state, which Andre Stalz (CycleJS) has commented on in the RxJS issues as something that shouldn't really be exposed (but again, if not, then how is it usable?).
I guess I just had an idea that with streams, you weren't supposed to use Subjects or represent anything via a state 'meatball', and in the first answer I'm not sure how this doesn't introduce mass chained streams which are orphaned/grow infinitely/have to build on each other in exact sequence.
I think you already found a good example with : http://jsbin.com/redeko/edit?js,output.
You take issue with the fact that this implementation
explicitly uses a state object for addition and removal of items.
However, thas is exactly the good practice you are looking for. If you rename that state object viewModel for example, it might be more apparent to you.
So what is state?
There will be other definitions but I like to think of state as follows:
given f an impure function, i.e. output = f(input), such that you can have different outputs for the same input, the state associated to that function (when it exists) is the extra variable such that f(input) = output = g(input, state) holds and g is a pure function.
So if the function here is to match an object representing a user input, to an array of todo, and if I click add on a todo list with already have 2 todos, the output will be 3 todos. If I do the same (same input) on a todo list with only one todo, the output will be 2 todos. So same input, different outputs.
The state here that allows to transform that function into a pure function is the current value of the todo array. So my input becomes an add click, AND the current todo array, passed through a function g which give a new todo array with a new todo list. That function g is pure. So f is implemented in a stateless way by making its previously hidden state explicit in g.
And that fits well with functional programming which revolves around composing pure functions.
Rxjs operators
scan
So when it comes to state management, with RxJS or else, a good practice is to make state explicit to manipulate it.
If you turn the output = g(input, state) into a stream, you get On+1 = g(In+1, Sn) and that's exactly what the scan operator does.
expand
Another operator which generalizes scan is expand, but so far I had very little use of that operator. scan generally does the trick.
Sorry for the long and mathy answer. It took me a while to get around those concepts and that's the way I made them understandable for me. Hopefully it works for you too.

Categories

Resources