Skip to content

Commit 27c099a

Browse files
authored
Merge pull request #54 from vankeisb/feature/piping-updates
introduce updatePiped
2 parents 1432fef + af5b47f commit 27c099a

2 files changed

Lines changed: 128 additions & 0 deletions

File tree

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* MIT License
3+
*
4+
* Copyright (c) 2019 Rémi Van Keisbelck
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*
24+
*/
25+
26+
import { Cmd } from './Cmd';
27+
import { genericUpdatePiped, updatePiped } from './UpdatePiped';
28+
29+
describe('UpdatePiped', () => {
30+
type MyModel = number;
31+
type MyCmd = readonly string[];
32+
33+
test('generic', () => {
34+
const model0: MyModel = 6;
35+
const [model, cmd] = genericUpdatePiped(
36+
(cmd1: MyCmd, cmd2: MyCmd) => new Array().concat(...cmd1, ...cmd2),
37+
[],
38+
model0,
39+
(model) => [model + 1, ['add 1']],
40+
(model) => [model * 6, ['multiply by 6']],
41+
);
42+
expect(model).toBe(42);
43+
expect(cmd).toEqual(['add 1', 'multiply by 6']);
44+
});
45+
46+
test('example with none handling', () => {
47+
type MyMsg = string;
48+
const model0: MyModel = 6;
49+
const [model, cmd] = updatePiped(
50+
model0,
51+
(model) => [model + 1, Cmd.none()],
52+
(model) => [model * 6, Cmd.none()],
53+
);
54+
expect(model).toBe(42);
55+
expect(cmd).toEqual(Cmd.none());
56+
});
57+
});

core/src/TeaCup/UpdatePiped.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* MIT License
3+
*
4+
* Copyright (c) 2019 Rémi Van Keisbelck
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*
24+
*/
25+
26+
import { Cmd } from './Cmd';
27+
28+
export type UpdateFunction<Model, Msg> = GenericUpdateFunction<Model, Cmd<Msg>>;
29+
30+
/**
31+
* Piped updates.
32+
* Useful for chaining building blocks making up the update loop.
33+
*/
34+
export function updatePiped<Model, Msg>(
35+
model: Model,
36+
...updates: readonly UpdateFunction<Model, Msg>[]
37+
): [Model, Cmd<Msg>] {
38+
const none: Cmd<Msg> = Cmd.none();
39+
const combine = (cmd1: Cmd<Msg>, cmd2: Cmd<Msg>) => {
40+
const none1 = isNone(cmd1);
41+
const none2 = isNone(cmd2);
42+
return none1 && none2 ? cmd1 : none1 ? cmd2 : none2 ? cmd1 : Cmd.batch([cmd1, cmd2]);
43+
};
44+
return genericUpdatePiped(combine, none, model, ...updates);
45+
}
46+
47+
function isNone<Msg>(cmd: Cmd<Msg>): boolean {
48+
return cmd.constructor.name === 'CmdNone';
49+
}
50+
51+
export type GenericUpdateFunction<Model, Cmd> = (model: Model) => [Model, Cmd];
52+
53+
/**
54+
* Generic implementation of chained updates.
55+
* See updatePiped().
56+
*/
57+
export function genericUpdatePiped<Model, Cmd>(
58+
combine: (cmd1: Cmd, cmd2: Cmd) => Cmd,
59+
none: Cmd,
60+
model: Model,
61+
...updates: readonly GenericUpdateFunction<Model, Cmd>[]
62+
): [Model, Cmd] {
63+
return updates.reduce<[Model, Cmd]>(
64+
(acc: [Model, Cmd], update: GenericUpdateFunction<Model, Cmd>) => {
65+
const [model0, cmd0] = acc;
66+
const [model1, cmd1] = update(model0);
67+
return [model1, combine(cmd0, cmd1)];
68+
},
69+
[model, none],
70+
);
71+
}

0 commit comments

Comments
 (0)