KEMBAR78
Stamps - a better way to object composition | PPTX
Stamps
Rethinking best practices
medium.com/@koresar
ctor()
method
X()
property A
ctor()
method
X()
property Aproperty B
ctor()
property A
property B
method
Y()
method
Z()
method
X()
ctor()
method
X()
property A
property Bmethod
Y()
method
Z()
…
ctor()
method X()
property A
property B
method Y()
method Z()
property C
ctor(override)
pony
Zuckerberg
yolo
the god
the true god
the new true
god
useless shit
useful thing()
leprosarium
matrix
new useless
shit
hell on Earth
pain
suffer
Typical class inheritance story
But actually I needed only
these:
ctor()
property B
method X()
useful thing()
Typical class inheritance story
• 1 constructor
• 211 methods
• 130 properties
• 55 events
(which essentially are properties
too)
• 1 static field
• 1 attribute
Rethinking inheritance
{
ctor()
method X()
property B
useful thing()
}
…
ctor()
method
X()
property B
useful
thing
pain
suffer
hell on
Earth
useless
shit
property A
method
Y()
method
Z()
Zuckerber
g
property C
Java/C# developers are like
To make that happen we invented stamps
Stamps
…
ctor()
method
X()
property B
useful
thing
pain
suffer
hell on
Earth
useless
shit
property A
method
Y()
method
Z()
Zuckerber
g
property C
• Stamps are composable behaviours
• Stamps are not a library or a module
• Stamps are a specification (like
Promises)
• Various stamp implementations are
compatible with each other
• Stamps are factory functions (like
classes)
import compose from 'stamp-implementation';
// not a real module
Stamps
The only thing in the specification
const Ctor = compose({
initializers: [function (...args) {
console.log('Hello Node Ninjas')
}]
});
Ctor(...args); // “Hello Node Ninjas”
Stamps
Constructor aka initializer
const MethodX = compose({
methods: {
X() {
// ...
}
}
});
MethodX().X();
Stamps
A method
const PropertyB = compose({
properties: {
B: 42
}
});
PropertyB().B; // 42
Stamps
A property
const UsefulThing = compose({
staticProperties: {
usefulThing() {
// ...
}
}
);
UsefulThing.usefulThing();
Stamps
A static property (method)
const Stamp1 = compose(
Ctor, MethodX, PropertyB, UsefulThing);
Stamps
Composing stamps
const myObject = Stamp1();
myObject.X();
myObject.B; // 42
Stamp1.usefulThing();
Stamps
Using the Stamp1
ctor()
method
X()
property B
useful
thing
const Stamp1 = compose(
Ctor, MethodX, PropertyB, UsefulThing);
import compose from 'stamp-implementation';
const Ctor = compose({
initializers: [ function () { /* … */ } ]
});
const MethodX = compose({
methods: { X() { /* … */ } }
});
const PropertyB = compose({
properties: { B: 42 }
});
const UsefulThing = compose({
staticProperties: { usefulThing() { /* … */ } }
});
const Stamp1 = compose(Ctor, MethodX, PropertyB, UsefulThing);
const myObject = Stamp1();
myObject.X();
myObject.B; // 42
Stamp1.usefulThing();
Stamps
ctor()
method
X()
property B
useful
thing
const Stamp1 = compose({
initializers: [() => {
// ...
}],
methods: {
X() {
// ...
}
},
properties: {
B: 42
},
staticProperties: {
usefulThing() {
// ...
}
}
});
const myObject = Stamp1();
myObject.X();
myObject.B; // 42
Stamp1.usefulThing();
Same but as a single stamp
stamp.compose() method
const Stamp1 = compose(Ctor, MethodX, PropertyB, UsefulThing);
Every compose call:
• creates a new stamp
• merges the metadata of the provided stamps
etc
const Stamp1 = Ctor.compose(MethodX, PropertyB, UsefulShit);
const Stamp1 = Ctor.compose(MethodX).compose(PropertyB).compose(UsefulThing);
const Stamp1 = Ctor.compose(MethodX.compose(PropertyB.compose(UsefulThing)));
const Stamp1 = compose(Ctor, MethodX).compose(PropertyB, UsefulThing);
Similar to Promises .then() Stamps have .compose()
Collected Stamp1 metadata
const Stamp1 = compose(Ctor, MethodX, PropertyB, UsefulThing);
console.log(Stamp1.compose);
{ [Function]
initializers: [ [Function] ],
methods: { X: [Function: X] },
properties: { B: 42 },
staticProperties: { usefulThing: [Function: usefulThing] } }
Stamp1.compose has:
property “initializers”
property “methods”
property “properties”
property “staticProperties”
ctor()
method
X()
property B
useful
thing
Now let’s take a classic Java example and
convert it to stamps.
The purpose of the example is not to solve a problem
but to show an idea behind the stamps.
@TesterInfo(
priority = Priority.HIGH,
createdBy = "Zavulon",
tags = {"sales","test"}
)
public class TestExample extends BaseTest {
TestExample(TestRunner r) {
this.runner = r;
}
@Test
void testA() {
// ...
}
@Test(enabled = false)
void testB() {
// …
}
}
Example: Java metadata and class configuration
Configuring class
metadata in Java
(annotations)
Configuring
object instance in
Java
(dependency injection)
Configuring class
members in Java
(interface implementation)
• class extension/inheritance
• Java annotations
• interface implementations
• dependency injection pattern
• has-a composition pattern
• is-a composition pattern
• proxy design pattern
• wrapper design pattern
• decorator design pattern
• …
How to setup a class behavior in Java?
This is nothing else, but
a process of configuring your class,
a process of collecting metadata
How to setup a stamp behavior?
• compose
• compose
• compose
• compose
• compose
• compose
• compose
• compose
• compose
• compose
stamp
const BaseTest = compose({
staticProperties: {
suite(info) {
return this.compose({
deepConfiguration: info
});
},
test(options, func) {
return this.compose({
methods: {
[func.name]: func
},
configuration: {
[func.name]: options
}
});
}
}
});
TesterStamp.suite()
Example: a stamp with two static methods
Example: compare Java and stamp
@TesterInfo(
priority = Priority.HIGH,
createdBy = "Zavulon",
tags = {"sales","test" }
)
public class TestExample
extends BaseTest {
TestExample(TestRunner r) {
this.runner = r;
}
@Test
void testA() {
// ...
}
@Test(enabled = false)
void testB() {
// …
}
}
const TestExample = BaseTest
.suite({
priority: Priority.HIGH,
createdBy: 'Zavulon',
tags: ['sales', 'test']
})
.compose({properties: {runner}})
.test(null,
function testA() {
// ...
}
)
.test({enabled: false},
function testB() {
// ...
}
);
const BaseTest = compose({
staticProperties: {
suite(info) {
return this.compose({
deepConfiguration: info
});
},
test(options, func) {
return this.compose({
methods: {
[func.name]: func
},
configuration: {
[func.name]: options
}
});
}
}
});
TesterStamp.suite().compose().test().test();
Example: a stamp with two static methods
Let’s see what the resulting stamp metadata looks like
{
deepConfiguration: {
priority: 'HIGH',
createdBy: 'Zavulon',
tags: ['sales', 'test']
},
properties: {
runner: ...
},
configuration: {
testA: {},
testB: {enabled: false},
testC: {enabled: true}
},
methods: {
testA() {},
testB() {},
testC() {}
}
}
TestExample.compose
Stamp’s metadata in specification
* methods - instance methods (prototype)
* properties - instance properties
* deepProperties - deeply merged instance prop
* propertyDescriptors - JavaScript standard pr
* staticProperties - stamp properties
* staticDeepProperties - deeply merged stamp p
* staticPropertyDescriptors - JavaScript stand
* initializers - list of initializers
* configuration - arbitrary data
* deepConfiguration - deeply merged arbitrary
The “magical” metadata merging algorithm
const dstMetadata = {};
mergeMetadata(dstMetadata, srcMetadata);
/**
* Combine two stamp metadata objects. Mutates `dst` object.
*/
function mergeMetadata(dst, src) {
_.assign(dst.methods, src.methods);
_.assign(dst.properties, src.properties);
_.assign(dst.propertyDescriptors, src.propertyDescriptors);
_.assign(dst.staticProperties, src.staticProperties);
_.assign(dst.staticPropertyDescriptors, src.staticPropertyDescriptors);
_.assign(dst.configuration, src.configuration);
_.merge(dst.deepProperties, src.deepProperties);
_.merge(dst.staticDeepProperties, src.staticDeepProperties);
_.merge(dst.deepConfiguration, src.deepConfiguration);
dst.initializers = dst.initializers.concat(src.initializers);
Awesome features you didn’t notice
The .compose() method is detachable
import {ThirdPartyStamp} from 'third-party-stuff';
I wish the .then() method of Promises was
as easy detachable as the .compose() method.
Like that:
const Promise = thirdPartyPromise.then;
Detaching the .compose() method
And reusing it as a compose() function to create new stamps
const compose = ThirdPartyStamp.compose;
const Stamp1 = compose({
properties: {
message: "Look Ma! I'm creating stamps without importing an imple
}
});
You can override the .compose()
import compose from 'stamp-specification';
function infectedCompose(...args) {
console.log('composing the following: ', args);
args.push({staticProperties: {compose: infectedCompose}});
return compose.apply(this, args);
}
const Ctor = infectedCompose({
initializers: [function () { /* ... */ }]
});
const MethodX = infectedCompose({
methods: { X() { /* ... */ } }
});
const PropertyB = infectedCompose({
properties: { B: 42 }
});
const UsefulThing = infectedCompose({
staticProperties: { usefulThing() { /* ... */ } }
});
const Stamp1 = infectedCompose(Ctor, MethodX)
.compose(PropertyB.compose(UsefulThing));
console.log gets
executed 7 times
{
You can create APIs like that
const MyUser = compose({
initializers: [function ({password}) {
this.password = password;
console.log(this.password.length);
}]
});
// Cannot read property 'password' of undefined
MyUser();
// Cannot read property 'length' of null
MyUser({password: null});
import MyUser from './my-user';
import ArgumentChecker from './argument-checker';
const MySafeUser = ArgumentChecker.checkArguments({
password: 'string'
})
.compose(MyUser);
// throws "Argument 'password' must be a string"
MySafeUser();
// throws "Argument 'password' must be a string"
MySafeUser({password: null});
You can create APIs like that
You can create APIs like that
1 import compose from 'stamp-specification';
2
3 const MyUser = compose({
4 initializers: [function ({password}) {
5 this.password = password;
6 console.log(this.password.length);
7 }]
8 });
9
10 // Cannot read property 'password' of undefined
11 MyUser();
12
13 // Cannot read property 'length' of null
14 MyUser({password: null});
15
16
17 const MySafeUser = ArgumentChecker.checkArguments({
18 password: 'string'
19 })
20 .compose(MyUser);
21
22 // Argument 'password' must be a string
23 MySafeUser();
const ArgumentChecker = compose({
staticProperties: {
checkArguments(keyValueMap) {
// deep merge all the pairs
// to the ArgumentChecker object
return this.compose({deepConfiguration: {
ArgumentChecker: keyValueMap
}});
}
},
...
ArgumentChecker stamp
...
initializers: [(options = {}, {stamp}) => {
// take the map of key-value pairs
// and iterate over it
const map = stamp.compose.deepConfiguration.ArgumentChec
for (const [argName, type] of map) {
if (typeof options[argName] !== type)
throw new Error(
`Argument "${argName}" must be a ${type}`);
}
}]
});
const ArgumentChecker = compose({
staticProperties: {
checkArguments(keyValueMap) {
// deep merge all the pairs to the ArgumentChecker object
return this.compose({deepConfiguration: {
ArgumentChecker: keyValueMap
}});
}
},
initializers: [function (options = {}, {stamp}) {
// take the map of key-value pairs and iterate over it
const map = stamp.compose.deepConfiguration.ArgumentChecker;
for (const [argName, type] of map) {
if (typeof options[argName] !== type)
throw new Error(
`Argument "${argName}" must be a ${type}`);
}
}]
});
ArgumentChecker stamp
ArgumentChecker stamp using stampit module
const ArgumentChecker = stampit() // <- creating a new empty stamp
.statics({
checkArguments(keyValueMap) {
return this.deepConf({ArgumentChecker: keyValueMap});
}
})
.init(function (options = {}, {stamp}) {
const map = stamp.compose.deepConfiguration.ArgumentChecker;
for (const [argName, type] of map) {
if (typeof options[argName] !== type)
throw new Error(`"${argName}" is missing`);
}
});
• Instead of classes obviously
• When you have many similar but different
models:
• games
(craft wooden old unique improved dwarf
sword)
• subscription types
(Free - Pro - Enterprise, yearly - monthly,
direct debit - invoice, credit card - bank
account, etc.)
• … your case
• As a dependency injection for complex
business logic
When to use Stamps
• When you need to squeeze every CPU cycle from yo
• games (LOL!)
• drivers
• In small utility modules (like left-pad)
When NOT to use Stamps
medium.com/@koresar
So, if you are building a new language, please,
omit classes.
Consider stamps instead
(or a similar composable behaviours)
Specs: https://github.com/stampit-org/stamp-specification
Development: https://github.com/stampit-org
stampit_jsNews:
kore_sar(C) Vasyl Boroviak
Chat: https://gitter.im/stampit-org/stampit

Stamps - a better way to object composition

  • 1.
  • 2.
  • 3.
    ctor() method X() property A ctor() method X() property ApropertyB ctor() property A property B method Y() method Z() method X() ctor() method X() property A property Bmethod Y() method Z() … ctor() method X() property A property B method Y() method Z() property C ctor(override) pony Zuckerberg yolo the god the true god the new true god useless shit useful thing() leprosarium matrix new useless shit hell on Earth pain suffer Typical class inheritance story But actually I needed only these: ctor() property B method X() useful thing()
  • 4.
    Typical class inheritancestory • 1 constructor • 211 methods • 130 properties • 55 events (which essentially are properties too) • 1 static field • 1 attribute
  • 5.
    Rethinking inheritance { ctor() method X() propertyB useful thing() } … ctor() method X() property B useful thing pain suffer hell on Earth useless shit property A method Y() method Z() Zuckerber g property C Java/C# developers are like
  • 6.
    To make thathappen we invented stamps
  • 7.
    Stamps … ctor() method X() property B useful thing pain suffer hell on Earth useless shit propertyA method Y() method Z() Zuckerber g property C • Stamps are composable behaviours • Stamps are not a library or a module • Stamps are a specification (like Promises) • Various stamp implementations are compatible with each other • Stamps are factory functions (like classes)
  • 8.
    import compose from'stamp-implementation'; // not a real module Stamps The only thing in the specification
  • 9.
    const Ctor =compose({ initializers: [function (...args) { console.log('Hello Node Ninjas') }] }); Ctor(...args); // “Hello Node Ninjas” Stamps Constructor aka initializer
  • 10.
    const MethodX =compose({ methods: { X() { // ... } } }); MethodX().X(); Stamps A method
  • 11.
    const PropertyB =compose({ properties: { B: 42 } }); PropertyB().B; // 42 Stamps A property
  • 12.
    const UsefulThing =compose({ staticProperties: { usefulThing() { // ... } } ); UsefulThing.usefulThing(); Stamps A static property (method)
  • 13.
    const Stamp1 =compose( Ctor, MethodX, PropertyB, UsefulThing); Stamps Composing stamps
  • 14.
    const myObject =Stamp1(); myObject.X(); myObject.B; // 42 Stamp1.usefulThing(); Stamps Using the Stamp1 ctor() method X() property B useful thing const Stamp1 = compose( Ctor, MethodX, PropertyB, UsefulThing);
  • 15.
    import compose from'stamp-implementation'; const Ctor = compose({ initializers: [ function () { /* … */ } ] }); const MethodX = compose({ methods: { X() { /* … */ } } }); const PropertyB = compose({ properties: { B: 42 } }); const UsefulThing = compose({ staticProperties: { usefulThing() { /* … */ } } }); const Stamp1 = compose(Ctor, MethodX, PropertyB, UsefulThing); const myObject = Stamp1(); myObject.X(); myObject.B; // 42 Stamp1.usefulThing(); Stamps ctor() method X() property B useful thing
  • 16.
    const Stamp1 =compose({ initializers: [() => { // ... }], methods: { X() { // ... } }, properties: { B: 42 }, staticProperties: { usefulThing() { // ... } } }); const myObject = Stamp1(); myObject.X(); myObject.B; // 42 Stamp1.usefulThing(); Same but as a single stamp
  • 17.
    stamp.compose() method const Stamp1= compose(Ctor, MethodX, PropertyB, UsefulThing); Every compose call: • creates a new stamp • merges the metadata of the provided stamps etc const Stamp1 = Ctor.compose(MethodX, PropertyB, UsefulShit); const Stamp1 = Ctor.compose(MethodX).compose(PropertyB).compose(UsefulThing); const Stamp1 = Ctor.compose(MethodX.compose(PropertyB.compose(UsefulThing))); const Stamp1 = compose(Ctor, MethodX).compose(PropertyB, UsefulThing); Similar to Promises .then() Stamps have .compose()
  • 18.
    Collected Stamp1 metadata constStamp1 = compose(Ctor, MethodX, PropertyB, UsefulThing); console.log(Stamp1.compose); { [Function] initializers: [ [Function] ], methods: { X: [Function: X] }, properties: { B: 42 }, staticProperties: { usefulThing: [Function: usefulThing] } } Stamp1.compose has: property “initializers” property “methods” property “properties” property “staticProperties” ctor() method X() property B useful thing
  • 19.
    Now let’s takea classic Java example and convert it to stamps. The purpose of the example is not to solve a problem but to show an idea behind the stamps.
  • 20.
    @TesterInfo( priority = Priority.HIGH, createdBy= "Zavulon", tags = {"sales","test"} ) public class TestExample extends BaseTest { TestExample(TestRunner r) { this.runner = r; } @Test void testA() { // ... } @Test(enabled = false) void testB() { // … } } Example: Java metadata and class configuration Configuring class metadata in Java (annotations) Configuring object instance in Java (dependency injection) Configuring class members in Java (interface implementation)
  • 21.
    • class extension/inheritance •Java annotations • interface implementations • dependency injection pattern • has-a composition pattern • is-a composition pattern • proxy design pattern • wrapper design pattern • decorator design pattern • … How to setup a class behavior in Java? This is nothing else, but a process of configuring your class, a process of collecting metadata How to setup a stamp behavior? • compose • compose • compose • compose • compose • compose • compose • compose • compose • compose stamp
  • 23.
    const BaseTest =compose({ staticProperties: { suite(info) { return this.compose({ deepConfiguration: info }); }, test(options, func) { return this.compose({ methods: { [func.name]: func }, configuration: { [func.name]: options } }); } } }); TesterStamp.suite() Example: a stamp with two static methods
  • 24.
    Example: compare Javaand stamp @TesterInfo( priority = Priority.HIGH, createdBy = "Zavulon", tags = {"sales","test" } ) public class TestExample extends BaseTest { TestExample(TestRunner r) { this.runner = r; } @Test void testA() { // ... } @Test(enabled = false) void testB() { // … } } const TestExample = BaseTest .suite({ priority: Priority.HIGH, createdBy: 'Zavulon', tags: ['sales', 'test'] }) .compose({properties: {runner}}) .test(null, function testA() { // ... } ) .test({enabled: false}, function testB() { // ... } );
  • 25.
    const BaseTest =compose({ staticProperties: { suite(info) { return this.compose({ deepConfiguration: info }); }, test(options, func) { return this.compose({ methods: { [func.name]: func }, configuration: { [func.name]: options } }); } } }); TesterStamp.suite().compose().test().test(); Example: a stamp with two static methods
  • 26.
    Let’s see whatthe resulting stamp metadata looks like { deepConfiguration: { priority: 'HIGH', createdBy: 'Zavulon', tags: ['sales', 'test'] }, properties: { runner: ... }, configuration: { testA: {}, testB: {enabled: false}, testC: {enabled: true} }, methods: { testA() {}, testB() {}, testC() {} } } TestExample.compose
  • 27.
    Stamp’s metadata inspecification * methods - instance methods (prototype) * properties - instance properties * deepProperties - deeply merged instance prop * propertyDescriptors - JavaScript standard pr * staticProperties - stamp properties * staticDeepProperties - deeply merged stamp p * staticPropertyDescriptors - JavaScript stand * initializers - list of initializers * configuration - arbitrary data * deepConfiguration - deeply merged arbitrary
  • 28.
    The “magical” metadatamerging algorithm const dstMetadata = {}; mergeMetadata(dstMetadata, srcMetadata); /** * Combine two stamp metadata objects. Mutates `dst` object. */ function mergeMetadata(dst, src) { _.assign(dst.methods, src.methods); _.assign(dst.properties, src.properties); _.assign(dst.propertyDescriptors, src.propertyDescriptors); _.assign(dst.staticProperties, src.staticProperties); _.assign(dst.staticPropertyDescriptors, src.staticPropertyDescriptors); _.assign(dst.configuration, src.configuration); _.merge(dst.deepProperties, src.deepProperties); _.merge(dst.staticDeepProperties, src.staticDeepProperties); _.merge(dst.deepConfiguration, src.deepConfiguration); dst.initializers = dst.initializers.concat(src.initializers);
  • 29.
    Awesome features youdidn’t notice
  • 30.
    The .compose() methodis detachable import {ThirdPartyStamp} from 'third-party-stuff'; I wish the .then() method of Promises was as easy detachable as the .compose() method. Like that: const Promise = thirdPartyPromise.then; Detaching the .compose() method And reusing it as a compose() function to create new stamps const compose = ThirdPartyStamp.compose; const Stamp1 = compose({ properties: { message: "Look Ma! I'm creating stamps without importing an imple } });
  • 31.
    You can overridethe .compose() import compose from 'stamp-specification'; function infectedCompose(...args) { console.log('composing the following: ', args); args.push({staticProperties: {compose: infectedCompose}}); return compose.apply(this, args); } const Ctor = infectedCompose({ initializers: [function () { /* ... */ }] }); const MethodX = infectedCompose({ methods: { X() { /* ... */ } } }); const PropertyB = infectedCompose({ properties: { B: 42 } }); const UsefulThing = infectedCompose({ staticProperties: { usefulThing() { /* ... */ } } }); const Stamp1 = infectedCompose(Ctor, MethodX) .compose(PropertyB.compose(UsefulThing)); console.log gets executed 7 times {
  • 32.
    You can createAPIs like that const MyUser = compose({ initializers: [function ({password}) { this.password = password; console.log(this.password.length); }] }); // Cannot read property 'password' of undefined MyUser(); // Cannot read property 'length' of null MyUser({password: null});
  • 33.
    import MyUser from'./my-user'; import ArgumentChecker from './argument-checker'; const MySafeUser = ArgumentChecker.checkArguments({ password: 'string' }) .compose(MyUser); // throws "Argument 'password' must be a string" MySafeUser(); // throws "Argument 'password' must be a string" MySafeUser({password: null}); You can create APIs like that
  • 34.
    You can createAPIs like that 1 import compose from 'stamp-specification'; 2 3 const MyUser = compose({ 4 initializers: [function ({password}) { 5 this.password = password; 6 console.log(this.password.length); 7 }] 8 }); 9 10 // Cannot read property 'password' of undefined 11 MyUser(); 12 13 // Cannot read property 'length' of null 14 MyUser({password: null}); 15 16 17 const MySafeUser = ArgumentChecker.checkArguments({ 18 password: 'string' 19 }) 20 .compose(MyUser); 21 22 // Argument 'password' must be a string 23 MySafeUser();
  • 35.
    const ArgumentChecker =compose({ staticProperties: { checkArguments(keyValueMap) { // deep merge all the pairs // to the ArgumentChecker object return this.compose({deepConfiguration: { ArgumentChecker: keyValueMap }}); } }, ... ArgumentChecker stamp
  • 36.
    ... initializers: [(options ={}, {stamp}) => { // take the map of key-value pairs // and iterate over it const map = stamp.compose.deepConfiguration.ArgumentChec for (const [argName, type] of map) { if (typeof options[argName] !== type) throw new Error( `Argument "${argName}" must be a ${type}`); } }] });
  • 37.
    const ArgumentChecker =compose({ staticProperties: { checkArguments(keyValueMap) { // deep merge all the pairs to the ArgumentChecker object return this.compose({deepConfiguration: { ArgumentChecker: keyValueMap }}); } }, initializers: [function (options = {}, {stamp}) { // take the map of key-value pairs and iterate over it const map = stamp.compose.deepConfiguration.ArgumentChecker; for (const [argName, type] of map) { if (typeof options[argName] !== type) throw new Error( `Argument "${argName}" must be a ${type}`); } }] }); ArgumentChecker stamp
  • 38.
    ArgumentChecker stamp usingstampit module const ArgumentChecker = stampit() // <- creating a new empty stamp .statics({ checkArguments(keyValueMap) { return this.deepConf({ArgumentChecker: keyValueMap}); } }) .init(function (options = {}, {stamp}) { const map = stamp.compose.deepConfiguration.ArgumentChecker; for (const [argName, type] of map) { if (typeof options[argName] !== type) throw new Error(`"${argName}" is missing`); } });
  • 39.
    • Instead ofclasses obviously • When you have many similar but different models: • games (craft wooden old unique improved dwarf sword) • subscription types (Free - Pro - Enterprise, yearly - monthly, direct debit - invoice, credit card - bank account, etc.) • … your case • As a dependency injection for complex business logic When to use Stamps
  • 40.
    • When youneed to squeeze every CPU cycle from yo • games (LOL!) • drivers • In small utility modules (like left-pad) When NOT to use Stamps
  • 41.
  • 42.
    So, if youare building a new language, please, omit classes. Consider stamps instead (or a similar composable behaviours)
  • 43.

Editor's Notes

  • #3 This talk is highly complex. It implies that the listener is an experienced developer, or at least have extensive experience with classic OOP and classes. Apologies if this is too much. Ask me.
  • #4 ctor -> constructor
  • #5 The simplest Window UI element in .NET Framework is System.Windows.Controls.Label. According to MSDN its class in .NET v4.0 has:
  • #9 We are keeping the specification as small as possible
  • #13 Clarify statics
  • #14 I need to say that `compose` can accept both POJO and other stamps in no particular order.
  • #18 Stamps are immutable
  • #21 This is a typical Java unit test implementation of some unit tests.
  • #22 …When this though came to my mind the first time I was like:
  • #25 Suite is using this.compose() internally.
  • #32 explain the two functions on the bottom will not log if I won’t override the statics
  • #38 Kind of too much for such a simple feature. There is a shorter syntax using the stampit module.