Commit 469d3ed0 by Vladislav Lagunoff

Добавлен ~/update

parent 11e62be8
tsconfig.json
/**/node_modules
/**/yarn-error.log
\ No newline at end of file
import { Eff } from './';
import Monad from 'burrido';
// Do notation
export const { Do } = Monad({
pure: Eff.of,
bind: (m, proj) => m.chain(proj),
});
Eff.Do = Do;
declare module "./index" {
interface EffStatics {
Do<Error, Success>(iter: () => IterableIterator<Eff<Error, Success>>): Eff<Error, Success>;
}
}
......@@ -45,12 +45,11 @@ export class EffBase<Error, Success> {
return new Apply([this], proj);
}
chain<Success2>(andThen: (x: Success) => Eff<Error, Success2>): Eff<Error, Success2>;
chain<Error2, Success2>(andThen: (x: Success) => Eff<Error2, Success2>): Eff<Error|Error2, Success2>;
chain<Error2, Success2>(andThen: (x: Success) => Eff<Error2, Success2>): Eff<Error|Error2, Success2> {
return new Chain(this.toEff(), (ethr: Either<Error, Success>) => {
return ethr.fold<Eff<Error|Error2, Success2>>(failure, andThen);
});
chain<Success2>(andThen: (x: Success) => Eff<Error, Success2>): Chain<Error, Success2>;
chain<Error2, Success2>(andThen: (x: Success) => Eff<Error2, Success2>): Chain<Error|Error2, Success2>;
chain<Error2, Success2>(andThen: (x: Success) => Eff<Error2, Success2>): Chain<Error|Error2, Success2> {
// @ts-ignore
return new Chain(this.toEff(), ethr => ethr.fold(failure, andThen));
}
chainTo<Success2>(value: Eff<Error, Success2>): Eff<Error, Success2>;
......@@ -80,17 +79,17 @@ export class EffBase<Error, Success> {
}
export function of<A>(value: A): Eff<never, A> {
export function of<A>(value: A): Pure<never, A> {
return new Pure(either.success(value));
}
export function success<A>(value: A): Eff<never, A> {
export function success<A>(value: A): Pure<never, A> {
return new Pure(either.success(value));
}
export function failure<A>(value: A): Eff<A, never> {
export function failure<A>(value: A): Pure<A, never> {
return new Pure(either.failure(value));
}
......@@ -148,8 +147,8 @@ export function record<R extends Record<string, Eff<any, any>>>(rec: R): Eff<{ [
/** Traverse an array */
export function traverse<ERR, A, B>(array: A[], f: (a: A, idx: number) => Eff<ERR, B>): Eff<ERR, B[]> {
if (array.length === 0) return success([]);
export function traverse<Error, A, B>(array: A[], f: (a: A, idx: number) => Eff<Error, B>): Eff<Error, B[]> {
if (array.length === 0) return success<B[]>([]);
return ap.apply(undefined, [...array.map(f), (...args) => args]);
}
......@@ -262,7 +261,7 @@ export function go<Error, Success>(effect: Eff<Error, Success>, onNext: (x: Eith
if (effect instanceof Apply) {
let allInitialized = false;
let subscriptions: Array<Function|null>;
let subscriptions: Array<Function|undefined|null> = new Array(effect.args.length);
const initializedFlags: Array<true|undefined> = new Array(effect.args.length);
const recentValues: unknown[] = new Array(effect.args.length);
const next = idx => result => {
......@@ -279,8 +278,12 @@ export function go<Error, Success>(effect: Eff<Error, Success>, onNext: (x: Eith
subscriptions[idx] = null;
for (const unsub of subscriptions) if (unsub !== null) return;
onComplete();
};
subscriptions = effect.args.map((eff, idx) => go(eff, next(idx), complete(idx)));
};
effect.args.forEach((eff, idx) => {
const canceller = go(eff, next(idx), complete(idx));
if (subscriptions[idx] !== null) subscriptions[idx] = canceller;
});
return () => subscriptions.forEach(
funOrNull => funOrNull ? funOrNull() : void 0
......@@ -352,7 +355,26 @@ export abstract class HasEffect<Error, Success> extends EffBase<Error, Success>
export const noop: Cmd<never> = new Batch([]);
export default {
export interface EffStatics {
of: typeof of,
success: typeof success,
failure: typeof failure,
fromCallback: typeof fromCallback,
fromPromise: typeof fromPromise,
fromPromise_: typeof fromPromise_,
fromEither: typeof fromEither,
thunk: typeof thunk,
lazy: typeof thunk,
ap: typeof ap,
record: typeof record,
batch: typeof batch,
concat: typeof concat,
forget: typeof forget,
go: typeof go,
noop: typeof noop,
}
export const Eff = {
of,
success,
failure,
......@@ -369,8 +391,5 @@ export default {
forget,
go,
noop,
};
} as EffStatics;
/// monadic do-notaion https://github.com/pelotom/burrido
// import Monad from 'burrido'
// export const Do: <err,a>(iter: () => IterableIterator<Eff<err,a>>) => Eff<err,a> = Monad({ pure: of, bind: chain }).Do;
......@@ -67,7 +67,6 @@ export class HttpEffect<A> extends HasEffect<HttpError, A> {
/** send a request */
export function send(req: RequestProgress): HttpEffect<Either<Progress, Response>>;
export function send(req: Request): HttpEffect<Response>;
export function send(req: Request|RequestProgress): HttpEffect<unknown> {
return new HttpEffect(req);
......@@ -75,9 +74,9 @@ export function send(req: Request|RequestProgress): HttpEffect<unknown> {
/** shortcut for GET requests */
export function get(url: string, request?: Omit<RequestProgress, 'url'|'method'>): HttpEffect<Either<Progress, Response>>;
export function get(url: string, request?: Omit<Request, 'url'|'method'>): HttpEffect<Response>;
export function get(url: string, request?: Omit<Request|RequestProgress, 'url'|'method'>): HttpEffect<unknown> {
//export function get(url: string, request?: Omit<RequestProgress, 'url'|'method'>): HttpEffect<Either<Progress, Response>>;
export function get(url: string, request?: Omit<Request, 'url'|'method'>): Eff<HttpError, Response>;
export function get(url: string, request?: Omit<Request|RequestProgress, 'url'|'method'>) {
return send({ ...request, method: 'GET', url });
}
......
import { Update } from './';
import Monad from 'burrido';
// Do notation
export const { Do } = Monad({
pure: Update.of,
bind: (m, proj) => m.chain(proj),
});
Update.Do = Do;
declare module "./" {
interface UpdateStatic {
Do<State, Result>(iter: () => IterableIterator<Update<any, any>>): Update<State, Result>;
}
interface BoundStatics<State> {
Do<Result>(iter: () => IterableIterator<Update<any, any>>): Update<State, Result>;
}
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
</head>
<body>
<div id="app"/>
</body>
<script type="text/javascript" src="entry.bundle.js"></script>
</html>
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Update } from '../';
import '../burrido';
import * as http from '../../http';
import { InputHTMLAttributes } from 'react';
type Props = {
};
type State = {
pending: boolean;
search: string;
response?: http.Response;
};
const U = Update.bind<State>();
class Widget extends Update.Component<Props, State> {
state: State = { search: '', pending: false };
handleNameChange: InputHTMLAttributes<HTMLInputElement>['onChange'] = (e) => this.setState({ search: e.target.value });
handleSubmit = (e: React.FormEvent) => this.setState(U.Do<void>(function*() {
const { search: q }: State = yield U.get;
const response = yield U.effect(http.get(http.join('https://api.github.com/search/repositories', { q }))).pending();
yield U.patch({ response });
}));
render() {
const { search, response, pending } = this.state;
return <div>
<form onSubmit={e => (e.preventDefault(), !pending && this.handleSubmit(e))}>
<input value={search} onChange={this.handleNameChange} placeholder="Github search"/>
<button type="submit" disabled={pending}>{pending ? 'Please, wait…' : 'Go'}</button>
</form>
<textarea value={JSON.stringify(response)} readOnly/>
</div>;
}
}
ReactDOM.render(<Widget/>, document.getElementById('app'));
{
"name": "example",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"react": "^16.6.3",
"react-dom": "^16.6.3",
"ts-loader": "3",
"typescript": "^3.1.6",
"webpack": "3",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "2"
}
}
const path = require('path');
const fs = require('fs');
const webpack = require('webpack');
const tsConfig = require('./tsconfig.json');
module.exports = function(env={}) {
const entry = env.entry || './index.tsx';
const output = path.resolve(__dirname);
return {
context: path.resolve(__dirname),
entry: { entry: [entry], },
output: {
path: output,
filename: '[name].bundle.js',
},
module: {
loaders: [{
test: /\.tsx?$/,
loader: 'ts-loader',
options: {
compilerOptions: tsConfig.compilerOptions,
transpileOnly: env.hasOwnProperty('transpileOnly') ? env.transpileOnly : true,
},
}],
},
resolve: {
extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'],
alias: {
react: path.resolve(__dirname, 'node_modules/react'),
},
},
};
};
This source diff could not be displayed because it is too large. You can view the blob instead.
import * as React from 'react';
import { Eff } from '../eff';
import { Either } from '../either';
import { absurd } from '../types';
// ADT
export type Update<State, A> =
| Pure<State, A> // { value: A }
| Modify<State, A> // { proj(x: State): State } // A ~ void
| Patch<State, A> // { patch: Partial<State> } // A ~ void
| Get<State, A> // { tag: 'Get' } // A ~ State
| Children<State, A> // { key: ControlledComponent<any, any>, update: Update<any, A> }
| Effect<State, A> // { effect: Eff<never, A> }
| FromPromise<State, A> // { promise: Promise<A> }
| Chain<State, A> // { update: Update<State, any>, chain(x: any): Update<State, A> }
| Apply<State, A> // { args: Update<State, any>[], proj(...args): A }
| Batch<State, A> // { steps: Updte<State, any>[] }
| Concat<State, A> // { steps: Updte<State, any>[] }
;
// Perform state update
export function go<State, A>(update: Update<State, A>, component: React.Component<any, State>, onNext: (x: A) => void, onComplete: () => void): () => void {
if (update instanceof Pure) {
onNext(update.value);
onComplete();
return noopFunc;
}
if (update instanceof Modify) {
component['updater'].enqueueSetState(component, update.proj, () => {
onNext(void 0 as any);
update.callback && update.callback();
onComplete();
});
return noopFunc;
}
if (update instanceof Patch) {
component['updater'].enqueueSetState(component, update.patch, () => {
onNext(void 0 as any);
update.callback && update.callback();
onComplete();
});
return noopFunc;
}
if (update instanceof Get) {
onNext(component.state as any);
onComplete();
return noopFunc;
}
if (update instanceof Children) {
return go(update.update, update.component, onNext, onComplete);
}
if (update instanceof Effect) {
return update.effect.run(onNext as any, onComplete);
}
if (update instanceof FromPromise) {
update.promise().then(x => (onNext(x), onComplete()));
return noopFunc;
}
if (update instanceof Chain) {
const subscriptions: Array<Function|null> = [];
subscriptions.push(go(update.update, component, result => {
const idx = subscriptions.length;
subscriptions.push(go(update.andThen(result), component, onNext, () => {
subscriptions[idx] = null;
for (const unsub of subscriptions) if (unsub !== null) return;
onComplete();
}));
}, () => {
subscriptions[0] = null;
for (const unsub of subscriptions) if (unsub !== null) return;
onComplete();
}));
return () => subscriptions.forEach(
funOrNull => funOrNull ? funOrNull() : void 0
);
}
if (update instanceof Apply) {
let allInitialized = false;
let subscriptions: Array<Function|undefined|null> = new Array(update.args.length);
const initializedFlags: Array<true|undefined> = new Array(update.args.length);
const recentValues: unknown[] = new Array(update.args.length);
const next = idx => result => {
recentValues[idx] = result;
check_initialized: {
if (allInitialized) break check_initialized;
initializedFlags[idx] = true;
for (const flag of initializedFlags) if (flag !== true) return;
allInitialized = true;
}
onNext(update.proj.apply(void 0, recentValues));
};
const complete = idx => () => {
subscriptions[idx] = null;
for (const unsub of subscriptions) if (unsub !== null) return;
onComplete();
};
update.args.forEach((u, idx) => {
const canceller = go(u, component, next(idx), complete(idx));
if (subscriptions[idx] !== null) subscriptions[idx] = canceller;
});
return () => subscriptions.forEach(
funOrNull => funOrNull ? funOrNull() : void 0
);
}
if (update instanceof Batch) {
if (update.steps.length === 0) { onNext(void 0 as any); onComplete(); return noopFunc; }
let subscriptions: Array<Function|null>;
const loop = idx => () => {
subscriptions[idx] = null;
for (const unsub of subscriptions) if (unsub !== null) return;
onNext(void 0 as any);
onComplete(); // If control flow reaches here, that means all nested commands are completed
};
subscriptions = update.steps.map((u, idx) => go(u, component, noopFunc, loop(idx)));
return () => subscriptions.forEach(
funOrNull => funOrNull ? funOrNull() : void 0
);
}
if (update instanceof Concat) {
let unsubscribe: Function|null = null;
const loop = idx => () => {
// If condition holds, then all nested effects are completed, therefore we're done
if (idx >= update.steps.length) { onNext(void 0 as any); onComplete(); return; }
unsubscribe = go(update.steps[idx], component, noopFunc, loop(idx + 1));
};
loop(0);
return () => unsubscribe ? unsubscribe() : void 0;
}
return absurd(update);
}
// Instance methods
export class UpdateBase<State, A> {
readonly _State: State;
readonly _A: A;
map<B>(proj: (x: A) => B): Apply<State, B> {
const self = this as any as Update<State, A>;
return new Apply<State, B>([self], proj);
}
mapTo<B>(value: B): Apply<State, B> {
const self = this as any as Update<State, A>;
return new Apply<State, B>([self], () => value);
}
chain<B,State2>(andThen: (x: A) => Update<State2, B>): Chain<State&State2, B>;
chain<B>(andThen: (x: A) => Update<State, B>): Chain<State, B>;
chain<B>(andThen: (x: A) => Update<State, B>): Chain<State, B> {
const self = this as any as Update<State, A>;
return new Chain<State, B>(self, andThen);
}
chainTo<B,State2>(value: Update<State2, B>): Chain<State&State2, B>;
chainTo<B>(value: Update<State, B>): Chain<State, B>;
chainTo<B>(value: Update<State, B>): Chain<State, B> {
const self = this as any as Update<State, A>;
return new Chain<State, B>(self, () => value);
}
run(component: React.Component<any, State>, onNext?: (x: A) => void, onComplete?: () => void): () => void {
const self = this as any as Update<State, A>;
return go(self, component, onNext || noopFunc, onComplete || noopFunc);
}
pending(): Update<State & { pending: boolean }, A> {
// @ts-ignore
return patch({ pending: true }).chainTo(this.chain(value => patch({ pending: false }).mapTo(value)));
}
}
export function ap<State,A,B>(a: Update<State,A>, f: (a: A) => B): Update<State,B>;
export function ap<State,A,B,C>(a: Update<State,A>, b: Update<State,B>, f: (a: A, b: B) => C): Update<State,C>;
export function ap<State,A,B,C,D>(a: Update<State,A>, b: Update<State,B>, c: Update<State,C>, f: (a: A, b: B, c: C) => D): Update<State,D>;
export function ap<State,A,B,C,D,E>(a: Update<State,A>, b: Update<State,B>, c: Update<State,C>, d: Update<State,D>, f: (a: A, b: B, c: C, d: D) => E): Update<State,E>;
export function ap<State,A,B,C,D,E,F>(a: Update<State,A>, b: Update<State,B>, c: Update<State,C>, d: Update<State,D>, e: Update<State,E>, f: (a: A, b: B, c: C, d: D, e: E) => F): Update<State,F>;
export function ap<State,A,B,C,D,E,F,G>(a: Update<State,A>, b: Update<State,B>, c: Update<State,C>, d: Update<State,D>, e: Update<State,E>, f_: Update<State,F>, f: (a: A, b: B, c: C, d: D, e: E, f: F) => G): Update<State,G>;
export function ap(): Update<unknown, unknown> {
const args = Array.prototype.slice.call(arguments, 0, arguments.length - 1);
const proj = arguments[arguments.length - 1];
return new Apply(args, proj);
}
export function of<A>(value: A): Pure<never, A> {
return new Pure(value);
}
export function patch<State>(patch: Partial<State>, callback?: () => void): Patch<State, void> {
return new Patch(patch, callback);
}
export function modify<State>(proj: (x: State) => State, callback?: () => void): Modify<State, void> {
return new Modify(proj, callback);
}
export function effect<Error, Success>(eff: Eff<Error, Success>): Effect<{}, Either<Error, Success>> {
return new Effect(eff as any);
}
export function batch<State>(...steps: Update<State, any>[]): Batch<State, void>;
export function batch<State>(steps: Update<State, any>[]): Batch<State, void>;
export function batch(): Batch<unknown, void> {
const steps = Array.isArray(arguments[0]) ? arguments[0] : arguments;
return new Batch(steps);
}
export function concat<State>(...steps: Update<State, any>[]): Concat<State, void>;
export function concat<State>(steps: Update<State, any>[]): Concat<State, void>;
export function concat(): Concat<unknown, void> {
const steps = Array.isArray(arguments[0]) ? arguments[0] : arguments;
return new Concat(steps);
}
class Pure<State, A> extends UpdateBase<State, A> {
constructor(
readonly value: A,
) { super(); }
}
class Modify<State, A> extends UpdateBase<State, A> {
constructor(
readonly proj: (x: State) => State,
readonly callback?: () => void,
) { super(); }
}
class Patch<State, A> extends UpdateBase<State, A> {
constructor(
readonly patch: Partial<State>,
readonly callback?: () => void,
) { super(); }
}
class Get<State, A> extends UpdateBase<State, A> {
}
class Children<State, A> extends UpdateBase<State, A> {
constructor(
readonly component: React.Component<any, any>,
readonly update: Update<any, A>
) { super(); }
}
class Effect<State, A> extends UpdateBase<State, A> {
constructor(
readonly effect: Eff<never, A>,
) { super(); }
}
class FromPromise<State, A> extends UpdateBase<State, A> {
constructor(
readonly promise: () => Promise<A>,
) { super(); }
}
class Chain<State, A> extends UpdateBase<State, A> {
constructor(
readonly update: Update<State, any>,
readonly andThen: (x: any) => Update<State, A>,
) { super(); }
}
class Apply<State, A> extends UpdateBase<State, A> {
constructor(
readonly args: Update<State, any>[],
readonly proj: (...args) => A,
) { super(); }
}
class Batch<State, A> extends UpdateBase<State, A> {
constructor(
readonly steps: Update<State, any>[],
) { super(); }
}
class Concat<State, A> extends UpdateBase<State, A> {
constructor(
readonly steps: Update<State, any>[],
) { super(); }
}
// Functional helpers
const noopFunc = () => {};
export const get = new Get();
export class Component<Props, State> extends React.Component<Props, State> {
constructor(props, context) {
super(props, context);
// @ts-ignore
if (typeof(props.initialState) !== 'undefined') this.state = props.initialState;
}
setState<K extends keyof State>(
state: ((prevState: Readonly<State>, props: Readonly<Props>) => (Pick<State, K> | State | null)) | (Pick<State, K> | State | null),
callback?: () => void
): void;
setState(update: Update<State, void>, callback?: () => void): void;
setState(updateOrProjOrPatch, callback?) {
if (updateOrProjOrPatch instanceof UpdateBase) {
const onUpdate = this.props['onUpdate'] as unknown;
if (typeof(onUpdate) === 'function') {
onUpdate(updateOrProjOrPatch);
} else {
updateOrProjOrPatch.run(this, callback);
}
return;
}
if (typeof(updateOrProjOrPatch) === 'function') {
return this.setState(new Modify<State, void>(updateOrProjOrPatch, callback))
} else {
return this.setState(new Patch<State, void>(updateOrProjOrPatch, callback))
}
}
}
export interface UpdateStatic {
ap: typeof ap,
of: typeof of,
patch: typeof patch,
modify: typeof modify,
effect: typeof effect,
get: typeof get,
batch: typeof batch,
concat: typeof concat,
Component: typeof Component,
bind: typeof bind,
};
export const Update = {
ap,
of,
patch,
modify,
effect,
get,
batch,
concat,
Component,
bind,
} as UpdateStatic;
export interface BoundStatics<State> {
ap<A,B,C>(a: Update<State,A>, b: Update<State,B>, f: (a: A, b: B) => C): Apply<State,C>;
ap<A,B,C,D>(a: Update<State,A>, b: Update<State,B>, c: Update<State,C>, f: (a: A, b: B, c: C) => D): Apply<State,D>;
ap<A,B,C,D,E>(a: Update<State,A>, b: Update<State,B>, c: Update<State,C>, d: Update<State,D>, f: (a: A, b: B, c: C, d: D) => E): Apply<State,E>;
ap<A,B,C,D,E,F>(a: Update<State,A>, b: Update<State,B>, c: Update<State,C>, d: Update<State,D>, e: Update<State,E>, f: (a: A, b: B, c: C, d: D, e: E) => F): Apply<State,F>;
ap<A,B,C,D,E,F,G>(a: Update<State,A>, b: Update<State,B>, c: Update<State,C>, d: Update<State,D>, e: Update<State,E>, f_: Update<State,F>, f: (a: A, b: B, c: C, d: D, e: E, f: F) => G): Apply<State,G>;
batch(...steps: Update<State, any>[]): Batch<State, void>;
batch(steps: Update<State, any>[]): Batch<State, void>;
concat(...steps: Update<State, any>[]): Concat<State, void>;
concat(steps: Update<State, any>[]): Concat<State, void>;
of<A>(value: A): Pure<State,A>;
patch(patch: Partial<State>, callback?: () => void): Patch<State, void>;
modify(proj: (x: State) => State, callback?: () => void): Modify<State, void>;
effect<Error, Success>(eff: Eff<Error, Success>): Effect<State, Either<Error, Success>>;
get: Get<State, State>;
}
export function bind<State>() {
return Update as any as BoundStatics<State>;
}
{
"name": "update",
"version": "1.0.0",
"main": "index.ts",
"license": "MIT",
"peerDependencies": {
"react": "^16.6.3"
},
"dependencies": {
"@types/react": "^16.7.6"
},
"optionalDependencies": {
"burrido": "^1.0.8"
}
}
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@types/prop-types@*":
version "15.5.6"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.6.tgz#9c03d3fed70a8d517c191b7734da2879b50ca26c"
"@types/react@^16.7.6":
version "16.7.6"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.7.6.tgz#80e4bab0d0731ad3ae51f320c4b08bdca5f03040"
dependencies:
"@types/prop-types" "*"
csstype "^2.2.0"
burrido@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/burrido/-/burrido-1.0.8.tgz#b29684a5486ab3301149147983c006933847f324"
dependencies:
immutagen "^1.0.0"
csstype@^2.2.0:
version "2.5.7"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.5.7.tgz#bf9235d5872141eccfb2d16d82993c6b149179ff"
immutagen@^1.0.0:
version "1.0.8"
resolved "https://registry.yarnpkg.com/immutagen/-/immutagen-1.0.8.tgz#efc32eccab30a833496de43a4ea7aa4353e1097a"
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment