diff --git a/corelib/src/option.cairo b/corelib/src/option.cairo index 0953626cac5..63ef5762491 100644 --- a/corelib/src/option.cairo +++ b/corelib/src/option.cairo @@ -132,6 +132,52 @@ //! [`None`] values unchanged //! //! [`map`]: OptionTrait::map +//! +//! ## Boolean operators +//! +//! These methods treat the [`Option`] as a boolean value, where [`Some`] +//! acts like [`true`] and [`None`] acts like [`false`]. There are two +//! categories of these methods: ones that take an [`Option`] as input, and +//! ones that take a function as input (to be lazily evaluated). +//! +//! The [`and`], [`or`], and [`xor`] methods take another [`Option`] as +//! input, and produce an [`Option`] as output. Only the [`and`] method can +//! produce an [`Option`] value having a different inner type `U` than +//! [`Option`]. +//! +//! | method | self | input | output | +//! |---------|-----------|-----------|-----------| +//! | [`and`] | `None` | (ignored) | `None` | +//! | [`and`] | `Some(x)` | `None` | `None` | +//! | [`and`] | `Some(x)` | `Some(y)` | `Some(y)` | +//! | [`or`] | `None` | `None` | `None` | +//! | [`or`] | `None` | `Some(y)` | `Some(y)` | +//! | [`or`] | `Some(x)` | (ignored) | `Some(x)` | +//! | [`xor`] | `None` | `None` | `None` | +//! | [`xor`] | `None` | `Some(y)` | `Some(y)` | +//! | [`xor`] | `Some(x)` | `None` | `Some(x)` | +//! | [`xor`] | `Some(x)` | `Some(y)` | `None` | +//! +//! [`and`]: OptionTrait::and +//! [`or`]: OptionTrait::or +//! [`xor`]: OptionTrait::xor +//! +//! The [`and_then`] and [`or_else`] methods take a function as input, and +//! only evaluate the function when they need to produce a new value. Only +//! the [`and_then`] method can produce an [`Option`] value having a +//! different inner type `U` than [`Option`]. +//! +//! | method | self | function input | function result | output | +//! |--------------|-----------|----------------|-----------------|-----------| +//! | [`and_then`] | `None` | (not provided) | (not evaluated) | `None` | +//! | [`and_then`] | `Some(x)` | `x` | `None` | `None` | +//! | [`and_then`] | `Some(x)` | `x` | `Some(y)` | `Some(y)` | +//! | [`or_else`] | `None` | (not provided) | `None` | `None` | +//! | [`or_else`] | `None` | (not provided) | `Some(y)` | `Some(y)` | +//! | [`or_else`] | `Some(x)` | (not provided) | (not evaluated) | `Some(x)` | +//! +//! [`and_then`]: OptionTrait::and_then +//! [`or_else`]: OptionTrait::or_else /// The `Option` enum representing either `Some(value)` or `None`. #[must_use] @@ -216,6 +262,130 @@ pub trait OptionTrait { self: Option, err: F, ) -> Result; + /// Returns [`None`] if the option is [`None`], otherwise returns `optb`. + /// + /// Arguments passed to `and` are eagerly evaluated; if you are passing the + /// result of a function call, it is recommended to use [`and_then`], which is + /// lazily evaluated. + /// + /// [`and_then`]: OptionTrait::and_then + /// + /// # Examples + /// + /// ``` + /// let x = Option::Some(2); + /// let y: Option = Option::None; + /// assert_eq!(x.and(y), Option::None); + /// + /// let x: Option = Option::None; + /// let y: Option = Option::Some("foo"); + /// assert_eq!(x.and(y), Option::None); + /// + /// let x = Option::Some(2); + /// let y: Option = Option::Some("foo"); + /// assert_eq!(x.and(y), Option::Some("foo")); + /// + /// let x: Option = Option::None; + /// let y: Option = Option::None; + /// assert_eq!(x.and(y), Option::None); + /// ``` + fn and, +Drop>(self: Option, optb: Option) -> Option; + + /// Returns [`None`] if the option is [`None`], otherwise calls `f` with the + /// wrapped value and returns the result. + /// + /// Some languages call this operation flatmap. + /// + /// # Examples + /// + /// ``` + /// use core::num::traits::CheckedMul; + /// + /// let option: Option = checked_mul(2_u32, 2_u32) + /// .and_then(|v| Option::Some(format!("{}", v))); + /// assert_eq!(option, Option::Some("4")); + /// + /// let option: Option = checked_mul(65536_u32, 65536_u32) + /// .and_then(|v| Option::Some(format!("{}", v))); + /// assert_eq!(option, Option::None); // overflowed! + /// + /// let option: Option = Option::::None + /// .and_then(|v| Option::Some(format!("{}", v))); + /// assert_eq!(option, Option::None); + /// ``` + fn and_then, +core::ops::FnOnce[Output: Option]>( + self: Option, f: F, + ) -> Option; + + /// Returns the option if it contains a value, otherwise returns `optb`. + /// + /// Arguments passed to `or` are eagerly evaluated; if you are passing the + /// result of a function call, it is recommended to use [`or_else`], which is + /// lazily evaluated. + /// + /// [`or_else`]: OptionTrait::or_else + /// + /// # Examples + /// + /// ``` + /// let x = Option::Some(2); + /// let y = Option::None; + /// assert_eq!(x.or(y), Option::Some(2)); + /// + /// let x = Option::None; + /// let y = Option::Some(100); + /// assert_eq!(x.or(y), Option::Some(100)); + /// + /// let x = Option::Some(2); + /// let y = Option::Some(100); + /// assert_eq!(x.or(y), Option::Some(2)); + /// + /// let x: Option = Option::None; + /// let y = Option::None; + /// assert_eq!(x.or(y), Option::None); + /// ``` + fn or<+Drop>(self: Option, optb: Option) -> Option; + + /// Returns the option if it contains a value, otherwise calls `f` and + /// returns the result. + /// + /// # Examples + /// + /// ``` + /// let nobody = || Option::::None; + /// let vikings = || Option::::Some("vikings"); + /// + /// assert_eq!(Option::Some("barbarians").or_else(vikings), Option::Some("barbarians")); + /// assert_eq!(Option::None.or_else(vikings), Option::Some("vikings")); + /// assert_eq!(Option::None.or_else(nobody), Option::None); + /// ``` + fn or_else, +core::ops::FnOnce[Output: Option]>( + self: Option, f: F, + ) -> Option; + + /// Returns [`Some`] if exactly one of `self`, `optb` is [`Some`], otherwise returns [`None`]. + /// + /// # Examples + /// + /// ``` + /// let x = Option::Some(2); + /// let y: Option = Option::None; + /// assert_eq!(x.xor(y), Option::Some(2)); + /// + /// let x: Option = Option::None; + /// let y = Option::Some(2); + /// assert_eq!(x.xor(y), Option::Some(2)); + /// + /// let x = Option::Some(2); + /// let y = Option::Some(2); + /// assert_eq!(x.xor(y), Option::None); + /// + /// let x: Option = Option::None; + /// let y: Option = Option::None; + /// assert_eq!(x.xor(y), Option::None); + /// ``` + fn xor<+Drop>(self: Option, optb: Option) -> Option; + /// Returns `true` if the `Option` is `Option::Some`, `false` otherwise. /// /// # Examples @@ -370,6 +540,51 @@ pub impl OptionTraitImpl of OptionTrait { } } + #[inline] + fn and, +Drop>(self: Option, optb: Option) -> Option { + match self { + Option::Some(_) => optb, + Option::None => Option::None, + } + } + + #[inline] + fn and_then, +core::ops::FnOnce[Output: Option]>( + self: Option, f: F, + ) -> Option { + match self { + Option::Some(x) => f(x), + Option::None => Option::None, + } + } + + #[inline] + fn or<+Drop>(self: Option, optb: Option) -> Option { + match self { + Option::Some(x) => Option::Some(x), + Option::None => optb, + } + } + + #[inline] + fn or_else, +core::ops::FnOnce[Output: Option]>( + self: Option, f: F, + ) -> Option { + match self { + Option::Some(x) => Option::Some(x), + Option::None => f(), + } + } + + #[inline] + fn xor<+Drop>(self: Option, optb: Option) -> Option { + match (self, optb) { + (Option::Some(x), Option::None) => Option::Some(x), + (Option::None, Option::Some(x)) => Option::Some(x), + _ => Option::None, + } + } + #[inline] fn is_some(self: @Option) -> bool { match self { diff --git a/corelib/src/test/option_test.cairo b/corelib/src/test/option_test.cairo index 5a905aa6ecb..8b06969997c 100644 --- a/corelib/src/test/option_test.cairo +++ b/corelib/src/test/option_test.cairo @@ -104,6 +104,88 @@ fn test_option_none_ok_or_else() { assert_eq!(option.ok_or_else( || 0), Result::Err(0)); } +#[test] +fn test_option_and() { + let x = Option::Some(2); + let y: Option = Option::None; + assert_eq!(x.and(y), Option::None); + + let x: Option = Option::None; + let y: Option = Option::Some("foo"); + assert_eq!(x.and(y), Option::None); + + let x = Option::Some(2); + let y: Option = Option::Some("foo"); + assert_eq!(x.and(y), Option::Some("foo")); + + let x: Option = Option::None; + let y: Option = Option::None; + assert_eq!(x.and(y), Option::None); +} + +#[test] +fn test_option_and_then() { + let checked_mul = core::num::traits::CheckedMul::checked_mul(2_u32, 2_u32); + let option: Option = checked_mul.and_then(|v| Option::Some(format!("{}", v))); + assert_eq!(option, Option::Some("4")); + + let checked_mul = core::num::traits::CheckedMul::checked_mul(65536_u32, 65536_u32); + let option: Option = checked_mul.and_then(|v| Option::Some(format!("{}", v))); + assert_eq!(option, Option::None); // overflowed! + + let option: Option = Option::::None + .and_then(|v| Option::Some(format!("{}", v))); + assert_eq!(option, Option::None); +} + +#[test] +fn test_option_or() { + let x = Option::Some(2); + let y = Option::None; + assert_eq!(x.or(y), Option::Some(2)); + + let x = Option::None; + let y = Option::Some(100); + assert_eq!(x.or(y), Option::Some(100)); + + let x = Option::Some(2); + let y = Option::Some(100); + assert_eq!(x.or(y), Option::Some(2)); + + let x: Option = Option::None; + let y = Option::None; + assert_eq!(x.or(y), Option::None); +} + +#[test] +fn test_option_or_else() { + let nobody = || Option::::None; + let vikings = || Option::::Some("vikings"); + + assert_eq!(Option::Some("barbarians").or_else(vikings), Option::Some("barbarians")); + assert_eq!(Option::None.or_else(vikings), Option::Some("vikings")); + assert_eq!(Option::None.or_else(nobody), Option::None); +} + +#[test] +fn test_option_xor() { + let x = Option::Some(2); + let y: Option = Option::None; + assert_eq!(x.xor(y), Option::Some(2)); + + let x: Option = Option::None; + let y = Option::Some(2); + assert_eq!(x.xor(y), Option::Some(2)); + + let x = Option::Some(2); + let y = Option::Some(2); + assert_eq!(x.xor(y), Option::None); + + let x: Option = Option::None; + let y: Option = Option::None; + assert_eq!(x.xor(y), Option::None); +} + fn test_option_some_is_none_or() { assert_eq!(Option::Some(2_u8).is_none_or(|x| x > 1), true); assert_eq!(Option::Some(0_u8).is_none_or(|x| x > 1), false);