From 6744e5f78cda23e7a80ec65640015b6fd0081193 Mon Sep 17 00:00:00 2001 From: Julio <30329843+julio4@users.noreply.github.com> Date: Thu, 26 Dec 2024 20:05:10 +0100 Subject: [PATCH 1/2] feat(corelib): OptionTrait::ok_or_else (#6930) --- corelib/src/option.cairo | 250 ++++++++++++++++++++++++++++- corelib/src/test/option_test.cairo | 117 ++++++++++++-- 2 files changed, 350 insertions(+), 17 deletions(-) diff --git a/corelib/src/option.cairo b/corelib/src/option.cairo index 5ca3532bb29..63ef5762491 100644 --- a/corelib/src/option.cairo +++ b/corelib/src/option.cairo @@ -116,11 +116,14 @@ //! //! * [`ok_or`] transforms [`Some(v)`] to [`Ok(v)`], and [`None`] to //! [`Err(err)`] using the provided default `err` value. +//! * [`ok_or_else`] transforms [`Some(v)`] to [`Ok(v)`], and [`None`] to +//! a value of [`Err`] using the provided function //! //! [`Err(err)`]: Result::Err //! [`Ok(v)`]: Result::Ok //! [`Some(v)`]: Option::Some //! [`ok_or`]: OptionTrait::ok_or +//! [`ok_or_else`]: OptionTrait::ok_or_else //! //! These methods transform the [`Some`] variant: //! @@ -129,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] @@ -191,12 +240,152 @@ pub trait OptionTrait { /// # Examples /// /// ``` - /// let option = Option::Some(123); - /// let result = option.ok_or('no value'); - /// assert!(result.unwrap() == 123); + /// assert_eq!(Option::Some('foo').ok_or(0), Result::Ok('foo')); + /// + /// let option: Option = Option::None; + /// assert_eq!(option.ok_or(0), Result::Err(0)); /// ``` fn ok_or>(self: Option, err: E) -> Result; + /// Transforms the `Option` into a `Result`, mapping `Option::Some(v)` to + /// `Result::Ok(v)` and `Option::None` to `Result::Err(err())`. + /// + /// # Examples + /// + /// ``` + /// assert_eq!(Option::Some('foo').ok_or_else(|| 0), Result::Ok('foo')); + /// + /// let option: Option = Option::None; + /// assert_eq!(option.ok_or_else(|| 0), Result::Err(0)); + /// ``` + fn ok_or_else, +core::ops::FnOnce[Output: E], +Drop>( + 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 @@ -341,6 +530,61 @@ pub impl OptionTraitImpl of OptionTrait { } } + #[inline] + fn ok_or_else, +core::ops::FnOnce[Output: E], +Drop>( + self: Option, err: F, + ) -> Result { + match self { + Option::Some(v) => Result::Ok(v), + Option::None => Result::Err(err()), + } + } + + #[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 46aa096465c..8b06969997c 100644 --- a/corelib/src/test/option_test.cairo +++ b/corelib/src/test/option_test.cairo @@ -83,6 +83,109 @@ fn test_option_none_is_none() { } #[test] +fn test_option_some_ok_or() { + assert_eq!(Option::Some('foo').ok_or(0), Result::Ok('foo')); +} + +#[test] +fn test_option_none_ok_or() { + let option: Option = Option::None; + assert_eq!(option.ok_or(0), Result::Err(0)); +} + +#[test] +fn test_option_some_ok_or_else() { + assert_eq!(Option::Some('foo').ok_or_else( || 0), Result::Ok('foo')); +} + +#[test] +fn test_option_none_ok_or_else() { + let option: Option = Option::None; + 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); @@ -115,17 +218,3 @@ fn test_option_none_map() { let x: Option = Option::None; assert!(x.map(|s: ByteArray| s.len()) == Option::None); } - -#[test] -fn test_option_some_ok_or() { - let x = Option::Some(123); - let result = x.ok_or('no value'); - assert!(result == Result::Ok(123)); -} - -#[test] -fn test_option_none_ok_or() { - let x: Option = Option::None; - let result = x.ok_or('no value'); - assert!(result == Result::Err('no value')); -} From 359dee9f099e56174429106510c902768c3364d4 Mon Sep 17 00:00:00 2001 From: julio4 <30329843+julio4@users.noreply.github.com> Date: Thu, 26 Dec 2024 21:56:35 +0100 Subject: [PATCH 2/2] feat(corelib): OptionTrait boolean operators