Coder Social home page Coder Social logo

functify's Introduction

functify

Build Status

Add functional methods like map, reduce, filter, etc. to iterables (ES6 Iterators).

Why

Only Array has handy functions like map, reduce, filter, etc. Other iterable objects such as Map, Set, String, NodeList, and your own iterables don't. It would be nice if all iterables had access these functions.

Unfortunately, the Array versions of map and filter return a new array. This results in wasted memory allocation when chaining map and filter. Iterators fix this. A value is only computed when the final iterator in chain is asked for the next value. The intermediate values are passed through the chain but aren't permenantly stored anywhere.

This sounds an awful like transducers. It is, the main difference is that methods can be called in-line as opposed composing function a priori.

Usage

let f = require('functify');

let numbers = f([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);

for (let odd in numbers.filter(n => n % 2)) {
    console.log(n);  // 1, 3, 5, ...
}

for (let even in numbers.filter(n => !(n % 2))) {
    console.log(n);  // 2, 4, 6, ...
}

for (let [odd, even] in numbers.split(n => n % 2, n => !(n % 2)).zip()) {
    console.log(`odd = ${odd}, even = ${even}`);  // [1, 2], [3, 4], ...
}

for (let square in numbers.take(3).map(n => n * n)) {
    console.log(square);  // 1, 4, 9
}

Maps and Objects

The new Map class in ES6 has three methods which return iterators:

  • keys()
  • values()
  • entries()

A Map instance itself can be used as an iterator, e.g.

let map = new Map();
map.set('x', 5);
map.set('y', 10);

for (let [k, v] of map) {
    console.log(`map['${k}'] = ${v}`);  // map['x'] = 5, map['y'] = 10
}

functify wraps Map instances and exposes versions of keys(), values(), and entries() that methods like map() and filter() can be chained to, e.g.

for (let v2 of functify(map).entries().map(pair => pair[1] * pair[1])) {
    console.log(v2);  // 25, 100
}

Note: chaining in the opposite order is not allowed because map may return something that isn't an entry, i.e. a [key, value] pair.

Plain old JavaScript objects do not have methods. ES5 has the static method Object.keys() and there is a pre-strawman proposal to add Object.values() and Object.entries(). The problem with these methods is that they return arrays which consume memory.

functify wraps Object instances, adding keys(), values(), and entries() methods along with all the other methods that functify provides.

let obj = {
    x: 5,
    y: 10
}

for (let [k, v] of functify(obj)) {
    console.log(`obj['${k}'] = ${v}`);  // obj['x'] = 5, obj['y'] = 10
}

The combines the simple creation and access syntax of Objects with the powerful iterators provided by Map.

Implementation Details

functify wraps iterables in an object with methods to performan map, reduce, filter, etc. This object is also iterable. Here's how:

class Functified {
    constructor(iterable) {
        this.iterable = iterable;
    }

    *[Symbol.iterator]() {
        for (let value of this.iterable) {
            yield value;
        }
    }

    // various instance and static methods

In order to make it easier to write methods on Functified there's also a static method fromGenerator(generator) which takes a generator and returns an iterator.

static fromGenerator(generator) {
    return funcitify({
        [Symbol.iterator]: generator
    });
}

This allows methods to be easily implemented. Here's the implementation for map:

map(callback) {
    var iterable = this.iterable;
    return Functified.fromGenerator(function* () {
        for (let value of iterable) {
            yield callback(value);
        }
    });
}

Pausable

Sometimes you may want to take some of the values from the iterator, do something with those values, and then resume taking values where you left of at some point in the future. Normally you would have to resort to creating an iterator and calling next() manually, e.g.

var numbers = [1,2,3,4,5];
var iterator = numbers[Symbol.iterator]();

for (let i = 0; i < 2; i++) {
    console.log(iterator.next().value);
}

// do something else

while (true) {
    let result = iterator.next();
    if (result.done) {
        break;
    }
    let value = iterator.next().value;
    let square = value * value;
    console.log(value * value);
}

The toPausable() creates an iterator Below is an example of how this works.

var numbers = [1,2,3,4,5];
var pausableNumbers = numbers.toPausable();

for (let n of pausableNumbers.take(2)) {
    console.log(n);     // 1 2
}

// do something else

for (let n of pausableNumbers.map(x => x * x).takeUntil(x => x > 16)) {
    console.log(n);     // 9 16
}

functify's People

Contributors

kevinbarabash avatar oliverjash avatar rybesh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

functify's Issues

Publish on npm?

Any chance you could publish this on npm? I'd like to use it. If you're not planning on maintaining it, no worries, I can just fork.

Should takeUntil be inclusive?

takeUntil does not return the last item (where the predicate matches).

Do you think it should, or do you think we could add another operator that does?

Suggestions/questions

Hi! Firstly, thanks for the great project. This is exactly what I've been looking for. I want to ask a few questions and raise a few ideas, if you have the time!

  1. It would be really convenient to have type-specific convenience methods like mapValues/toMap for Map.
  2. I've started writing some type definitions in TypeScript. Perhaps we could add these to the project? You could continue to ship the compiled JS, plus we and any other TypeScript users of this library get type safety!

Here's an extract of what I've been working on for the above two suggestions. Let me know what you think and whether this is worth taking any further!

class Functified<A> {
    constructor(
        public iterable: Iterable<A>
    ) {}

    *[Symbol.iterator]() {
        for (const value of this.iterable) {
            yield value;
        }
    }

    map<B>(fn: (t: A) => B) {
        const { iterable } = this;
        const generator = function* () {
            for (const value of iterable) {
                yield fn(value);
            }
        };
        return Functified.fromGenerator(generator);
    }

    static fromGenerator<B>(generator: () => IterableIterator<B>) {
        const iterable: Iterable<B> = {
            [Symbol.iterator]: generator
        };
        return new Functified(iterable);
    }
}

class FunctifiedMap<K, V> extends Functified<[K, V]> {
    constructor(map: Iterable<[K, V]>) {
        super(map)
    }

    mapValues<V2>(fn: (t: V) => V2) {
        return new FunctifiedMap(
            this.map(([ key, value ]): [ K, V2 ] => [ key, fn(value) ])
        )
    }

    toMap() {
        const transformedArray = Array.from(this.iterable);
        return new Map(transformedArray);
    }

    static fromGenerator<K, V>(generator: () => IterableIterator<[K, V]>) {
        const iterable: Iterable<[K, V]> = {
            [Symbol.iterator]: generator
        };
        return new FunctifiedMap(iterable);
    }
}

{
    const originalMap = new Map([ [ 'x', 5 ], [ 'y', 10 ] ]);

    const transformedMapAsFunctifiedMap = new FunctifiedMap(originalMap)
        .mapValues(value => value * 5)

    console.log(transformedMapAsFunctifiedMap.toMap())
}

rewrite without using generators

Generators aren't necessary for iterators. Few browsers support them natively and the polyfill/generated code is quite heavy.

require("functify") should export a single function

In an effort to make functify easier to use I'm going to have it export only the functify function. Static methods will be available off of functify itself. Using it will look something like this:

let f = require("functify");

for (let i of f.range(0,10).map(x => x * x)) {
  console.log(i);
}

throw exceptions where appropriate

e.g. only certain methods are available on Pausables, if any other method is called it should throw (or end the iterable and log a warning)

let functify accept Objects

for (let [k,v] of functify(obj)) { ... }

as a convenience for

for (let [k, v] of Functified.entries(obj)) { ... }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.