我怎么能用Ramda.js更好地做到这一点

时间:2015-03-06 10:24:48

标签: javascript functional-programming ramda.js

所以我有一个div列表:list

我希望列表的子集删除带有.fade类的div。 并且只是使用.selected类从div中获取列表。

所以使用R.takeWhileR.dropWhile

然后我想映射该新列表,并在.activeR.takeR.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)

2 个答案:

答案 0 :(得分:22)

根据您的说明,您似乎希望filter超过takeWhiledropWhile

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也需要返回一个值。但是,您addClass的情况很糟糕。由于它会改变值(这是副作用),因此使用map有点用词不当。您最好使用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允许您调用对象上的函数。我们想要使用addClass参数在jquery包装对象上调用active。让我们为此做一个函数:

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没有改变其论点,我们也可以使用参数化将mapforEach合并为一个。我们可以通过使用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);

答案 1 :(得分:1)

我想你问这个问题是因为你得到了意想不到的结果。

一般说法是函数addActiveClass引入了副作用 - 它修改了DOM。这没有错,但从FP的角度来看,你最好隔离这种效果,e.q。在IO monad。

函数takeFromSelected和removeFadeItems使用谓词函数(function(item){!$(item)...};);确定要丢弃和采取什么。谓词函数需要返回true或false。在你的情况下,他们不会返回任何东西。解决方案是在return

前添加!$(item)

具有副作用的函数addActiveClass没有返回值。这会打破pipeline,因为下一个函数removeFadeItems不会显示任何内容。只需让addActiveClass返回$(item)。

我还没有对其进行测试,但是如果添加三个return,它可能会有效。

祝你好运。

更新:因为addActiveClass返回一个jQuery对象并将其传递给removeFadeItems,所以你不需要再次在$()中包装项目。