代码之家  ›  专栏  ›  技术社区  ›  qwe

在rust中处理多个选项的惯用方法是什么?

  •  4
  • qwe  · 技术社区  · 6 年前

    因为我对rust还不太熟悉,所以我需要指导如何习惯性地处理错误。我觉得处理样板文件的错误很烦人。

    我有很多问题 Option<T> 太冗长了,无法处理每一个 None 手动操作。

    例如,在haskell中,可以链接可选值( Maybe )使用多种操作员操作: fmap , <*> , >>= 等:

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

    同样的东西在铁锈中看起来是不可能的。我试图将一个两个字符的卡片字符串解析为一个结构 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
    

    多亏了链子 >>= 哈斯克尔使它成为可能(而且很简单!)操纵单子的内部值。为了达到这个目标,我不得不写 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)
    }
    
    4 回复  |  直到 6 年前
        1
  •  10
  •   Shepmaster Lukas Kalbertodt    6 年前

    如前所述, Option Result 它们的实用方法。另外,try接线员( ? )也可用于极为常见的“返回故障或展开结果”的情况

    我会实施 FromStr 对于 Face Suit . 然后,您的代码将如下所示:

    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 })
        }
    }
    

    如果您没有/不能,可以使用 选择权 . 你没有定义 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_char Face::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)

    参见:

        2
  •  6
  •   duplode    6 年前

    好像你想要 Option::and_then :

    pub fn and_then<U, F>(self, f: F) -> Option<U> 
    where
        F: FnOnce(T) -> Option<U>
    

    实例:

    fn sq(x: u32) -> Option<u32> { Some(x * x) }
    fn nope(_: u32) -> Option<u32> { None }
    
    assert_eq!(Some(2).and_then(sq).and_then(sq), Some(16));
    assert_eq!(Some(2).and_then(sq).and_then(nope), None);
    assert_eq!(Some(2).and_then(nope).and_then(sq), None);
    assert_eq!(None.and_then(sq).and_then(sq), None);
    
        3
  •  4
  •   Jmb    6 年前

    除了其他答案外,您还可以查看一元表达式板条箱,如 mdo map_for . 例如 map_for :

    fn from_str(x: &str) -> Result<Self, Self::Err> {
        let mut xs = x.chars();
        map_for!{
            ax <- xs.next();
            f  <- FACES.find(ax);
            a  <- Face::from_usize(f);
            bx <- xs.next();
            s  <- SUITS.find(bx);
            b  <- Suit::from_usize (s);
            => Card::new(a, b) }
        .ok_or(Err(()))
    }
    

    完全公开:我是 地图符号 机箱。

        4
  •  0
  •   duplode    6 年前

    Maybe -铁锈中的一元链结 Result 是由 the try! macro . 应该看起来像

    fn from_str(x: &str) -> Result<Self, Self::Err> {
        let mut xs = x.chars();
        let a = try!(chain(xs.next(), |x| FACES.find(x), Face::from_usize));
        let b = try!(chain(xs.next(), |x| SUITS.find(x), Suit::from_usize));
        Ok(Card::new(face, suit))
    }
    
    推荐文章