如何展平嵌套结果?
How can I flatten nested Results?
我正在使用第三方库,该库提供我必须“按原样”使用的基于树的数据结构。 APIreturnsResult<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)
}
如果没有附加功能,如何实现?
这是一个问题示例,当您在函数式编程中使用 Option
等各种包装器时。在函数式编程中有所谓的 'pure' 函数,而不是改变一些状态(全局变量,输出参数)只依赖于输入参数并且只 return 它们的结果作为 return 值没有任何副作用。它使程序更具可预测性和安全性,但也带来了一些不便。
假设我们有 let x = Some(2)
和一些函数 f(x: i32) -> Option<f32>
。当您使用 map
将 f
应用到 x
时,您将嵌套 Option<Option<f32>>
,这与您遇到的问题相同。
但是在函数式编程的世界里(Rust 从他们的想法中获得了很多灵感并支持很多典型的 'functional' 特性)他们提出了解决方案:monads。
我们可以显示 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
不仅是一个函子,还是一个 monad。在我们的例子中 bind
被实现为 and_then
方法。例如,您可以像这样在我们的示例中使用它:x.and_then(f)
,并得到简单的 Option<f32>
作为结果。因此,您可以使用 .and_then
链代替 .map
链,它的行为非常相似,但不会有嵌套结果。
我正在使用第三方库,该库提供我必须“按原样”使用的基于树的数据结构。 APIreturnsResult<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)
}
如果没有附加功能,如何实现?
这是一个问题示例,当您在函数式编程中使用 Option
等各种包装器时。在函数式编程中有所谓的 'pure' 函数,而不是改变一些状态(全局变量,输出参数)只依赖于输入参数并且只 return 它们的结果作为 return 值没有任何副作用。它使程序更具可预测性和安全性,但也带来了一些不便。
假设我们有 let x = Some(2)
和一些函数 f(x: i32) -> Option<f32>
。当您使用 map
将 f
应用到 x
时,您将嵌套 Option<Option<f32>>
,这与您遇到的问题相同。
但是在函数式编程的世界里(Rust 从他们的想法中获得了很多灵感并支持很多典型的 'functional' 特性)他们提出了解决方案:monads。
我们可以显示 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
不仅是一个函子,还是一个 monad。在我们的例子中 bind
被实现为 and_then
方法。例如,您可以像这样在我们的示例中使用它:x.and_then(f)
,并得到简单的 Option<f32>
作为结果。因此,您可以使用 .and_then
链代替 .map
链,它的行为非常相似,但不会有嵌套结果。