由于我对Rust还比较新,所以需要指导如何按惯例处理错误。我觉得处理错误的样板代码非常烦人。
我被多个Option<T>
困扰。手动处理每种None
情况太冗长。
例如,在Haskell中,您可以使用各种运算符(例如fmap
、<*>
、>>=
)链接可选值(Maybe
)操作:
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 通过 >>=
进行链接,使得操作单子的内部值成为可能(而且很容易!)。为了实现接近这样的功能,我不得不编写 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)
}
?
已经稳定下来了,我认为try!
的时代已经过去了。let a = chain(xs.next(), |x| FACES.find(x), Face::from_usize)?;
看起来更好一些。 - Alectry!
的语法糖吗? - leftaroundaboutTry
trait支持,所以它更灵活。例如,它适用于Option
,并且当它稳定后,它将可用于用户类型。 - Shepmaster