Fun with Stamps. Episode 1. Stamp basics
Hello. I’m developer Vasyl Boroviak and welcome to the first episode of Vasyl Boroviak presents Fun with Stamps.
Other articles of the series
- Episode 1. Stamp basics (this article)
- Episode 2. Dependency injection in FP
- Episode 3. Comparing with the ES2015 classes
- Episode 4. Implementing stamps yourself in 30 LOC
- Episode 5. Composition design pattern
- Episode 6. Statics — properties on stamps
- Episode 7. Early and late dependency injection
- Episode 8. Tracking and overriding composition
- Episode 9. Detaching compose()
- Episode 10. My stamp mental model
- Episode 11. Interfering composition
- Episode 12. New @stamp home
- Episode 13. Method collision control
- Episode 14. New @stamp/it as a replacement of Stampit
- Episode 15. The @stamp/ modules ecosystem
- Episode 16. TypeScript mix-in classes vs Stamps
- Episode 17. Easy 100% unit test coverage in JS
- Episode 18. Dependency injection paradise
- Episode 19. Java/C# abstract methods in JavaScript
- Episode 20. Stampit v4
- Episode 21. Private data in JavaScript. 4 ways using stamps
- Episode 22. JavaScript instanceof as composable stamp
- Episode 23. New stampit.js.org with all the docs
- Episode 24. New “name” feature
- Season 2. Best practices.
What are Stamps?
Stamps are an evolution in software development. After a decade of doing classic OOP and a couple of attempts to dive into functional programming (FP) I fell in love with stamps.
Many people feel that classic OOP is just bad. Some feel that FP is also not an answer.
Stamps are a new programming paradigm intended to be a better alternative to classic OOP and an addition to FP. Stamps are not limited to JavaScript, although only the JavaScript implementation exist to date.
Many have been amazed after embracing the concept. Here is what they say:
thanks for a library that makes javascript really fun :)
i’ve been using stamps for an internal project and it has made it easier to write reusable, expressive code
and it’s fun to use
Or:
every time i talk about stamps i get all excited and my colleagues have to settle me down
And one more quote from here:
Stamps are like mixins on steroids. They offer a great declarative API to create and compose your factories of objects (stamps) with baked in support for composing prototypes, mixing in features, deep copying composition and private variables.
All that is powered by these 100 lines of code.
So, what is it?
- Stamps are composable (mixable?, combinable?) factory functions
- Stamps are used as a replacement for classes
- Stamps are an addition to functional programming
- Stamps are a different software development paradigm
First example
Stamps are created using the special compose function or various utilities.
const EmptyStamp = compose(); // creating an empty stamp
const obj = EmptyStamp(); // it can create empty objects!
console.log(obj); // {}
Let’s make our stamp produce objects with the property foo
.
const HasFoo = compose({
properties: {
foo: 'default foo!'
}
});
const obj = HasFoo();
console.log(obj); // { foo: 'default foo!' }
Now, let’s create a stamp which will produce objects with the method printFoo
.
const PrintFoo = compose({
methods: {
printFoo() {
console.log(this.foo || 'There is no foo');
}
}
});
const obj = PrintFoo();
obj.printFoo(); // There is no foo
Boring, eh? But let’s compose
those two:
const Foo = compose(HasFoo, PrintFoo);
const obj = Foo();
obj.printFoo(); // default foo!
Alternatively you can compose them like this:
const Foo = HasFoo.compose(PrintFoo);
// or
const Foo = PrintFoo.compose(HasFoo);
// or
const Foo = compose().compose(HasFoo).compose(PrintFoo);
// or
const Foo = compose(HasFoo).compose(PrintFoo);
// or
// ...well you get it :)
Factory arguments
We all want to pass arguments to our factories. Also, how do I insert my logic when an object is being created (aka constructor in classic OOP)? Here is how you do it:
const InitFoo = compose({
initializers: [function ({foo}) {
if (foo) this.foo = foo;
}]
});
const obj = InitFoo({foo: 'incoming foo!'});
console.log(obj); // { foo: 'incoming foo!' }
A bit long. But a shorter syntax is possible by using latest stampit or stampit v3.
import {init} from 'stampit';const InitFoo = init(function({foo}) {
if (foo) this.foo = foo;
});
Of course you can compose that stamp too:
const Foo = compose(HasFoo, PrintFoo, InitFoo);
const obj = Foo({foo: 'incoming foo!'});
obj.printFoo(); // incoming foo!
Stamps can have as many initializers as you want. Each initializer will be executed and will receive the same set of arguments.
Statics — properties on stamp
You can declare the so called “static” properties. Those are attached to the stamp itself. The stamp below declares a single static property trace
, which is a function:
const TraceStampMetaData = compose({
staticProperties: {
trace() {
console.log(`This stamp consists of ${this.compose}`);
}
}
});
And, of course, you can compose that stamp with other stamps. All derived stamps will have that property too:
const Foo2 = Foo.compose(TraceStampMetaData);
Foo2.trace(); // will print the Foo2 stamp meta-data. See below!
Breaking down a stamp into pieces
Each stamp has the .compose()
method. It also serves as the meta-data holder. Let’s print the Foo
stamp from above.
const Foo = compose(HasFoo, PrintFoo, InitFoo);
console.log(Foo);
Will print this:
{ [Function: Stamp]
compose:
{ [Function]
methods: { printFoo: [Function: printFoo] },
properties: { foo: 'default foo!' },
initializers: [ [Function] ] } }
Foo
is the function which has the property .compose
which is also a function and has properties .methods
, .properties
, and .initializers
.
You, the developers, are allowed and even encouraged to manipulate that meta-data! The data is called the “stamp descriptor” or just “descriptor”.
Objects created by stamps
Let’s take a look at the objects created by this stamp:
const obj = Foo();
console.log(obj); // { foo: 'default foo!' }
It is very similar to the “properties:“ line printed above. Right?
Let’s look at the prototype of the object:
console.log(Object.getPrototypeOf(obj));
// { printFoo: [Function: printFoo] }
Woah! That’s very similar to the “methods:”. Indeed, it is the “.methods” object itself:
console.log(Object.getPrototypeOf(obj) === Foo.compose.methods);
// true
Similarities with Promises (aka Thenables)
- Thenable ~ Composable
- .then() ~ .compose()
- Promise ~ Stamp
- new Promise(function(resolve, reject)) ~ compose(…composables)
When to use stamps?
- When you need dependency injection in both OOP and FP (the Episode number 2)
- When you need multiple similar behavior objects (article by Jaime González García)
- Instead of classes (the Episode 3)
- And many other applications, like UI components, etc.
Conclusion
You have just read everything you need to know to start using stamps. Although, this is only the tip of the iceberg.
The rest of the episodes:
- Episode 1. Stamp basics (this article)
- Episode 2. Dependency injection in FP
- Episode 3. Comparing with the ES2015 classes
- Episode 4. Implementing stamps yourself in 30 LOC
- Episode 5. Composition design pattern
- Episode 6. Statics — properties on stamps
- Episode 7. Early and late dependency injection
- Episode 8. Tracking and overriding composition
- Episode 9. Detaching compose()
- Episode 10. My stamp mental model
- Episode 11. Interfering composition
- Episode 12. New @stamp home
- Episode 13. Method collision control
- Episode 14. New @stamp/it as a replacement of Stampit
- Episode 15. The @stamp/ modules ecosystem
- Episode 16. TypeScript mix-in classes vs Stamps
- Episode 17. Easy 100% unit test coverage in JS
- Episode 18. Dependency injection paradise
- Episode 19. Java/C# abstract methods in JavaScript
- Episode 20. Stampit v4
- Episode 21. Private data in JavaScript. 4 ways using stamps
- Episode 22. JavaScript instanceof as composable stamp
- Episode 23. New stampit.js.org with all the docs
- Episode 24. New “name” feature
- Season 2. Best practices.