Skip to content

Commit

Permalink
feat(lang): include callable class object
Browse files Browse the repository at this point in the history
includes a class object that produces objects that can be called to
execute custom functions without additional API methods
  • Loading branch information
esatterwhite committed Aug 1, 2022
1 parent 232c21e commit b0e8d00
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 0 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ in a clean and consitent fashion. Nothing more, and nothing less.
* [`slugify`(text: String [, separator: String = '-']): String](#slugifytext-string--separator-string----string)
* [`typecast`(text: String): Object](#typecasttext-string-object)
* [`typeOf`(element: `any`): String](#typeofelement-any-string)
* [`Callable`: Class](#callable-class)
* [Authors](#authors)
* [Contributors ✨](#contributors-)

Expand Down Expand Up @@ -376,6 +377,32 @@ typeOf(() => {}) // function
typeOf(new Set()) // set
```

### `Callable`: [Class][]

A class object whose instances are derived from [Function][] and can be called.
When exteded, a [Symbol][] function defined by `Symbol.for('call')` will be executed
with any arguments that were passed

##### Example

```javascript
const {Callable} = require('@logdna/stdlib')
const __call__ = Symbol.for('call')
class Hello extends Callable {
constructor(opts) {
this.yell = !!opts.yell
}
[__call__](name) {
const output = `Hello, ${name}`
console.log(this.yell ? `${output.toUpperCase()}!` : output)
}
}

const screamAt = new Hello({yell: true})

screamAt('bill') // HELLO, BILL!
```

## Authors

* [**Eric Satterwhite**](mailto:eric.satterwhite@logdna.com) <eric.satterwhite@logdna.com>
Expand All @@ -388,6 +415,8 @@ typeOf(new Set()) // set
[Number]: https://mdn.io/number
[Object]: https://mdn.io/object
[Function]: https://mdn.io/function
[Class]: https://mdn.io/class
[Symbol]: https://mdn.io/symbol
[Generator]: https://mdn.io/generator
[itertools]: https://docs.python.org/3.7/library/itertools.html

Expand Down
42 changes: 42 additions & 0 deletions lib/callable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use strict'
/**
* @module lib/callable
* @author Eric Satterwhite
**/

/**
* A class object whose instances are also callable
* To define a callable object, extend this class and gif it a symbol function
* using Symbol.for("call")
* @constructor
* @alias module:lib/callable
* @extends Function
* @example
* class Message extends Callable {
* constructor(name) {
* super()
* this.name = name
* }
*
* [__call__]() {
* return `Hello ${this.name}`
* }
* }
*
* const bob = new Message('bob')
* bob.name // bob
* bob() // Hello bob
**/
class Callable extends Function {
get [Symbol.toStringTag]() {
return 'Callable'
}

constructor() {
super('...args', 'return this._bound[Symbol.for("call")](...args)')
this._bound = this.bind(this)
return this._bound
}
}

module.exports = Callable
23 changes: 23 additions & 0 deletions test/unit/callable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use strict'

const {test, threw} = require('tap')
const typeOf = require('../../lib/type-of.js')
const Callable = require('../../lib/callable.js')

test('callable', async (t) => {
t.test('string representation', async (t) => {
t.equal(typeOf(new Callable()), 'callable', 'identifies as callable')
})

t.test('extensbility', async (t) => {
const __call__ = Symbol.for('call')
class Sum extends Callable {
[__call__](a, b = 1) {
return a + b
}
}

const sum = new Sum()
t.equal(sum(10, 3), 13, 'call function executed')
})
}).catch(threw)

0 comments on commit b0e8d00

Please sign in to comment.