functional-programming - 使参数可用于 Ramda 管道函数中的所有函数
问题描述
我Ramda
在 node 中使用 express。我有一条标准路线:
app.get('/api/v1/tours', (req, res) => {
}
我想使用 Ramda 编写函数,但我在路由之外编写这些函数(因此它们可以在其他路由中重用)。例如:
function extractParams() {
return req.query.id;
}
function findXById(id) {
return xs.find(el => el.id == id);
}
function success(answer) {
return res.status(200).json(answer);
}
现在我想在几个路由器中组合这些功能。其中之一将是:
app.get('/api/v1/tours', (req, res) => {
return R.pipe(extractParams, findXById, success)();
}
有什么方法可以准备一个通用包装器,将路由器上的请求和响应对象包装起来,以供这些功能使用?我想我也必须更改他们的签名。
解决方案
我认为这里真正需要的是一个版本,pipe
它接受一些初始参数并返回一个接受剩余参数的新函数,所有函数都具有这样的双重应用程序签名。我想出了以下doublePipe
实现:
const doublePipe = (...fns) => (...initialArgs) =>
pipe (...(map (pipe (apply, applyTo (initialArgs)), fns) ))
const foo = (x, y) => (z) => (x + y) * z
const bar = (x, y) => (z) => (x + y) * (z + 1)
const baz = doublePipe (foo, bar)
console .log (
baz (2, 4) (1) //=> (2 + 4) * (((2 + 4) * 1) + 1) => 42
// / \ '------+----'
// bar ( x --/ , `-- y , `-- z, which is foo (2, 4) (1) )
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script>const {pipe, map, apply, applyTo} = R </script>
请注意,函数foo
和bar
都将接收相同的x
和y
参数,并且foo (x, y)
将接收z
从外部提供的参数,其结果传递z
为bar (x, y)
。
这是一个有趣的函数,它是解决这类问题的一个相当有用的通用解决方案。 但它在您的 Express 环境中不起作用,因为处理程序需要具有签名(req, res) => ...
而不是(req, res) => (...args) => ...
.
所以下面是一个替代方案,它模仿了一个简单的类似 Express 的环境,并使用了一个稍微不同的doublePipe
版本,它不需要额外的调用,只需调用不带参数的第一个函数,然后按预期顺序将结果传递给其他函数. 这意味着第一个函数doublePipe
必须有签名(req, res) => () => ...
,而其他函数有(req, res) => (val) => ...
。虽然我们可以修复它,使第一个只是(req, res) => ...
,但在我看来,这种不一致不会有帮助。
const doublePipe = (...fns) => (...initialArgs) =>
reduce (applyTo, void 0, map (apply (__, initialArgs), fns))
const xs = [{id: 1, val: 'abc'}, {id: 2, val: 'def'},{id: 3, val: 'ghi'}, {id: 4, val: 'jkl'}]
const extractParams = (req, res) => () => req .query .id
const findXById = (xs) => (req, res) => (id) => xs .find (el => el .id == id)
const success = (req, res) => (answer) => res .status (200) .json (answer)
app .get ('/api/v1/tours', doublePipe (extractParams, findXById (xs), success))
console .log (
app .invoke ('get', '/api/v1/tours?foo=bar&id=3')
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script>
const {__, map, reduce, applyTo, apply, head, compose, split, objOf, fromPairs, last} = R
// Minimal version of Express, only enough for this demo
const base = compose (head, split ('?'))
const makeRequest = compose (objOf ('query'), fromPairs, map (split ('=')), split ('&'), last, split ('?'))
const makeResponse = () => {
const response = {
status: (val) => {response .status = val; return response},
json: (val) => {response.body = JSON .stringify (val); delete response.json; return response}
}
return response
}
const app = {
handlers: {get: {}, post: {}},
get: (route, handler) => app .handlers .get [route] = handler,
invoke: (method, route) =>
app .handlers [method] [base (route)] (makeRequest (route), makeResponse ())
}
</script>
findById
没有所需的签名,但是findById(xs)
有,所以这就是我们传递给pipe
.
最后,请注意,Ramda 和 Express 可能永远不会很好地结合在一起,因为发送到 Express 的处理程序旨在修改其参数,而 Ramda 旨在从不改变输入数据。也就是说,这似乎可以很好地满足这些要求。
更新:解释doublePipe
一条评论似乎表明需要更完整的描述doublePipe
。我只会讨论第二个版本,
const doublePipe = (...fns) => (...initialArgs) =>
reduce (applyTo, void 0, map (apply (__, initialArgs), fns))
这里有两个可能的调用:
// foo :: (a, b) -> f
const foo = doublePipe (
f1, // :: (a, b) -> Void -> (c)
f2, // :: (a, b) -> c -> d
f3, // :: (a, b) -> d -> e
f4, // :: (a, b) -> e -> f
)
// bar :: (a, b, c) -> f
const bar = doublePipe (
g1, // :: (a, b, c) -> Void -> d
g2, // :: (a, b, c) -> d -> e
g3, // :: (a, b, c) -> e -> f
)
如果你不熟悉 Hindley-Milner 签名(例如(a, b) -> c -> d
上面的),我在Ramda wiki上写了一篇关于它们在 Ramda 中的使用的长篇文章。该函数是通过传递-来构建的。生成的函数采用类型和(在您的示例中) 的参数并返回 type 的值。类似地,通过提供 -to 、返回一个接受类型参数的函数、和返回一个类型的值来创建。foo
f1
f4
doublePipe
a
b
req
res
f
bar
g1
g3
doublePipe
a
b
c
f
我们可以doublePipe
更加命令式地重写以显示所采取的步骤:
const doublePipe = (...fns) => (...initialArgs) => {
const resultFns = map (apply (__, initialArgs), fns)
return reduce (applyTo, void 0, resultFns)
}
并扩大一点,这也可能看起来像
const doublePipe = (...fns) => (...initialArgs) => {
const resultFns = map (fn => fn(...initialArgs), fns)
return reduce ((value, fn) => fn (value), undefined, resultFns)
}
在第一行中,我们将初始参数部分应用于每个提供的函数,为我们提供了一个更简单函数的列表。对于foo
resultFns 看起来像[f1(req, res), f2(req, res), f3(req, res), f4(req, res)]
,它会有签名[Void -> c, c -> d, d -> e, e -> f]
。我们现在可以选择pipe
这些函数并调用生成的return pipe(...resultFns)()
函数reduce
(undefined
将每个结果传递给下一个。
我倾向于从 Ramda 函数的角度来思考,但是没有它们你可以很容易地写出来:
const doublePipe = (...fns) => (...initialArgs) =>
fns
.map (fn => fn (...initialArgs))
.reduce ((value, fn) => fn (value), void 0)
我希望这更清楚!
推荐阅读
- php - 在 Phalcon 控制器中找不到模型
- c# - 尝试向 WebActivatorEx.PostApplicationStartMethod 添加参数
- pandas - 熊猫:无法从重复的轴重新索引
- node.js - 如何使用 pem lib NodeJs 创建证书颁发机构“CA”?
- laravel - 如何在登录失败时显示错误消息?
- javascript - JS函数在大于或小于之间切换
- reactjs - 我在 redux 中更改状态的调度没有被调用?
- scala - 如何在 Scala 中映射多维数组
- javascript - 来自 react-redux 的连接的高阶组件不适用于 Typescript Typings
- apache-spark - 如何在 Spark SQL 中将时间戳列转换为毫秒长列