Skip to content

Commit

Permalink
Merge branch 'master' into greenkeeper/nyc-13.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
taras committed Feb 15, 2019
2 parents 688e831 + 5f625a7 commit 2df2b9d
Show file tree
Hide file tree
Showing 13 changed files with 416 additions and 237 deletions.
25 changes: 23 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,40 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

## [0.13.0] - 2019-02-14

### Changed

- BREAKING: Transitions initiated from a microstate reference in the
store, now return the new reference to the *same
microstate*. Before, they always returned the root of the microstate
tree, which made modelling side-effects difficult on deep microstate trees.
- Upgraded Rollup and associated plugins to latest version (via
greenkeeper) #306, #307, #309, #310
- Fix up typos in README #308 (thanks @leonardodino)
- Improved code coverage in unit tests#320
- Remove several pieces of dead code that weren't serving any purpose
#314, #318

### Fixed

- Passing a microstate to `Array#push` and `Array#unshift` allowed
that microstate to become part of `valueOf`. Now, array unwraps all
arguments before performing any operations.

## [0.12.4] - 2018-12-12

### Fixed

- Add explicit Profunctor class name to prevent the class name from being stripped by Uglifyjs https://github.com/microstates/microstates.js/pull/303

### Fixed
### Changed

- Gather all transitions from the prototype chain https://github.com/microstates/microstates.js/pull/290

## [0.12.3] - 2018-12-12

### Fixed
### Changed

- Gather all transitions from the prototype chain https://github.com/microstates/microstates.js/pull/290

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "microstates",
"version": "0.12.4",
"version": "0.13.0",
"description": "Composable State Primitives for JavaScript",
"keywords": [
"lens",
Expand Down
98 changes: 6 additions & 92 deletions src/identity.js
Original file line number Diff line number Diff line change
@@ -1,96 +1,10 @@
import { foldl } from 'funcadelic';
import { promap, valueOf, pathOf, Meta, mount } from './meta';
import { methodsOf } from './reflection';
import { isArrayType } from './types/array';
import { create } from './microstates';
import { valueOf } from './meta';

//function composition should probably not be part of lens :)
import { At, view, Path, compose, set } from './lens';

class Location {
static id = At(Symbol('@id'));
}
import Storage from './storage';
import Pathmap from './pathmap';

export default function Identity(microstate, observe = x => x) {
let current;
let identity;
let pathmap = {};

function tick(next) {
update(next);
observe(identity);
return identity;
}

function update(microstate) {
current = microstate;
return identity = promap(proxify, persist, microstate);

function proxify(microstate) {
let path = pathOf(microstate);
let Type = microstate.constructor.Type;
let value = valueOf(microstate);

let id = view(compose(Path(path), Location.id), pathmap);

let Id = id != null && id.constructor.Type === Type ? id.constructor : IdType(Type, path);
return new Id(value);
}

function persist(id) {
let location = compose(Path(id.constructor.path), Location.id);
let existing = view(location, pathmap);
if (!equals(id, existing)) {
pathmap = set(location, id, pathmap);
return id;
} else {
return existing;
}
}
}

function IdType(Type, P) {
class Id extends Type {
static Type = Type;
static path = P;
static name = `Id<${Type.name}>`;

constructor(value) {
super(value);
Object.defineProperty(this, Meta.symbol, { enumerable: false, configurable: true, value: new Meta(this, valueOf(value))});
}
}

let methods = Object.keys(methodsOf(Type)).concat(["set"]);

Object.assign(Id.prototype, foldl((methods, name) => {
methods[name] = function(...args) {
let path = P;
let microstate = path.reduce((microstate, key) => {
if (isArrayType(microstate)) {
let value = valueOf(microstate)[key];
return mount(microstate, create(microstate.constructor.T, value), key);
} else {
return microstate[key];
}
}, current);
let next = microstate[name](...args);

return next === current ? identity : tick(next);
};
return methods;
}, {}, methods));

return Id;
}
update(microstate);
return identity;
}

function equals(id, other) {
if (other == null) {
return false;
} else {
return other.constructor.Type === id.constructor.Type && valueOf(id) === valueOf(other);
}
let { Type } = microstate.constructor;
let pathmap = Pathmap(Type, new Storage(valueOf(microstate), () => observe(pathmap.get())));
return pathmap.get();
}
11 changes: 6 additions & 5 deletions src/lens.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Functor, map, Semigroup } from 'funcadelic';

import { childAt } from './tree';

