Ramda:有没有办法在管道期间 'fork' 一个参数到两个函数?
Ramda: Is there a way to 'fork' a parameter to two functions during pipe?
我是函数式编程初学者。我正在使用 Ramda 开发 React Native 应用程序。该应用程序可让用户维护自己的房屋。
我编写了一个名为 asyncPipe
的函数,它可以让我通过管道传递 promises 和普通函数。我将它用于 loginFlow
,它目前有一个 http 请求 (getHouseList
) 作为它的最后一个功能。
const asyncPipe = (...fns) => x => fns.reduce(async (y, f) => f(await y), x);
const loginFlow = asyncPipe(
// ... someFunctions
getHouseList
);
// used later like this in LoginForm.js's handleSubmit():
const list = await loginFlow(credentials);
因此,登录后,该应用会加载用户的房屋。现在,根据他只有一所房子还是多所房子,我想将用户发送到列表视图以选择房子,或者如果他只有一所房子,则发送到详细视图。此外,我想发送一个 Redux 动作来将列表保存在我的减速器中,如果只有一个,我想发送另一个动作来选择房子。
目前我是这样做的:
const list = await loginFlow(credentials);
dispatch(addHouses(list));
if (list.length > 1) {
navigate('ListScreen')
} else {
dispatch(pickHouse(list[0]);
navigate('DetailScreen') ;
}
但如您所见,这是非常必要的。似乎我必须 'fork' 列表并在管道中使用它两次(因为 Redux 的 dispatch
没有 return 值)。
我的主要问题是:
如何更实用地/声明性地做到这一点(如果有办法的话)?
我有一个小问题,在这里强制执行是否可以/如果执行功能是个好主意。
你可能会扩展你的异步管道,使用类似 tap
:
const loginFlow = asyncPipe(
// ... some functions
getHouseList,
tap(compose(dispatch, addHouses)),
tap(unless(list => list.length > 1, list => dispatch(pickHouse(list[0])))),
list => navigate(list.length > 1 ? 'ListScreen' : 'DetailScreen', list)
);
是否值得这样做取决于您的应用。如果管道已经很长,那么以这种方式在末尾添加东西可能会更干净,即使它们不是特别实用的部分。但是对于较短的管道,这可能没有多大意义。
您可能还想查看 now-deprecated、pipeP or its replacement, pipeWith
(then
)。
但是您在标题中询问了关于分叉参数的问题。 Ramda 的 converge
正是这样做的:
converge(f, [g, h])(x) //=> f(g(x), h(x))
这也允许您传递两个以上的函数,并将多个参数传递给结果函数:
converge(f, [g, h, i])(x, y) //=> f(g(x, y), h(x, y), i(x, y))
鉴于我们可以使用 R.then
and R.otherwise
,那么 asyncPipe
并不是真正需要的。函数式编程的原则之一实际上是委托编排...
最后,您当然可以更具声明性,一个好的开始方式是尝试避免命令式控制流。 R.ifElse
一定能帮到你:)
如果您的代码有副作用,
然后在你的管道中使用 R.tap
:)
const fake = cb => () => cb([
{ name: 'Hitmands', id: 1 },
{ name: 'Giuseppe', id: 2 },
]);
const fakeApiCall = () => new Promise(resolve => setTimeout(fake(resolve), 500));
const dispatch = action => data => console.log(`dispatch("${action}")`, data);
const navigate = view => data => console.log(`navigate("${view}")`, data);
const loginFlow = (...fns) => R.pipe(
R.tap(() => console.log('login Flow Start')),
fakeApiCall,
R.then(R.pipe(
...fns,
R.tap(() => console.log('login Flow End')),
)),
)
const flow = loginFlow(
R.tap(dispatch('addHouse')), // use tap for side effects
R.ifElse(
R.pipe(R.length, R.gt(R.__, 1)), // result.length > 1
R.tap(navigate('ListScreen')), // onTrue
R.pipe( // onFalse
R.tap(dispatch('pickHouse')),
R.tap(navigate('DetailScreen')),
),
),
);
/* await */ flow();
/** UPDATES **/
const isXGreaterThan1 = R.gt(R.__, 1);
const isListLengthGreatherThanOne = R.pipe(R.length, isXGreaterThan1);
console.log(`is list.length > 1`, isListLengthGreatherThanOne([1, 2, 3]));
console.log(`is list.length > 1`, isListLengthGreatherThanOne([1]));
console.log(`is list.length > 1`, isListLengthGreatherThanOne([]));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
我是函数式编程初学者。我正在使用 Ramda 开发 React Native 应用程序。该应用程序可让用户维护自己的房屋。
我编写了一个名为 asyncPipe
的函数,它可以让我通过管道传递 promises 和普通函数。我将它用于 loginFlow
,它目前有一个 http 请求 (getHouseList
) 作为它的最后一个功能。
const asyncPipe = (...fns) => x => fns.reduce(async (y, f) => f(await y), x);
const loginFlow = asyncPipe(
// ... someFunctions
getHouseList
);
// used later like this in LoginForm.js's handleSubmit():
const list = await loginFlow(credentials);
因此,登录后,该应用会加载用户的房屋。现在,根据他只有一所房子还是多所房子,我想将用户发送到列表视图以选择房子,或者如果他只有一所房子,则发送到详细视图。此外,我想发送一个 Redux 动作来将列表保存在我的减速器中,如果只有一个,我想发送另一个动作来选择房子。
目前我是这样做的:
const list = await loginFlow(credentials);
dispatch(addHouses(list));
if (list.length > 1) {
navigate('ListScreen')
} else {
dispatch(pickHouse(list[0]);
navigate('DetailScreen') ;
}
但如您所见,这是非常必要的。似乎我必须 'fork' 列表并在管道中使用它两次(因为 Redux 的 dispatch
没有 return 值)。
我的主要问题是:
如何更实用地/声明性地做到这一点(如果有办法的话)?
我有一个小问题,在这里强制执行是否可以/如果执行功能是个好主意。
你可能会扩展你的异步管道,使用类似 tap
:
const loginFlow = asyncPipe(
// ... some functions
getHouseList,
tap(compose(dispatch, addHouses)),
tap(unless(list => list.length > 1, list => dispatch(pickHouse(list[0])))),
list => navigate(list.length > 1 ? 'ListScreen' : 'DetailScreen', list)
);
是否值得这样做取决于您的应用。如果管道已经很长,那么以这种方式在末尾添加东西可能会更干净,即使它们不是特别实用的部分。但是对于较短的管道,这可能没有多大意义。
您可能还想查看 now-deprecated、pipeP or its replacement, pipeWith
(then
)。
但是您在标题中询问了关于分叉参数的问题。 Ramda 的 converge
正是这样做的:
converge(f, [g, h])(x) //=> f(g(x), h(x))
这也允许您传递两个以上的函数,并将多个参数传递给结果函数:
converge(f, [g, h, i])(x, y) //=> f(g(x, y), h(x, y), i(x, y))
鉴于我们可以使用 R.then
and R.otherwise
,那么 asyncPipe
并不是真正需要的。函数式编程的原则之一实际上是委托编排...
最后,您当然可以更具声明性,一个好的开始方式是尝试避免命令式控制流。 R.ifElse
一定能帮到你:)
如果您的代码有副作用,
然后在你的管道中使用 R.tap
:)
const fake = cb => () => cb([
{ name: 'Hitmands', id: 1 },
{ name: 'Giuseppe', id: 2 },
]);
const fakeApiCall = () => new Promise(resolve => setTimeout(fake(resolve), 500));
const dispatch = action => data => console.log(`dispatch("${action}")`, data);
const navigate = view => data => console.log(`navigate("${view}")`, data);
const loginFlow = (...fns) => R.pipe(
R.tap(() => console.log('login Flow Start')),
fakeApiCall,
R.then(R.pipe(
...fns,
R.tap(() => console.log('login Flow End')),
)),
)
const flow = loginFlow(
R.tap(dispatch('addHouse')), // use tap for side effects
R.ifElse(
R.pipe(R.length, R.gt(R.__, 1)), // result.length > 1
R.tap(navigate('ListScreen')), // onTrue
R.pipe( // onFalse
R.tap(dispatch('pickHouse')),
R.tap(navigate('DetailScreen')),
),
),
);
/* await */ flow();
/** UPDATES **/
const isXGreaterThan1 = R.gt(R.__, 1);
const isListLengthGreatherThanOne = R.pipe(R.length, isXGreaterThan1);
console.log(`is list.length > 1`, isListLengthGreatherThanOne([1, 2, 3]));
console.log(`is list.length > 1`, isListLengthGreatherThanOne([1]));
console.log(`is list.length > 1`, isListLengthGreatherThanOne([]));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>