我怎样才能用 Ramda.js 做得更好
How can i do this better with Ramda.js
所以我有一个 div 的列表:list
我想要列表的一个子集删除 div 和 .fade
class。
并且还只是从 div 和 .selected
class.
中获取列表
所以使用 R.takeWhile
和 R.dropWhile
。
然后我想映射到那个新列表并在该列表的子集上添加 .active
class R.take
和 R.forEach
或 R.map
类似于:
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);
我对这个 FP 东西真的很陌生,正在努力掌握它。
任何见解将不胜感激!!谢谢! :)
更新
为了将来参考,这就是我所做的:
@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)
我想你问这个问题是因为你得到了意想不到的结果。
一个普遍的说法是函数 addActiveClass 引入了一个副作用——它修改了 DOM。这没有错,但是从 FP 的角度来看,您最好隔离这种影响,e.q。在 IO monad 中。
函数takeFromSelected和removeFadeItems使用谓词函数(function(item){ !$(item) ... };);确定要丢弃和带走的东西。谓词函数需要 return true 或 false。在你的情况下,他们什么都不 return。解决方法是在!$(item)
前面加上return
具有副作用的函数 addActiveClass 没有 return 值。这会破坏 pipeline
,因为下一个函数 removeFadeItems
不会收到任何东西。只需将 addActiveClass
return 设为 $(item).
我还没有测试过,但是如果你加上三个 return
,它可能会起作用。
祝你好运。
更新:因为 addActiveClass
return 是一个 jQuery 对象并将其传递给 removeFadeItems
您不需要再次将项目包装在 $() 中。
根据您的描述,您似乎想使用 filter
more than takeWhile
or dropWhile
。
takeWhile
保留数组的值直到谓词第一次失败:
> R.takeWhile(R.isEmpty, [[], [], [1, 2, 3], [], [1, 3]])
[ [], [] ]
dropWhile
删除数组的值,直到谓词第一次失败:
> R.dropWhile(R.isEmpty, [[], [], [1, 2, 3], [], [1, 3]])
[ [ 1, 2, 3 ], [], [ 1, 3 ] ]
filter
删除所有未通过谓词的值。
> R.filter(R.isEmpty, [[], [], [1, 2, 3], [], [1, 3]])
[ [], [], [] ]
在你的情况下你想要这样的东西:
var removeFadeItems = R.filter(function(x) {
return !$(x).hasClass('fade');
});
var takeFromSelected = R.filter(function(x) {
return $(x).hasClass('selected');
});
另外,正如@donnut 所说,你的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
,因为它是为产生副作用而设计的:
var addActiveClass = function(x) {
$(x).addClass('active');
};
所以你最终得到:
var processList = R.pipe(
R.forEach(addActiveClass),
takeFromSelected,
removeFadeItems
);
processList(list);
重构
现在,由于您的一些函数是引用透明的(它们不会改变事物),您可以将其重构为更清晰、更可组合且更高效。
首先要注意的是,您要在每个函数中重新包装 div。 $
是一个很好的函数,可以用来只包装一次。因此,让我们以此开始管道。
var processList = R.pipe(
R.map($),
...
现在,invoker
允许您调用对象上的函数。我们想用参数 active
在 jquery 包装对象上调用 addClass
。让我们为此创建一个函数:
var addActive = R.invoker(1, 'addClass', 'active');
我们可以将其添加到管道中。
var processList = R.pipe(
R.map($),
R.forEach(addActive),
...
过滤器与我们在 addActive
中所做的类似,让我们先通过将谓词分开来重构它们:
var faded = R.invoker(1, 'hasClass', 'fade');
var notFaded = R.not(faded);
var selecteded = R.invoker(1, 'hasClass', 'selected');
这里最棒的是 ramda 函数的可组合性让我们可以说 R.not(faded)
,事情不用考虑就可以工作。
所以让我们将其添加到管道中。
var processList = R.pipe(
R.map($),
R.forEach(addActive),
R.filter(notFaded),
R.filter(selecteded)
);
这似乎并没有改变处理过程的大部分内容。这很好!原语已经改变,它们更简单,更容易看到发生了什么,但整体流程是一样的。
现在是兴奋的时候了。由于参数化,您可以将两个过滤器组合在一起,而不必担心它们是否有意义。有一条法律规定 R.pipe(R.filter(p), R.filter(q)) == R.pipe(R.filter(R.and(p, q))
。这意味着您不必过滤数组两次,您可以只过滤一次并依次应用谓词。
var processList = R.pipe(
R.map($),
R.forEach(addActive),
R.filter(R.and(notFaded, selecteded))
);
如果addClass
没有改变它的参数,我们也可以使用参数化将map
和forEach
合二为一。我们可以通过使用 clone
:
创建我们自己的非变异 addClass
来解决这个问题
var newActive = R.pipe(
R.invoker(0, 'clone'),
R.invoker(1, 'addClass', 'active')
);
所以我们可以再次使用地图!。管道可以更改为:
var processList = R.pipe(
R.map($),
R.map(newActive),
R.filter(R.and(notFaded, selecteded))
);
我们现在可以使用参数化将贴图组合在一起。法律规定 R.pipe(R.map(f), R.map(g)) == R.map(R.pipe(f, g))
。我们不是在数组上映射两次,而是映射一次,然后依次在映射中组合函数。所以我们的管道现在看起来像这样:
var processList = R.pipe(
R.map(R.pipe($, newActive)),
R.filter(R.and(notFaded, selecteded))
);
我们可以进行进一步的重构和优化。我们可以在映射之前进行过滤,这样我们就可以迭代更少的元素,或者将 invoker
调用抽象为一个 jquery 包装器 DSL。我们鼓励您继续进行重构,但这是与原始版本相比的一个相当不错的更改。每个函数只做很少的事情,更可组合,更可测试,更容易理解。
整个重构如下
之前:
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);
之后:
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);
所以我有一个 div 的列表:list
我想要列表的一个子集删除 div 和 .fade
class。
并且还只是从 div 和 .selected
class.
所以使用 R.takeWhile
和 R.dropWhile
。
然后我想映射到那个新列表并在该列表的子集上添加 .active
class R.take
和 R.forEach
或 R.map
类似于:
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);
我对这个 FP 东西真的很陌生,正在努力掌握它。
任何见解将不胜感激!!谢谢! :)
更新
为了将来参考,这就是我所做的:
@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)
我想你问这个问题是因为你得到了意想不到的结果。
一个普遍的说法是函数 addActiveClass 引入了一个副作用——它修改了 DOM。这没有错,但是从 FP 的角度来看,您最好隔离这种影响,e.q。在 IO monad 中。
函数takeFromSelected和removeFadeItems使用谓词函数(function(item){ !$(item) ... };);确定要丢弃和带走的东西。谓词函数需要 return true 或 false。在你的情况下,他们什么都不 return。解决方法是在!$(item)
return
具有副作用的函数 addActiveClass 没有 return 值。这会破坏 pipeline
,因为下一个函数 removeFadeItems
不会收到任何东西。只需将 addActiveClass
return 设为 $(item).
我还没有测试过,但是如果你加上三个 return
,它可能会起作用。
祝你好运。
更新:因为 addActiveClass
return 是一个 jQuery 对象并将其传递给 removeFadeItems
您不需要再次将项目包装在 $() 中。
根据您的描述,您似乎想使用 filter
more than takeWhile
or dropWhile
。
takeWhile
保留数组的值直到谓词第一次失败:
> R.takeWhile(R.isEmpty, [[], [], [1, 2, 3], [], [1, 3]])
[ [], [] ]
dropWhile
删除数组的值,直到谓词第一次失败:
> R.dropWhile(R.isEmpty, [[], [], [1, 2, 3], [], [1, 3]])
[ [ 1, 2, 3 ], [], [ 1, 3 ] ]
filter
删除所有未通过谓词的值。
> R.filter(R.isEmpty, [[], [], [1, 2, 3], [], [1, 3]])
[ [], [], [] ]
在你的情况下你想要这样的东西:
var removeFadeItems = R.filter(function(x) {
return !$(x).hasClass('fade');
});
var takeFromSelected = R.filter(function(x) {
return $(x).hasClass('selected');
});
另外,正如@donnut 所说,你的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
,因为它是为产生副作用而设计的:
var addActiveClass = function(x) {
$(x).addClass('active');
};
所以你最终得到:
var processList = R.pipe(
R.forEach(addActiveClass),
takeFromSelected,
removeFadeItems
);
processList(list);
重构
现在,由于您的一些函数是引用透明的(它们不会改变事物),您可以将其重构为更清晰、更可组合且更高效。
首先要注意的是,您要在每个函数中重新包装 div。 $
是一个很好的函数,可以用来只包装一次。因此,让我们以此开始管道。
var processList = R.pipe(
R.map($),
...
现在,invoker
允许您调用对象上的函数。我们想用参数 active
在 jquery 包装对象上调用 addClass
。让我们为此创建一个函数:
var addActive = R.invoker(1, 'addClass', 'active');
我们可以将其添加到管道中。
var processList = R.pipe(
R.map($),
R.forEach(addActive),
...
过滤器与我们在 addActive
中所做的类似,让我们先通过将谓词分开来重构它们:
var faded = R.invoker(1, 'hasClass', 'fade');
var notFaded = R.not(faded);
var selecteded = R.invoker(1, 'hasClass', 'selected');
这里最棒的是 ramda 函数的可组合性让我们可以说 R.not(faded)
,事情不用考虑就可以工作。
所以让我们将其添加到管道中。
var processList = R.pipe(
R.map($),
R.forEach(addActive),
R.filter(notFaded),
R.filter(selecteded)
);
这似乎并没有改变处理过程的大部分内容。这很好!原语已经改变,它们更简单,更容易看到发生了什么,但整体流程是一样的。
现在是兴奋的时候了。由于参数化,您可以将两个过滤器组合在一起,而不必担心它们是否有意义。有一条法律规定 R.pipe(R.filter(p), R.filter(q)) == R.pipe(R.filter(R.and(p, q))
。这意味着您不必过滤数组两次,您可以只过滤一次并依次应用谓词。
var processList = R.pipe(
R.map($),
R.forEach(addActive),
R.filter(R.and(notFaded, selecteded))
);
如果addClass
没有改变它的参数,我们也可以使用参数化将map
和forEach
合二为一。我们可以通过使用 clone
:
addClass
来解决这个问题
var newActive = R.pipe(
R.invoker(0, 'clone'),
R.invoker(1, 'addClass', 'active')
);
所以我们可以再次使用地图!。管道可以更改为:
var processList = R.pipe(
R.map($),
R.map(newActive),
R.filter(R.and(notFaded, selecteded))
);
我们现在可以使用参数化将贴图组合在一起。法律规定 R.pipe(R.map(f), R.map(g)) == R.map(R.pipe(f, g))
。我们不是在数组上映射两次,而是映射一次,然后依次在映射中组合函数。所以我们的管道现在看起来像这样:
var processList = R.pipe(
R.map(R.pipe($, newActive)),
R.filter(R.and(notFaded, selecteded))
);
我们可以进行进一步的重构和优化。我们可以在映射之前进行过滤,这样我们就可以迭代更少的元素,或者将 invoker
调用抽象为一个 jquery 包装器 DSL。我们鼓励您继续进行重构,但这是与原始版本相比的一个相当不错的更改。每个函数只做很少的事情,更可组合,更可测试,更容易理解。
整个重构如下
之前:
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);
之后:
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);