class Box {
static get of() {
return (...args) => new this(...args);
Expand All @@ -22,8 +24,7 @@ class Const extends Box {}

Functor.instance(Id, {
map(fn, id) {
let next = fn(id.value);
return next === id.value ? id : Id.of(next);
return Id.of(fn(id.value));
}
});

Expand Down Expand Up @@ -59,8 +60,8 @@ export function Lens(get, set) {

export const transparent = Lens(x => x, y => y);

export function At(property, container = {}) {
let get = context => context != null ? context[property] : undefined;
export function At(property, container) {
let get = context => context != null ? childAt(property, context) : undefined;
let set = (part, whole) => {
let context = whole == null ? (Array.isArray(container) ? [] : {}) : whole;
if (part === context[property]) {
Expand All @@ -77,6 +78,6 @@ export function At(property, container = {}) {
return Lens(get, set);
}

export function Path(path = []) {
export function Path(path) {
return path.reduce((lens, key) => compose(lens, At(key)), transparent);
}
71 changes: 17 additions & 54 deletions src/meta.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import { type, append } from 'funcadelic';
import { At, compose, transparent, over, view } from './lens';

export class Meta {
static symbol = Symbol('Meta');
static data = At(Meta.symbol);
static context = compose(Meta.data, At('context'));
static lens = compose(Meta.data, At('lens'));
static path = compose(Meta.data, At('path'));
static value = compose(Meta.data, At('value'));
static source = compose(Meta.data, At('source'));
static Type = compose(Meta.data, At('Type'));

constructor(object, value) {
this.root = object;
Expand Down Expand Up @@ -41,55 +38,21 @@ export function mount(microstate, substate, key) {
let parent = view(Meta.data, microstate);
let prefix = compose(parent.lens, At(key, parent.value));

return promap(x => x, object => {
return over(Meta.data, meta => ({
get root() {
return parent.root;
},
get lens() {
return compose(prefix, meta.lens);
},
get path() {
return parent.path.concat([key]).concat(meta.path);
},
get value() {
return meta.value;
},
get source() {
return meta.source;
}
}), object);
}, substate);
}

export const Profunctor = type(class {
static name = 'Profunctor';

promap(input, output, object) {
if (metaOf(object) == null) {
return object;
} else {
return this(object).promap(input, output, object);
return over(Meta.data, meta => ({
get root() {
return parent.root;
},
get lens() {
return compose(prefix, meta.lens);
},
get path() {
return parent.path.concat([key]).concat(meta.path);
},
get value() {
return meta.value;
},
get source() {
return meta.source;
}
}
});

export const { promap } = Profunctor.prototype;

Profunctor.instance(Object, {
promap(input, output, object) {
let next = input(object);
let keys = Object.keys(object);
if (next === object || keys.length === 0) {
return output(next);
} else {
return output(append(next, keys.reduce((properties, key) => {
return append(properties, {
get [key]() {
return promap(input, output, object[key]);
}
});
}, {})));
}
}
});
}), substate);
}
89 changes: 89 additions & 0 deletions src/pathmap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { methodsOf } from './reflection';
import { create } from './microstates';
import { view, Path } from './lens';
import { valueOf, Meta } from './meta';
import { defineChildren } from './tree';
import { stable } from 'funcadelic';

import Storage from './storage';
// TODO: explore compacting non-existent locations (from removed arrays and objects).

export default function Pathmap(Root, ref) {
let paths = new Storage();

class Location {

static symbol = Symbol('Location');

static allocate(path) {
let existing = paths.getPath(path.concat(Location.symbol));
if (existing) {
return existing;
} else {
let location = new Location(path);
paths.setPath(path, { [Location.symbol]: location });
return location;
}
}

get currentValue() {
return view(this.lens, ref.get());
}

get reference() {
if (!this.currentReference || (this.currentValue !== valueOf(this.currentReference))) {
return this.currentReference = this.createReference();
} else {
return this.currentReference;
}
}

get microstate() {
return view(this.lens, create(Root, ref.get()));
}

constructor(path) {
this.path = path;
this.lens = Path(path);
this.createReferenceType = stable(Type => {
let location = this;
let typeName = Type.name ? Type.name: 'Unknown';

class Reference extends Type {
static name = `Ref<${typeName}>`;
static location = location;

constructor(value) {
super(value);
Object.defineProperty(this, Meta.symbol, { enumerable: false, configurable: true, value: new Meta(this, valueOf(value))});
defineChildren(key => Location.allocate(path.concat(key)).reference, this);
}
}

for (let methodName of Object.keys(methodsOf(Type)).concat("set")) {
Reference.prototype[methodName] = (...args) => {
let microstate = location.microstate;
let next = microstate[methodName](...args);
ref.set(valueOf(next));
return location.reference;
};
}

return Reference;
});
}

createReference() {
let { Type } = this.microstate.constructor;
let Reference = this.createReferenceType(Type);

return new Reference(this.currentValue);
}

get(reference = paths.getPath([Location.symbol, 'reference'])) {
return reference.constructor.location.reference;
}
}

return Location.allocate([]);
}
28 changes: 28 additions & 0 deletions src/storage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { view, set, Path } from './lens';

export default class Storage {
constructor(value, observe = x => x) {
this.value = value;
this.observe = observe;
}

get() {
return this.value;
}

set(value) {
if (value !== this.value) {
this.value = value;
this.observe();
}
return this;
}

getPath(path) {
return view(Path(path), this.value);
}

setPath(path, value) {
return this.set(set(Path(path), value, this.value));
}
}
Loading

0 comments on commit 2df2b9d

Please sign in to comment.