npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

it.js

v0.7.0

Published

Object-oriented functional combinators

Downloads

16

Readme

It.js

It.js is a library to make it easier to create accessor/iterator functions, for use with things like _.map, _.filter, _.sortBy, _.each, and so on... It complements nicely with Underscore.js.

This library is inspired by the article Combinator Recipes for Working With Objects in JavaScript by Reginald Braithwaite, but I want it to look more fluent and chainable.

tldr

In short, this library allows you to write this:

It.send('toLowerCase')

instead of this:

function(name) { return name.toLowerCase() }

And this:

It.instantiate(Status)

instead of this:

function(json) { return new Status(json) }

They are chainable, so you can also write things like this:

It.get('first').get('length')

instead of this:

function(person) { return person.first.length }

example.js

var _ = require('underscore')
var It = require('./')
var numbers = [3, 1, 4, 1, 5]
var strings = ['this', 'is', 'a', 'Book']

It

It provides an identity function, just like _.identity, but much shorter.

This maps an array with itself... Pretty useless

console.log(_.map(numbers, It))
[ 3, 1, 4, 1, 5 ]

This gets a sorted copy of an array. We just sort by itself! (This is nice since underscore has only _.sortBy, but not _.sort)

console.log(_.sortBy(numbers, It))
console.log(_.sortBy(strings, It))
[ 1, 1, 3, 4, 5 ]
[ 'Book', 'a', 'is', 'this' ]

.get

.get returns the value of a property. Here's where things get interesting...

Let's create a function that returns the length of a string (or an array, or whatever object that has .length property).

// equivalent to function(x) { return x.length }
var getLength = It.get('length')

We can use it to sort the strings by their length.

console.log(_.sortBy(strings, getLength))
[ 'a', 'is', 'this', 'Book' ]

.send

Use .send(...) to call a method on an object.

// equivalent to function(x) { return x.toUpperCase() }
var upcase = It.send('toUpperCase')

With this, we can map all these strings to uppercase:

console.log(_.map(strings, upcase))
[ 'THIS', 'IS', 'A', 'BOOK' ]

And with this, case-insensitive sorting is easy:

console.log(_.sortBy(strings, upcase))
[ 'a', 'Book', 'is', 'this' ]

Chaining

Of course, all of these are chainable.

// equivalent to function(x) { return x.substr(0, 1).toUpperCase() }
var firstCharacterCapitalized = It.send('substr', 0, 1).send('toUpperCase')

Get the first character of each string, capitalized.

console.log(_.map(strings, firstCharacterCapitalized))
[ 'T', 'I', 'A', 'B' ]

Now, let's move on and try to use chaining to do something more practical...

Here we have a list of people. (name generation thanks to chance.js)

var addressBook = [
  { first: 'Sifwa', last: 'Duhav', phone: '(416) 984-4454' },
  { first: 'Moc', phone: '(898) 983-5755' },
  { first: 'Diblacbo', last: 'Li', phone: '(258) 838-8314' },
  { first: 'Betu', last: 'Jol', phone: '(219) 234-9591' },
  { first: 'Fuhetu', last: 'Ra', phone: '(631) 437-2332' }
]

Let's sort them by the length of first name!

// equivalent to function(x) { return x.first.length }
var firstNameLength = It.get('first').get('length')
console.log(_.sortBy(addressBook, firstNameLength))
[ { first: 'Moc', phone: '(898) 983-5755' },
  { first: 'Betu', last: 'Jol', phone: '(219) 234-9591' },
  { first: 'Sifwa', last: 'Duhav', phone: '(416) 984-4454' },
  { first: 'Fuhetu', last: 'Ra', phone: '(631) 437-2332' },
  { first: 'Diblacbo', last: 'Li', phone: '(258) 838-8314' } ]

.set

.set(property, value) sets a property on an object. The result of this operation will be the invoked object, so you can chain more operations (something like .set('a','b').set('c','d')).

Let's set everyone's score to zero! Yes, scores in an address book!

_.each(addressBook, It.set('score', 0))
console.log(addressBook)
[ { first: 'Sifwa',
    last: 'Duhav',
    phone: '(416) 984-4454',
    score: 0 },
  { first: 'Moc', phone: '(898) 983-5755', score: 0 },
  { first: 'Diblacbo',
    last: 'Li',
    phone: '(258) 838-8314',
    score: 0 },
  { first: 'Betu', last: 'Jol', phone: '(219) 234-9591', score: 0 },
  { first: 'Fuhetu', last: 'Ra', phone: '(631) 437-2332', score: 0 } ]

.maybe

.maybe(func) invokes a passed function with current value only if the current value is truthy.

In the address book above, Moc doesn't have a last name. Without .maybe(), we will end up calling .toLowerCase() on undefined, and an Error will be thrown.

We want to call .toLowerCase() only when we have something to call on.

