如何展平嵌套的结果?
Posted
技术标签:
【中文标题】如何展平嵌套的结果?【英文标题】:How can I flatten nested Results? 【发布时间】:2020-03-20 12:54:36 【问题描述】:我正在使用第三方库,它提供了我必须“按原样”使用的基于树的数据结构。 API 返回Result<T, Error>
。我必须进行一些顺序调用并将错误转换为我的应用程序的内部错误。
use std::error::Error;
use std::fmt;
pub struct Tree
branches: Vec<Tree>,
impl Tree
pub fn new(branches: Vec<Tree>) -> Self
Tree branches
pub fn get_branch(&self, id: usize) -> Result<&Tree, TreeError>
self.branches.get(id).ok_or(TreeError
description: "not found".to_string(),
)
#[derive(Debug)]
pub struct TreeError
description: String,
impl Error for TreeError
fn description(&self) -> &str
self.description.as_str()
impl fmt::Display for TreeError
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result
self.description.fmt(f)
#[derive(Debug)]
pub struct MyAwesomeError
description: String,
impl MyAwesomeError
pub fn from<T: fmt::Debug>(t: T) -> Self
MyAwesomeError
description: format!(":?", t),
impl Error for MyAwesomeError
fn description(&self) -> &str
&self.description
impl fmt::Display for MyAwesomeError
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result
self.description.fmt(f)
如果我写这段代码:
pub fn take_first_three_times(tree: &Tree) -> Result<&Tree, MyAwesomeError>
let result = tree
.get_branch(0)
.map(|r| r.get_branch(0))
.map(|r| r.map(|r| r.get_branch(0)));
// ...
result
的类型将是 Result<Result<Result<Tree, TreeError>, TreeError>, TreeError>
。我不想通过match
的级联来处理错误。
我可以写一个内部函数,调整API的接口,处理基函数级别的错误:
fn take_first_three_times_internal(tree: &Tree) -> Result<&Tree, TreeError>
tree.get_branch(0)?.get_branch(0)?.get_branch(0)
pub fn take_first_three_times(tree: &Tree) -> Result<&Tree, MyAwesomeError>
take_first_three_times_internal(tree).map_err(MyAwesomeError::from)
如何在没有附加功能的情况下实现这一点?
【问题讨论】:
另见How do I unwrap an arbitrary number of nested Option types? 谢天谢地,它现在存在:doc.rust-lang.org/std/result/enum.Result.html#method.flatten。目前,仅限夜间 【参考方案1】:这是一个问题示例,当您在函数式编程中使用各种包装器(如 Option
)时。在函数式编程中有所谓的“纯”函数,它不改变某些状态(全局变量、输出参数),只依赖输入参数,只将它们的结果作为返回值返回,没有任何副作用。它使程序更加可预测和安全,但也带来了一些不便。
假设我们有let x = Some(2)
和一些函数f(x: i32) -> Option<f32>
。当您使用map
将该f
应用于x
时,您将嵌套Option<Option<f32>>
,这与您遇到的问题相同。
但是在函数式编程的世界里(Rust 的想法受到了很多启发,并且支持许多典型的“函数式”特性),他们提出了解决方案:monad。
我们可以向map
显示类似(A<T>, FnOnce(T)->U) -> A<U>
的签名,其中A
类似于包装器类型,例如Option
或Result
。在 FP 中,这种类型称为函子。但它有一个高级版本,称为 monad。除了map
函数之外,它的接口中还有一个类似的函数,传统上称为bind
,其签名类似于(A<T>, FnOnce(T) -> A<U>) -> A<U>
。更多详情there.
事实上,Rust 的Option
和Result
不仅是一个函子,也是一个单子。在我们的例子中,bind
实现为 and_then
方法。例如,您可以在我们的示例中像这样使用它:x.and_then(f)
,结果得到简单的Option<f32>
。因此,您可以使用.and_then
链来代替.map
链,它的作用非常相似,但不会有嵌套结果。
【讨论】:
以上是关于如何展平嵌套的结果?的主要内容,如果未能解决你的问题,请参考以下文章