functional-programming - 有条件的 Ramda 管道?

标签 functional-programming ramda.js

我有一个过滤器函数,它基本上具有多个确定过滤逻辑的变量。如果定义了变量,我想过滤——如果没有,我不想过滤(即在管道中执行函数)。更一般地说,有一个谓词,我可以检查管道的每个参数,以确定我应该调用它还是只传递给下一个函数。

我这样做是为了防止复杂的分支逻辑,但是对于函数式编程来说还是很新的,我认为这将是重构的最佳方式。

例如:

resources = R.pipe(
   filterWithRadius(lat, lng, radius), // if any of these arguments are nil, act like R.identity
   filterWithOptions(filterOptions)(keyword), // if either filterOptions or keyword is nil, act like R.identity
   filterWithOptions(tagOptions)(tag) // same as above.
)(resources);

我正在考虑使用 R.unless/R.when但它似乎不适用于具有多个参数的函数。 R.pipeWith如果它处理函数参数而不是在这里会很有用。

作为示例实现:

const filterWithRadius = R.curry((lat, long, radius, resources) =>
  R.pipe(
    filterByDistance(lat, long, radius), // simply filters down a geographic location, will fail if any of lat/long/radius are not defined
    R.map(addDistanceToObject(lat, long)), // adds distance to the lat and long to prop distanceFromCenter
    R.sortBy(R.prop("distanceFromCenter")) // sorts by distance
  )(resources)
);
resources是这些资源对象的数组。本质上,每个函数,filterRadiusfilterOptions是纯函数,需要一组资源和有效参数(不是未定义),并输出一个新的过滤列表。所以这里的目标是以某种方式组合(或重构),如果参数都是未定义的,它将运行函数,否则只是充当身份。

有比这更清洁/更好的方法吗?

resources = R.pipe(
   lat && lng && radius
     ? filterWithRadius(lat, lng, radius)
     : R.identity,
   keyword ? filterWithOptions(filterOptions)(keyword) : R.identity,
   tag ? filterWithOptions(tagOptions)(tag) : R.identity
)(resources);

最佳答案

我认为您希望将这种行为的责任放在错误的地方。如果您希望管道函数对某些数据具有一种行为,而对其他数据具有不同的行为(或者在这种情况下,缺少数据),那么这些单独的函数应该处理它,而不是包装它们的管道函数。

但是,正如 Ori Drori 指出的那样,您可以编写一个函数装饰器来实现这一点。

这里有一个建议:

// Dummy implementations
const filterWithRadius = (lat, lng, radius, resources) =>
  ({...resources, radiusFilter: `${lat}-${lng}-${radius}`})
const filterWithOptions = (opts, val, resources) => 
  ({...resources, [`optsFilter-${opts}`]: val})

// Test function (to be used in pipelines, but more general)
const ifNonNil = (fn) => (...args) => any(isNil, args) 
  ? identity 
  : (data) => fn (...[...args, data])
  // alternately, for variadic result : (...newArgs) => fn (...[...args, ...newArgs])

// Pipeline call
const getUpdatedResources = (
  {lat, lng, radius, filterOptions, keyword, tagOptions, tag}
) => pipe (
  ifNonNil (filterWithRadius) (lat, lng, radius),
  ifNonNil (filterWithOptions) (filterOptions, keyword),
  ifNonNil (filterWithOptions) (tagOptions, tag)
)

// Test data
const resources = {foo: 'bar'}

const query1 = {
   lat: 48.8584, lng: 2.2945, radius: 10, 
   filterOptions: 'baz', keyword: 'qux',
   tagOptions: 'grault', tag: 'corge'
}

const query2 = {
   lat: 48.8584, lng: 2.2945, radius: 10, 
   tagOptions: 'grault', tag: 'corge'
}

const query3 = {
   lat: 48.8584, lng: 2.2945, radius: 10, 
   filterOptions: 'baz', keyword: 'qux',
}

const query4 = {
   filterOptions: 'baz', keyword: 'qux',
   tagOptions: 'grault', tag: 'corge'
}

const query5 = {
   lat: 48.8584/*, lng: 2.2945*/, radius: 10, 
   filterOptions: 'baz', keyword: 'qux',
   tagOptions: 'grault', tag: 'corge'
}

const query6 = {}

// Demo
console .log (getUpdatedResources (query1) (resources))
console .log (getUpdatedResources (query2) (resources))
console .log (getUpdatedResources (query3) (resources))
console .log (getUpdatedResources (query4) (resources))
console .log (getUpdatedResources (query5) (resources))
console .log (getUpdatedResources (query6) (resources))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script> const {any, isNil, pipe, identity} = R </script>


我们从您的filter* 的虚拟实现开始。函数,它们只是将属性添加到输入对象。

这里的重要功能是ifNotNil .它的函数为n参数,返回 n - 1 的函数调用时检查这些参数是否为 nil 的参数.如果有,则返回标识函数;否则它返回一个有一个参数的函数,该函数又用 n - 1 调用原始函数论据和最新的一个。

我们使用它来构建一个管道,该管道将从接受所需变量的函数返回(这里从潜在的查询对象天真地解构)。通过传递查询然后传递要转换的实际数据来调用该函数。

这些示例显示了包含和排除的参数的各种组合。

这假设您的函数没有柯里化(Currying),也就是说,filterWithRadius看起来像 (lat, lng, radius, resources) => ...如果它们是 curry ,我们可以这样写:

const ifNonNil = (fn) => (...args) => any(isNil, args) 
  ? identity 
  : reduce ((f, arg) => f(arg), fn, args)



const filterWithRadius = (lat) => (lng) => (radius) => (resources) => 
  ({...resources, radiusFilter: `${lat}-${lng}-${radius}`})

但仍在管道中称为

pipe (
  ifNonNil (filterWithRadius) (lat, lng, radius),
  // ...
)

您甚至可以在同一管道中混合和匹配 curry 和非 curry 版本,尽管我希望这会增加困惑。

关于functional-programming - 有条件的 Ramda 管道?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59676424/

相关文章:

list - scala 返回第一个列表中的一些

javascript - 删除 if 语句

javascript - 如何使用 Ramda 将对象数组映射到单个对象

javascript - Ramda如何将参数传递给高阶函数?

javascript - 如何拆分字符串数组并返回具有键/值对的单个对象

functional-programming - 如何在 Scheme/Lisp 中为此数据结构编写平均函数?

javascript - 使用 Ramda 处理 Promise 和 Wait

javascript - webpack 导入所有代码而不是某些函数

javascript - 使用reduce fnt代替map

functional-programming - 寻找学习练习: implement these monads