Search code examples
javascriptfunctional-programmingpointfreetacit-programming

How do I map & filter this in a point-free style


Dear StackOverflowers…

I have a set of posts:

const posts = [
  { title: 'post1', tags: ['all', 'half', 'third', 'quarter', 'sixth']},
  { title: 'post2', tags: ['all', 'half', 'third', 'quarter', 'sixth']},
  { title: 'post3', tags: ['all', 'half', 'third', 'quarter']},
  { title: 'post4', tags: ['all', 'half', 'third']},
  { title: 'post5', tags: ['all', 'half']},
  { title: 'post6', tags: ['all', 'half']},
  { title: 'post7', tags: ['all']},
  { title: 'post8', tags: ['all']},
  { title: 'post9', tags: ['all']},
  { title: 'post10', tags: ['all']},
  { title: 'post11', tags: ['all']},
  { title: 'post12', tags: ['all']}
];

And an ever increasing set of utility functions:

const map = f => list => list.map(f);
const filter = f => list => list.filter(f);
const reduce = f => y => xs => xs.reduce((y,x)=> f(y)(x), y);
const pipe = (fn,...fns) => (...args) => fns.reduce( (acc, f) => f(acc), fn(...args));
const comp = (...fns) => pipe(...fns.reverse()); //  const comp = (f, g) => x => f(g(x));
const prop = prop => obj => obj[prop];
const propEq = v => p => obj => prop(p)(obj) === v;
const flatten = reduce(y=> x=> y.concat(Array.isArray(x) ? flatten (x) : x)) ([]);
const unique = list => list.filter((v, i, a) => a.indexOf(v) === i);
const add = a => b => a + b;
const addO = a => b => Object.assign(a, b);
const log = x => console.log(x);

And I would like to massage the data into the format:

[
 { title: 'sixth', posts: [array of post objects that all have tag 'sixth'] },
 { title: 'quarter', posts: [array of post objects that all have tag 'quarter'] },
 { title: 'third', posts: [array of post objects that all have tag ’third'] },
 etc...
]

Using a point-free style, utilising just the reusable, compact utility functions.

I can get the unique tags from all the posts:

const tagsFor = comp(
  unique,
  flatten,
  map(prop('tags'))
);

tagsFor(posts);

And I can work out how to achieve what I want using map & filter:

tagsFor(posts).map(function(tag) {
  return {
    title: tag,
    posts: posts.filter(function(post) {
      return post.tags.some(t => t === tag);
    });
  };
});

I just can’t seem to get my head around achieving this in a tacit manner.

Any pointers would be gratefully received...


Solution

  • So with many thanks to @naomik for the restructuring and @Berghi for leading me down the rabbit hole of combinatory logic this is what I came up with…

    First off, tagsFor is collecting all the unique entries of some nested arrays into a single array, which sounds like generic functionality rather than something specific to any particular problem so I rewrote it to:

    const collectUniq = (p) => comp( // is this what flatMap does?
      uniq,
      flatten,
      map(prop(p))
    );
    

    So taking @naomik’s input we have:

    const hasTag = tag => comp(  // somePropEq?
      some(eq(tag)),
      prop('tags')
    );
    
    
    const makeTag = files => tag => ({
      title: tag,
      posts: filter (hasTag(tag)) (files)
    });
    
    
    const buildTags = comp(
      map(makeTag(posts)),
      collectUniq('tags')
    );
    

    The problem for any tacit solution is that the data (posts) is buried in makeTag in map.

    SKI calculus, and BCKW logic gives us a useful set of combinatory logic functions, which I’ll just leave here:

    const I = x => x;                       // id
    const B = f => g => x => f(g(x));       // compose <$> 
    const K = x => y => x;                  // pure
    const C = f => x => y => f(y)(x);       // flip
    const W = f => x => f(x)(x);            // join
    const S = f => g => x => f(x)(g(x));    // sub <*>
    

    We can could alias these to id, comp, pure, flip etc.. but in this instance I don’t think it helps grok anything.

    So, let’s dig out posts with B (compose):

    const buildTags = comp(
      B(map, makeTag)(posts),
      collectUniq('tags')
    );
    

    And now we can see it is in the form of f(x)(g(x)) where: f = B(map, makeTag); g = collectUniq('tags’); and x = posts:

    const buildTags = S(B(map)(makeTag))(collectUniq('tags'));
    

    Now it’s tacit, declarative, and easy to grok (to my mind anyway)

    Right, somebody get me a beer that took me 3 DAYS! (ouch)