Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add curried version of createSelector? #159

Closed
mrtnbroder opened this issue Aug 9, 2016 · 9 comments
Closed

add curried version of createSelector? #159

mrtnbroder opened this issue Aug 9, 2016 · 9 comments

Comments

@mrtnbroder
Copy link

mrtnbroder commented Aug 9, 2016

I've just ran into this problem. I wanted to cleanup some selectors and refactor them to be point-free.

Now, as the very last function of createSelector receives the return values of all the functions before that, I have to write one function that takes all x arguments at once and handle them. this makes it kinda hard to write in point-free style.

Given this selector for example:

const favoritePetsOfUser = (user, pets) => pets.filter((pet) => pet.id === user.favorites.favoritePetId)
const petsOfUserSelector = createSelector(
  userSelector,
  petsSelector,
  favoritePetsOfUser
)

to get rid of (user, pets) I could write (using ramda)

const pathToFav = R.pathEq(['pets', 'favoritePetId'])

// and use it like this:
const favoritePetsOfUser = R.compose(R.filter, pathToFav)

and use that as the last function within my selector.

BUT, and here's the thing: Only when the function would apply each argument one by one like this: favoritePetsOfUser(user)(pets)

so first call to favoritePetsOfUser would setup the path to the user and the second one would then call the actual filter method.

But actually it will be called like this favoritePetsOfUser(user, pets).

To make it work now I'd have to uncurry my functions first, which would be a lot of boilerplate if this happens a lot.

This sucks, so I'd love to have a curried version of createSelector, like createSelectorN or something like that, that applies each argument to the last function one by one.

I hope this makes sense to you. It's 2 am here almost and I'm pretty tired 😎 👍

Thanks!

@MattSPalmer
Copy link

MattSPalmer commented Aug 9, 2016

Hey Martin,

Not sure if this is what you meant by a lot of boilerplate, but would this do it?

const uncurry = curriedFn => (...args) => (
    args.reduce((left, right) => left(right), curriedFn)
);

const createSelectorN = (...selectors, curriedFn) => (
    createSelector(...selectors, uncurry(curriedFn))
);

@mrtnbroder
Copy link
Author

Hey @MattSPalmer,

Thanks! That is indeed exactly what I wanted... Ugh. Maybe I should have slept about it first before creating an issue.

I will write a helper file and import my createSelectorN from there.

Feel free to close this :)

@mrtnbroder
Copy link
Author

Actually, what if we could change the API of createSelector a bit to accept the selector functions first as an array of functions and let the second argument be the actual transforming function, where the second argument could also be applied at a later point in time, as the whole thing would be curried.

// create a new selector immediatly
const userAndPetsSelector = createSelector([selectUser, selectPets], transformingFunction)

// apply transforming function later
const userAndPets = createSelector([selectUser, selectPets])
const userAndPetsSelector = userAndPets(transformingFunction)

We would be more explicit with the functions we fed into our actual transforming function and could do a lot more stuff like, using concat etc. to push more selecting functions into the first argument of createSelector

@ellbee ellbee added this to the v3.0.0 milestone Aug 11, 2016
@ellbee
Copy link
Collaborator

ellbee commented Aug 11, 2016

It's an interesting proposal, but at this point in time the API for createSelector is stable and won't change. We could maybe add createCurriedSelector as a new function though.

@MattSPalmer Nice solution, in case anyone else is wanting the same thing I'm going to link to it from the FAQ. Cheers!

@alex3165
Copy link
Contributor

alex3165 commented Mar 10, 2017

@ellbee Is it still something needed to release 3.0.0 ? Should we keep this open ?

@ellbee
Copy link
Collaborator

ellbee commented Mar 10, 2017

Not needed for 3.0.0, but lets keep it open.

@ellbee ellbee modified the milestone: v3.0.0 Mar 10, 2017
@koutsenko
Copy link

koutsenko commented Mar 25, 2017

Can anyone provide an example how-to use createSelectorN?
And will be a createCurriedSelector included to release?

Thanks.

@monfera
Copy link

monfera commented May 27, 2017

A minor aside, and also incompatible: it might be useful to use partial application with createSelectorN itself, or, since the arguments are of an arbitrary arity, go full tilt and break it up instead, e.g. changing from this:

const createSelectorN = (...selectors, curriedFn) => (
    createSelector(...selectors, uncurry(curriedFn))
)

to

const createSelectorN = curriedFn => (...selectors) => (
    createSelector(...selectors, uncurry(curriedFn))
)

so a user may call

createSelectorN(func)(selector1, ..., selectorN)

which of course is just a lift operator on the space of selectors (it lifts the ordinary function func to work with selector inputs and yield a selector as a result).

This can be useful when the same lifted func can be plugged in eg.

const liftedFunc = createSelectorN(func)

const selA = liftedFunc(sel1, sel2)
const selB = liftedFunc(sel3, sel4)
const selC = liftedFunc(sel5, sel6)

so it's just a minor DRY application but chimes well with currying the ordinary function

@markerikson
Copy link
Contributor

Closing as this seems to be both stale and adequately answered.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants