Skip to content

Commit

Permalink
Overloaded API (#9)
Browse files Browse the repository at this point in the history
Version 2.1.0:

* Alternative single-reference don't-make-me-think API, fixes #8
* Fix docs
  • Loading branch information
barneycarroll authored Jan 3, 2018
1 parent 22cb6fe commit bea25d6
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 51 deletions.
91 changes: 82 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,56 @@ Throw your rose-tinted [lenses](https://medium.com/javascript-inside/an-introduc

# What

Patchinko exposes 3 functions: `P`, `S`, & `PS`.
Patchinko exposes 4 granular APIs: `P`, `S`, `PS`, & `D`.

`P` is like `Object.assign`: given `P(target, input1, input2, etc)`, it consumes inputs left to right and copies their properties onto the supplied target

*…except that…*

If any target properties are instances of `S(function)`, it will supply the scoped function with the target property for that key, and assign the result back to the target; if any target properties are `D`, it will delete the property of the same key on the target.
If any target properties are instances of `S(function)`, it will supply the scoped function with the target property for that key, and assign the result back to the target.

If any target properties are `D`, it will delete the property of the same key on the target.

`PS([ target, ] input)` is a composition of `P` & `S`, for when you need to patch recursively. If you supply a `target`, the original value will be left untouched (useful for immutable patching).

# How
***

The kitchen sink example:
Patchinko also comes with a don't-make-me-think single-reference overloaded API - useful when the essential patching operations are intuitive but the different API invocations are cognitively overbearing to determine or noisy to read.

`O` is an overloaded API that subsumes the above (with the exception of the n-ary immutable `PS` overload):

1. No arguments stands in for `D`.
2. A function argument stands in for `S`.
3. A non-function single argument stands in for `PS`.
4. …otherwise, `P`.

# Where

Supplied in CommonJS module format & as unscoped top-level references. Available on [npm](https://npmjs.org/package/patchinko) & [UNPKG cdn](https://unpkg.com/patchinko).

In Node:

```js
const {P, S, PS, D} = require('patchinko')
// or
const O = require('patchinko/overloaded')
```

In the browser:

```html
<script src=//unpkg.com/patchinko></script>
<script>console.log({P, S, PS, D})</script>
<!-- or -->
<script src=//unpkg.com/patchinko/overloaded></script>
<script>console.log({O})</script>
```

# How

The kitchen sink example:

```js
// Some arbitrary structure
const thing = {
foo: 'bar',
Expand All @@ -35,7 +68,7 @@ const thing = {
mean: (...set) =>
set.reduce((a, b) => a + b) / set.length,

fibonacci: function(x) {
fibonacci(x){
return x <= 1 ? x : this.fibonacci(x - 1) + this.fibonacci(x - 2)
},
},
Expand All @@ -55,14 +88,14 @@ P(thing, {
bish: D, // Delete property `bish`

utils: PS({ // We want to patch a level deeper
fibonacci: S(function closure(definition){ // Memoize `fibonacci`
var cache = {}
fibonacci: S(fibonacci => { // Memoize `fibonacci`
const cache = {}

return function override(x){
return function(x){
return (
x in cache
? cache[x]
: cache[x] = definition.apply(this, arguments)
: cache[x] = fibonacci.call(this, x)
)
}
})
Expand Down Expand Up @@ -90,6 +123,46 @@ Observe that:

`stupidly.deep.stucture` & `utils.fibonacci` show that any kind of structure can be modified or replaced at any kind of depth: `P` is geared towards the common case of objects, but `S` can deal with any type in whatever way necessary. You get closures for free so gnarly patch logic can be isolated at the point where it makes the most sense.

***

Using the overloaded API, the same results are achieved as follows:

```js
const O = require('patchinko/overloaded')

O(thing, {
foo: 'baz',

bish: O,

utils: O({
fibonacci: O(fibonacci => {
const cache = {}

return function(x){
return (
x in cache
? cache[x]
: cache[x] = fibonacci.call(this, x)
)
}
})
}),

stupidly: O({
deep: O({
structure: O(structure =>
structure.concat('roflmao')
)
}),
with: O(structure =>
O([], structure, {1: 'copy'}) // [1]
)
})
})
```

[1️] The single-API overload forbids the immutable `PS` overload because more than 1 argument will necessarily fork to `P`. Thus immutable nested structure patching with `O` requires 2 invocations, 1 forking to `S` and the 2nd to `P`.

# Why

Expand Down
14 changes: 12 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ function P(target){
for(var i = 1; i < arguments.length; i++)
for(var key in arguments[i])
if(arguments[i].hasOwnProperty(key))
arguments[i][key] == D
arguments[i][key] === D
? delete target[key]
: target[key] =
arguments[i][key] instanceof S
Expand Down Expand Up @@ -35,6 +35,16 @@ function PS(target, input){

function D(){}

function O(x){
return arguments.length
? 1 < arguments.length
? P.apply(arguments)
: typeof x === 'function'
? new S(x)
: PS(x)
: D
}

try {
module.exports = {P: P, S: S, PS: PS, D: D}
module.exports = {P: P, S: S, PS: PS, D: D, O: O}
} catch(e) {}
34 changes: 34 additions & 0 deletions overloaded.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
function O(a, b){
if(arguments.length == 1)
if(typeof a == 'function')
if(!(this instanceof O))
return new O(a)

else
this.apply = function(c){
return a(c)
}

else
return new O(function(c){
return O(c, a)
})

else {
for(var i = 1; i < arguments.length; i++, b = arguments[i])
for(var key in b)
if(b.hasOwnProperty(key))
b[key] == O
? delete a[key]
: a[key] =
b[key] instanceof O
? b[key].apply(a[key])
: b[key]

return a
}
}

try {
module.exports = O
} catch(e) {}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "patchinko",
"version": "2.0.0",
"version": "2.1.0",
"description": "Concise monkey-patching utility",
"main": "index.js",
"repository": "[email protected]:barneycarroll/patchinko.git",
Expand Down
55 changes: 16 additions & 39 deletions tests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ o.spec('`P`', () => {

o('is equivalent to `Object.assign` in the absence of `S`', () => {
const [factoryA, factoryB] = [
() => ({a:'foo', b:2, d: {bar: 'z'}, f: [3, 4]}),
() => ({a:'baz', c:3, d: {fizz: 'z'}, f: 'buzz'}),
() => ({a: 'foo', b: 2, d: {bar: 'z'}, f: [3, 4]}),
() => ({a: 'baz', c: 3, d: {fizz: 'z'}, f: 'buzz'}),
]

const [a, b] = [factoryA(), factoryB()]
Expand All @@ -52,7 +52,7 @@ o.spec('`P`', () => {
})

o.spec('with `S`', () => {
o('supplies the target\'s property value to the scoped function', () => {
o("supplies the target's property value to the scoped function", () => {
const unique = Symbol('unicum')

let interception
Expand All @@ -75,58 +75,35 @@ o.spec('`P`', () => {

o(
P(
{ a: unique1 },
{ a: unique1 },
{ a: S(I) }
).a
).equals(
unique1
)
)

o(
P(
{ a: unique1 },
{ a: unique1 },
{ a: S(() => unique2) }
).a
).equals(
unique2
)
)
})
})

o.spec('with `D`', () => {
o('deletes the target property with the same key', () => {
o(
P(
{ a: 1, b: 2 },
{ a: 1, b: 2 },
{ a: D }
)
).deepEquals(
{ b: 2 }
)
})

o('assigns the product of any scoped closures to the target properties', () => {
const unique1 = Symbol('unicum1')
const unique2 = Symbol('unicum2')

o(
P(
{ a: unique1 },
{ a: S(I) }
).a
).equals(
unique1
)

o(
P(
{ a: unique1 },
{ a: S(() => unique2) }
).a
).equals(
unique2
)
})
})
})

Expand Down Expand Up @@ -155,17 +132,17 @@ o.spec('`PS`', () => {
})
}
)
)
.deepEquals(
{
a: {
b: two
}
).deepEquals(
{
a: {
b: two
}
)
}
)

o(interception).equals(one)
})

o('accepts a custom target', () => {
o(
P(
Expand All @@ -174,7 +151,7 @@ o.spec('`PS`', () => {
},
{
a: PS(
[],
[],
{
1: 3
}
Expand Down
Loading

0 comments on commit bea25d6

Please sign in to comment.