// equivalent to function(x) { return x.last && x.last.toLowerCase() }
var lastNameLowered = It.get('last').maybe(It.send('toLowerCase'))
console.log(_.map(addressBook, lastNameLowered))
[ 'duhav', undefined, 'li', 'jol', 'ra' ]

Then you can filter out falsy value by using _.filter(..., It).

console.log(_.filter(_.map(addressBook, lastNameLowered), It))
[ 'duhav', 'li', 'jol', 'ra' ]

.or

Instead of using .maybe, we can use .or to put a default value.

var lastNameLowered2 = It.get('last').or('None').send('toLowerCase')
console.log(_.map(addressBook, lastNameLowered2))
[ 'duhav', 'none', 'li', 'jol', 'ra' ]

.instantiate

.instantiate(Constructor) can be used to quickly map things into an instance.

Here we have a Person class.

function Person(info) {
  this.info = info
}
Person.prototype.getName = function() {
  return this.info.first + ' ' + this.info.last
}
Person.prototype.greet = function() {
  console.log('Hello! I am "' + this.getName() + '"')
}

We can map everyone in the address book into a new Person instance!

// equivalent to function(x) { return new Person(x) }
var people = _.map(addressBook, It.instantiate(Person))
_.each(people, It.send('greet'))
Hello! I am "Sifwa Duhav"
Hello! I am "Moc undefined"
Hello! I am "Diblacbo Li"
Hello! I am "Betu Jol"
Hello! I am "Fuhetu Ra"

It.self

You can use It.self instead of It to create a function that uses the value of this instead of the value of passed argument.

You can use it to quickly make an accessor function

Person#getFirstName returns the first name.

// equivalent to function() { return this.info.first }
Person.prototype.getFirstName = It.self.get('info').get('first')

This function takes a last name, and returns a name suffix. No need to check of null here, we'll let .maybe do it.

function initial(string) {
  return ' ' + string.substr(0, 1) + '.'
}

Person#getLastInitial returns the initial of last name. If the person does not have last name, then return empty string.

// equivalent to function() { return (this.info.last && initial(this.info.last)) || '' }
Person.prototype.getLastInitial = It.self.get('info').get('last').maybe(initial).or('')

We can then redefine the getName method to make use of them:

Person.prototype.getName = function() {
  return this.getFirstName() + this.getLastInitial()
}
_.each(people, It.send('greet'))
Hello! I am "Sifwa D."
Hello! I am "Moc"
Hello! I am "Diblacbo L."
Hello! I am "Betu J."
Hello! I am "Fuhetu R."

.compose

You can use .derive to compose your own functionality.

Here we have these vectors...

var vectors = [
  { x: 1, y: 5 }, { x: 5, y: 1 }, { x: 2, y: -3 }
]

We also have a square function...

function square(x) {
  return x * x
}

Let's get the square of x and y components of these vectors!

console.log(_.map(vectors, It.get('x').compose(square)))
console.log(_.map(vectors, It.get('y').compose(square)))
[ 1, 25, 4 ]
[ 25, 1, 9 ]

You can also use .compose to chain functions together.

var test = { a: { b: 1 }, b: { a: 2 } }
var getA = It.get('a')
var getB = It.get('b')
var getAB = getA.compose(getB)
var getBA = getB.compose(getA)
console.log(test)
console.log(getA(test))
console.log(getB(test))
console.log(getAB(test))
console.log(getBA(test))
{ a: { b: 1 }, b: { a: 2 } }
{ b: 1 }
{ a: 2 }
1
2

Let's bring back that array of Person instances, and the firstNameLength function we created earlier before.

The firstNameLength function works with JSON data, not Person instances. Luckily, the Person class stores the original JSON data in the .info property. So we can still sort these people by their first name length.

_.each(_.sortBy(people, It.get('info').compose(firstNameLength)), It.send('greet'))
Hello! I am "Moc"
Hello! I am "Betu J."
Hello! I am "Sifwa D."
Hello! I am "Fuhetu R."
Hello! I am "Diblacbo L."

.tap

.tap invokes the passed function with the current value, and returns the current value.

log the numbers and while mapping to get the squares

console.log(numbers)
console.log(_.map(numbers, It.tap(console.log).compose(square)))
[ 3, 1, 4, 1, 5 ]
3
1
4
1
5
[ 9, 1, 16, 1, 25 ]

make everyone greet while mapping to get their first name

console.log(_.map(people, It.tap(It.send('greet')).send('getFirstName')))
Hello! I am "Sifwa D."
Hello! I am "Moc"
Hello! I am "Diblacbo L."
Hello! I am "Betu J."
Hello! I am "Fuhetu R."
[ 'Sifwa', 'Moc', 'Diblacbo', 'Betu', 'Fuhetu' ]

License

MIT Licensed