Reputation: 75
So I have a list of divs: list
i want a subset of the list removing the divs with the .fade
class.
and also just grabbing the list from the div with .selected
class.
so using R.takeWhile
and R.dropWhile
.
then i want to map over that new list and add a .active
class on a subset of that list with R.take
and R.forEach
or R.map
something like :
var takeFromSelected = R.dropWhile(function(item){!$(item).hasClass('selected')};
var removeFadeItems = R.takeWhile(function(item){!$(item).hasClass('fade')});
var addActiveClass = function(x){ $(x).addClass('.active')};
var processList = R.pipe(R.map(addActiveClass), removeFadeItems, takeFromSelected);
processList(list);
Im really new to this FP stuff and trying to get the grip of it.
Any insight would be greatly apreciated!! Thanks! :)
for future reference this is what i did :
@addActiveClass = (x)->
$(x).addClass('active')
return
@takeFromSelected = R.dropWhile((item)-> !$(item).hasClass('selected'))
@removeFadeItems = R.takeWhile((item)-> !$(item).hasClass('fade'))
@addWeekView = R.compose(addActiveClass, removeFadeItems, takeFromSelected)
Upvotes: 3
Views: 3923
Reputation: 2336
From your description, it sounds like you want to use filter
more than takeWhile
or dropWhile
.
takeWhile
keeps values of the array until the first time the predicate fails:
> R.takeWhile(R.isEmpty, [[], [], [1, 2, 3], [], [1, 3]])
[ [], [] ]
dropWhile
removes values of the array until the first time the predicate fails:
> R.dropWhile(R.isEmpty, [[], [], [1, 2, 3], [], [1, 3]])
[ [ 1, 2, 3 ], [], [ 1, 3 ] ]
filter
removes all values that do not pass the predicate.
> R.filter(R.isEmpty, [[], [], [1, 2, 3], [], [1, 3]])
[ [], [], [] ]
In your case you want something like:
var removeFadeItems = R.filter(function(x) {
return !$(x).hasClass('fade');
});
var takeFromSelected = R.filter(function(x) {
return $(x).hasClass('selected');
});
Also, as @donnut stated, your map
needs to return a value as well. However, you're kind of in a bad way with addClass
. Since it mutates the value (which is a side-effect), using map
is a bit of a misnomer. You're better off using forEach
, as it is made for side-effecting things:
var addActiveClass = function(x) {
$(x).addClass('active');
};
So you end up with:
var processList = R.pipe(
R.forEach(addActiveClass),
takeFromSelected,
removeFadeItems
);
processList(list);
Now, being that some of your functions are referentially transparent (they don't mutate things), you can refactor this to be a bit clearer, more composable and slightly more efficient.
The first thing to note is that you're rewrapping your divs in each function. The $
is a good function to use to wrap things just once. So let's start the pipeline with that.
var processList = R.pipe(
R.map($),
...
Now, invoker
allows you to call a function on an object. We want to call addClass
on the jquery wrapped object with the argument of active
. Let's make a function for that:
var addActive = R.invoker(1, 'addClass', 'active');
We can add this to the pipeline.
var processList = R.pipe(
R.map($),
R.forEach(addActive),
...
The filters are similar to what we did in addActive
, let's refactor those too first by making the predicates separate:
var faded = R.invoker(1, 'hasClass', 'fade');
var notFaded = R.not(faded);
var selecteded = R.invoker(1, 'hasClass', 'selected');
The great thing here is the composability of ramda functions allows us to say R.not(faded)
, and things just work without thinking about it.
So lets add this to the pipeline.
var processList = R.pipe(
R.map($),
R.forEach(addActive),
R.filter(notFaded),
R.filter(selecteded)
);
This doesn't seem to have changed much of what the processing looks like. This is good! The primitives have changed, they're simpler and easier to see what's going on, but the overall flow is the same.
Now it's time to get heady. Because of parametricity, you can combine the two filters together without worrying about whether or not they make sense. There's a law that states R.pipe(R.filter(p), R.filter(q)) == R.pipe(R.filter(R.and(p, q))
. What this means is that you do not have to filter the array twice, you can filter it just once and apply the predicates in turn.
var processList = R.pipe(
R.map($),
R.forEach(addActive),
R.filter(R.and(notFaded, selecteded))
);
If addClass
did not mutate its argument, we could also use parametricity to combine the map
and forEach
into one. We can solve this by making our own non-mutating addClass
with clone
:
var newActive = R.pipe(
R.invoker(0, 'clone'),
R.invoker(1, 'addClass', 'active')
);
So we can use map again!. The pipeline can change to:
var processList = R.pipe(
R.map($),
R.map(newActive),
R.filter(R.and(notFaded, selecteded))
);
And we can now use parametricity to combine the maps together. The law states R.pipe(R.map(f), R.map(g)) == R.map(R.pipe(f, g))
. Instead of mapping twice over the array, we map once, and compose the functions within the map in turn. So our pipeline now looks like this:
var processList = R.pipe(
R.map(R.pipe($, newActive)),
R.filter(R.and(notFaded, selecteded))
);
There are further refactorings and optimizations we can make. We could filter before mapping so we end up iterating over less elements, or abstract the invoker
calls to a little jquery wrapper DSL. You're encouraged to continue with the refactoring, but this is a pretty nice change from the original. Each of functions does a very little, is much more composable, more testable, and more understandable.
The whole refactoring is as follows.
Before:
var removeFadeItems = R.filter(function(x) {
return !$(x).hasClass('fade');
});
var takeFromSelected = R.filter(function(x) {
return $(x).hasClass('selected');
});
var addActiveClass = function(x) {
$(x).addClass('active');
};
var processList = R.pipe(
R.forEach(addActiveClass),
takeFromSelected,
removeFadeItems
);
processList(list);
After:
var faded = R.invoker(1, 'hasClass', 'fade');
var selecteded = R.invoker(1, 'hasClass', 'selected');
var notFaded = R.not(faded);
var newActive = R.pipe(
R.invoker(0, 'clone'),
R.invoker(1, 'addClass', 'active')
);
var processList = R.pipe(
R.map(R.pipe($, newActive)),
R.filter(R.and(notFaded, selecteded))
);
processList(list);
Upvotes: 23
Reputation: 720
I suppose you ask this question because you get an unexpected result.
A general remark is that the function addActiveClass introduces a side-effect — it modifies the DOM. This isn't wrong, but from a FP point of view you better isolate this effect, e.q. in a IO monad.
The function takeFromSelected and removeFadeItems use predicate functions (function(item){ !$(item) ... };); to determine what to drop and take. A predicate function needs to return true or false. In your case they do not return anything. Solution is to add return
in front of !$(item)
The function addActiveClass, the one with the side-effect, does not have a return value. This breaks the pipeline
as the next function removeFadeItems
will not reveive anything. Just make addActiveClass
return the $(item).
I haven't tested it, but if you add the three return
s, it will probably work.
Good luck.
update: because addActiveClass
returns a jQuery object and passes that on to removeFadeItems
you do not need to wrap item in a $() again.
Upvotes: 1