rust - 在 Rust 中处理多个 `Option<T>` 的惯用方法是什么?

标签 rust option-type monads

由于我是 Rust 的新手,我需要有关如何以惯用方式完成错误处理的指导。我发现错误处理样板文件真的很烦人。

我受困于多个 Option<T> 。手动处理每个 None 案例太冗长了。

例如,在 Haskell 中,您可以将可选值 (Maybe) 操作与各种运算符链接起来:fmap<*>>>= 等:

f x = x * x
g x = x ++ x
main = print $ g <$> show <$> f <$> Just 2

这在 Rust 中看起来是不可能的。我正在尝试将两个字符的卡片字符串解析为结构 Card :

const FACES: &'static str = "23456789TJQKA";
const SUITS: &'static str = "CDHS";
enum Face { /* ... */ }
enum Suit { C, D, H, S }
struct Card {
    face: Face,
    suit: Suit
}
impl FromStr for Card {
    type Err = ();
    fn from_str(x: &str) -> Result<Self, Self::Err> {
        let mut xs = x.chars();
        let a = chain(xs.next(), |x| FACES.find(x), Face::from_usize);
        let b = chain(xs.next(), |x| SUITS.find(x), Suit::from_usize);
        if let (Some(face), Some(suit)) = (a, b) {
            Ok(Card::new(face, suit))
        } else {
            Err(())
        }
    }
}

这段代码在 Haskell 中看起来像这样:

import Data.List (elemIndex)
x = Just 'C'
suits = "CDHS"
data Suit = C | D | H | S deriving Show
fromInt 0 = C
find = flip elemIndex
main = print $ x >>= find suits >>= return . fromInt

感谢通过 >>= 的链接,Haskell 使得操纵 monad 的内部值成为可能(而且很容易!)。为了实现接近于此的目标,我必须编写 chain 函数,这看起来非常不合时宜:

fn join<T>(x: Option<Option<T>>) -> Option<T> {
    if let Some(y) = x {
        y
    } else {
        None
    }
}

fn bind<A, B, F>(x: Option<A>, f: F) -> Option<B>
where
    F: FnOnce(A) -> Option<B>,
{
    join(x.map(f))
}

fn chain<A, B, C, F, G>(x: Option<A>, f: F, g: G) -> Option<C>
where
    F: FnOnce(A) -> Option<B>,
    G: FnOnce(B) -> Option<C>,
{
    bind(bind(x, f), g)
}

最佳答案

如前所述,OptionResult上面有 实用方法。此外,try 运算符 (?) 也可用于“返回失败或解包结果”这种极其常见的情况

我将为 FaceSuit 实现 FromStr。您的代码将如下所示:

impl FromStr for Card {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let face = s[0..1].parse()?;
        let suit = s[1..2].parse()?;

        Ok(Card { face, suit })
    }
}

如果您没有/不能,您可以使用Option 上的现有方法。你没有定义 Foo::from_usize,所以我假设返回 Foo,所以它会使用 map:

fn from_str(s: &str) -> Result<Self, Self::Err> {
    let mut c = s.chars();

    let face = c
        .next()
        .and_then(|c| FACES.find(c))
        .map(Face::from_usize)
        .ok_or(())?;
    let suit = c
        .next()
        .and_then(|c| SUITS.find(c))
        .map(Suit::from_usize)
        .ok_or(())?;

    Ok(Card { face, suit })
}

这两个路径都允许您有有用的错误,例如让您知道西装/面部是否丢失/无效的枚举。 () 的错误类型对消费者来说是无用的。

您还可以定义 Suit::from_charFace::from_char 并且不泄露数组的实现。

综合起来:

impl Suit {
    fn from_char(c: char) -> Option<Self> {
        use Suit::*;

        [('c', C), ('d', D), ('h', H), ('s', S)]
            .iter()
            .cloned()
            .find(|&(cc, _)| cc == c)
            .map(|(_, s)| s)
    }
}

enum Error {
    MissingFace,
    MissingSuit,
    InvalidFace,
    InvalidSuit,
}

impl FromStr for Card {
    type Err = Error;

    fn from_str(x: &str) -> Result<Self, Self::Err> {
        use Error::*;

        let mut xs = x.chars();

        let face = xs.next().ok_or(MissingFace)?;
        let face = Face::from_char(face).ok_or(InvalidFace)?;
        let suit = xs.next().ok_or(MissingSuit)?;
        let suit = Suit::from_char(suit).ok_or(InvalidSuit)?;

        Ok(Card { face, suit })
    }
}

fn join<T>(x: Option<Option<T>>) -> Option<T>

这是x.and_then(|y| y)

fn bind<A, B, F>(x: Option<A>, f: F) -> Option<B>
where
    F: FnOnce(A) -> Option<B>,

这是x.and_then(f)

fn chain<A, B, C, F, G>(x: Option<A>, f: F, g: G) -> Option<C>
where
    F: FnOnce(A) -> Option<B>,
    G: FnOnce(B) -> Option<C>,

这是x.and_then(f).and_then(g)

另见:

关于rust - 在 Rust 中处理多个 `Option<T>` 的惯用方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50731439/

相关文章:

rust - Cloudflare Workers 中的内存不足 WebAssembly 实例化

具有可为空选项字段的 Rust 结构

java - jackson 反序列化可选抛出 NoSuchFieldError

haskell - mapM 如何与 Haskell 中的 const 函数一起工作?

haskell - Haskell 中的所有 Monad 实例是否只是从 Hask 映射到 Hask 的不同方式?

rust - 在 Rust 中,如何在 BigInt 上使用已实现的特征 FromStr?

rust - 如何使用功能标志运行 cargo

hashmap - 如何遍历Hashmap,打印键/值并删除Rust中的值?

ios - 无法使用类型为 '[Int : [String]]' 的索引下标类型为 'String!' 的值

haskell - 我怎样才能继续实现这个monad转换器?