简体   繁体   中英

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...

I can see the influence of some of my other answers in your current work ^_^ @Bergi is giving you good advice, too. Just keep making generic procedures and composing them together.

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

Well the goal shouldn't be to go completely point-free. Often times you will end up with really weird comp (comp (f)) and comp (f) (comp (g)) stuff that is really hard to grok when you come back to it later.

We can still make a couple improvements with your code tho

This is the code we are changing

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

This is the updated code

// yay
tagsFor(posts).map(makeTag(posts));

// OR
map (makeTag (posts)) (tagsFor (posts));

Here's the utitilies

const comp = f => g => x => f (g (x));
const apply = f => x => f (x);
const eq = x => y => y === x;
const some = f => xs => xs.some(apply(f));
const filter = f => xs => xs.filter(apply(f));

const postHasTag = tag => comp (some (eq (tag))) (prop ('tags'));

const makeTag = posts => tag => ({
  title: tag,
  posts: filter (postHasTag (tag)) (posts)
});

Of course this is just one way to do it. Let me know if this helps or if you have any other questions !


"Ever-increasing set of utility functions"

It might feel overwhelming to have lots of utility functions, but you should be watching out for some that feel like you're duplicating behaviours.

Take this one for example ...

const propEq = v => p => obj => prop(p)(obj) === v;

3 parameters doesn't mean it's a bad function, but it should at least cause you to think twice about it and make sure they're required. Remember, it becomes harder to compose functions with more parameters, so you should be thinking carefully about the order of parameters too. Anyway, this propEq function should be raising a red flag for you.

const eq = x => y => y === x;
const prop = x => y => y[x];
const propEq = p => x => comp (eq(x)) (prop(p))

Once you have eq defined as a function, you should be able to compose it when you encounter the uncomposable === in your other functions. This goes for all operators in JavaScript.

As a little challenge, take a look at your reduce , pipe , and comp and see if you can remove a couple points. If you're getting stuck, let me know.

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 )

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM