Skip to content

Commit

Permalink
A number of changes around behavior_traits and type_traits::Unit
Browse files Browse the repository at this point in the history
Merged unit_conversion traits into the convert module.
Made type traits depend on Try* traits (ToScalar, etc) instead of the non-Try ones.
Added ConversionError enum.
Made IsCommensurableWith generic over the value type it's commensurable with
  • Loading branch information
turboladen committed Feb 2, 2024
1 parent cef64bb commit 0580827
Show file tree
Hide file tree
Showing 23 changed files with 257 additions and 211 deletions.
2 changes: 1 addition & 1 deletion api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ publish = ["agrian-registry"]
[dependencies]
approx.workspace = true
ffi_common = { workspace = true, optional = true }
lazy_static.workspace = true
num-traits = "0.2.16"
pest = "^2.1"
pest_derive = "^2.1"
Expand All @@ -20,7 +21,6 @@ thiserror = "1.0"
[dev-dependencies]
bincode = "1.3"
criterion = "0.5"
lazy_static.workspace = true
rmp-serde = "1.0"
serde_json = "1.0"

Expand Down
1 change: 0 additions & 1 deletion api/src/measurement/v2.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
mod convert;
mod ops;
mod ucum;
mod unit_conversion;

use crate::{v2::type_traits, Measurement};

Expand Down
27 changes: 26 additions & 1 deletion api/src/measurement/v2/convert.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{v2::behavior_traits::convert, Measurement};
use crate::{v2::behavior_traits::convert, Measurement, Unit};

impl convert::Invert for Measurement {
fn invert(&mut self) {
Expand Down Expand Up @@ -56,3 +56,28 @@ impl convert::ToMagnitude<f64> for Measurement {
crate::UcumUnit::magnitude(self)
}
}

impl<'a> convert::TryConvertTo<'a, Unit> for Measurement {
type Error = crate::Error;

fn try_convert_to(&'a self, rhs: &'a Unit) -> Result<Self, Self::Error> {
// Just delegate to the old trait impl for now.
crate::Convertible::convert_to(self, rhs)
}
}

impl<'a> convert::TryConvertTo<'a, str> for Measurement {
type Error = crate::Error;

fn try_convert_to(&'a self, rhs: &'a str) -> Result<Self, Self::Error> {
// Just delegate to the old trait impl for now.
crate::Convertible::convert_to(self, rhs)
}
}

impl convert::ToReduced for Measurement {
fn to_reduced(&self) -> Self {
// Just delegate to the old trait impl for now.
crate::reduce::ToReduced::to_reduced(self).unwrap()
}
}
2 changes: 1 addition & 1 deletion api/src/measurement/v2/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::{
};

use crate::{
v2::behavior_traits::{ops, unit_conversion::TryConvertTo},
v2::behavior_traits::{convert::TryConvertTo, ops},
Measurement,
};

Expand Down
26 changes: 0 additions & 26 deletions api/src/measurement/v2/unit_conversion.rs

This file was deleted.

2 changes: 0 additions & 2 deletions api/src/parser/prefix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ pub mod ucum_symbol;
#[cfg(feature = "v2")]
mod v2;

pub use self::ucum_symbol::*;

use super::{Error, Visit};
use crate::{
parser::{symbols::symbol_parser::Rule, ucum_symbol::UcumSymbol},
Expand Down
11 changes: 9 additions & 2 deletions api/src/parser/prefix/v2.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::{
parser::definition::Definition, v2::type_traits::Prefix as TPrefix, Classification, Prefix,
UcumSymbol,
parser::definition::Definition,
v2::{behavior_traits, type_traits::Prefix as TPrefix},
Classification, Prefix, UcumSymbol,
};

impl TPrefix<f64> for Prefix {
Expand Down Expand Up @@ -38,3 +39,9 @@ impl TPrefix<f64> for Prefix {
Definition::new_non_dimensional(UcumSymbol::definition_value(self))
}
}

impl behavior_traits::convert::ToScalar<f64> for Prefix {
fn to_scalar(&self) -> f64 {
self.definition_value()
}
}
4 changes: 3 additions & 1 deletion api/src/parser/terms/v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ impl convert::ToInverse for Vec<Term> {

impl convert::ToScalar<f64> for Vec<Term> {
fn to_scalar(&self) -> f64 {
self.iter().map(convert::ToScalar::to_scalar).product()
self.iter()
.map(convert::ToScalar::<f64>::to_scalar)
.product()
}
}

Expand Down
2 changes: 1 addition & 1 deletion api/src/unit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ impl Unit {
Self::new(reduced).to_string()
}

pub fn as_str<'a>(&'a self) -> Cow<'a, str> {
pub fn as_str(&self) -> Cow<'_, str> {
match self.terms.len() {
0 => Cow::Borrowed(""),
1 => self.terms[0].as_str(),
Expand Down
1 change: 0 additions & 1 deletion api/src/unit/v2.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
mod convert;
mod ops;
mod ucum;
mod unit_conversion;

use std::borrow::Cow;

Expand Down
7 changes: 7 additions & 0 deletions api/src/unit/v2/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,10 @@ impl convert::ToMagnitude<f64> for Unit {
crate::UcumUnit::magnitude(self)
}
}

impl convert::ToReduced for Unit {
fn to_reduced(&self) -> Self {
// Just delegate to the old trait impl for now.
crate::reduce::ToReduced::to_reduced(self)
}
}
8 changes: 0 additions & 8 deletions api/src/unit/v2/unit_conversion.rs

This file was deleted.

1 change: 0 additions & 1 deletion api/src/v2/behavior_traits.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
pub mod convert;
pub mod ops;
pub mod ucum;
pub mod unit_conversion;
103 changes: 103 additions & 0 deletions api/src/v2/behavior_traits/convert.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,54 @@
use std::convert::Infallible;

#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
pub enum ConversionError<T> {
#[error(transparent)]
Unit(#[from] UnitConversionError),

#[error(transparent)]
Scalar(#[from] ScalarConversionError<T>),
}

#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
#[error("Unable to convert: {source_unit:?} to {destination_unit:?}")]
pub struct UnitConversionError {
source_unit: String,
destination_unit: String,
}

impl UnitConversionError {
pub fn new<'a, S, D>(source_unit: &'a S, destination_unit: &'a D) -> Self
where
&'a S: ToString,
&'a D: ToString,
{
Self {
source_unit: source_unit.to_string(),
destination_unit: destination_unit.to_string(),
}
}
}

#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
#[error("Unable to convert: {source_unit:?} to scalar value")]
pub struct ScalarConversionError<T> {
source_unit: String,
#[source]
source: T,
}

impl<T> ScalarConversionError<T> {
pub fn new<'a, S>(source_unit: &'a S, source: T) -> Self
where
&'a S: ToString,
{
Self {
source_unit: source_unit.to_string(),
source,
}
}
}

// NOTE: The difference with this trait is that it takes a `&mut self` instead of `self`, allowing
// it to be implemented a bit more conventionally on types: ex. `impl Invert on Term` instead of
// `impl Invert on &mut Term`.
Expand Down Expand Up @@ -43,6 +94,17 @@ pub trait TryToScalar<V> {
fn try_to_scalar(&self) -> Result<V, Self::Error>;
}

impl<T, V> TryToScalar<V> for T
where
T: ToScalar<V>,
{
type Error = Infallible;

fn try_to_scalar(&self) -> Result<V, Self::Error> {
Ok(self.to_scalar())
}
}

pub trait ToMagnitude<V> {
fn to_magnitude(&self) -> V;
}
Expand All @@ -59,6 +121,17 @@ pub trait TryToMagnitude<V> {
fn try_to_magnitude(&self) -> Result<V, Self::Error>;
}

impl<T, V> TryToMagnitude<V> for T
where
T: ToMagnitude<V>,
{
type Error = Infallible;

fn try_to_magnitude(&self) -> Result<V, Self::Error> {
Ok(self.to_magnitude())
}
}

// NOTE: This is the next version of `AsFraction`, which was incorrectly named, according to Rust
// API guidelines. The difference with this trait is that a) you can specify the output type for
// the `to_fraction()` call, letting wrapper crates use this trait (since other types may not
Expand All @@ -70,3 +143,33 @@ pub trait ToFraction<N = Option<Self>, D = Option<Self>, F = (N, D)> {
fn numerator(&self) -> N;
fn denominator(&self) -> D;
}

/// Trait for infallible conversion.
///
// NOTE: The difference with this trait is that it doesn't require the output to be a `Result` like
// the original does. This allows for implementing for types that can guarantee a conversion.
#[allow(clippy::module_name_repetitions)]
pub trait ConvertTo<U: ?Sized, O = Self> {
/// _The_ method for doing the conversion.
///
fn convert_to(&self, rhs: &U) -> O;
}

pub trait TryConvertTo<'a, U: ?Sized, O = Self>: Sized {
type Error;

/// _The_ method for doing the conversion.
///
/// # Errors
///
/// This should fail if `self` couldn't be converted to `O`.
///
fn try_convert_to(&'a self, rhs: &'a U) -> Result<O, Self::Error>;
}

// NOTE: The difference with this trait is that it's generic over `T`, allowing
// for multiple implementations.
//
pub trait ToReduced<T = Self> {
fn to_reduced(&self) -> T;
}
40 changes: 32 additions & 8 deletions api/src/v2/behavior_traits/ops.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::cmp::Ordering;

use super::{convert::ToScalar, ucum::Dimensionable};
use super::{convert::TryToScalar, ucum::Dimensionable};

/// Trait for determining if two things are dimensionally equal. This is a required check for many
/// operations, such as adding, subtracting, and commensurability checking.
/// operations, such as adding, subtracting, and commensurability checking. Really, any type that
/// will be involved in comparing for commensurability should implement this.
///
pub trait DimEq<Rhs = Self> {
fn dim_eq(&self, rhs: &Rhs) -> bool;
Expand All @@ -21,6 +22,18 @@ where

/// Trait to determine if two, typically `Measurement`s are the same quantity.
///
/// Note that while the implementation for most cases should involve:
/// 1. Checking if compared items are in the same dimension; if not, they cannot
/// be compared.
/// 2. Checking if the scalar values of compared items are equal; if so, the
/// items are commensurable.
///
/// We explicitly don't provide generic implementations for any value type `V`,
/// since the manner of checking the equality of `V` can differ depending on `V`.
/// For example, we _do_ provide an implementation for `V` as `f64`, and in that
/// case, we use the [Unit in the last place (ULP)](https://en.wikipedia.org/wiki/Unit_in_the_last_place)
/// method for comparison. Comparing other value types may merit other methods.
///
/// ```
/// use wise_units::Measurement;
///
Expand All @@ -33,37 +46,48 @@ where
/// assert!(!one_km.is_commensurable_with(&two_km).unwrap());
///
/// ```
pub trait IsCommensurableWith<'a, Rhs = Self>: DimEq<Rhs> {
pub trait IsCommensurableWith<'a, V, Rhs = Self>: DimEq<Rhs> {
fn is_commensurable_with(&'a self, rhs: &'a Rhs) -> Option<bool>;
}

impl<'a, T> IsCommensurableWith<'a> for T
impl<'a, T> IsCommensurableWith<'a, f64> for T
where
T: Dimensionable + ToScalar<f64> + 'a,
T: Dimensionable + TryToScalar<f64> + 'a,
{
fn is_commensurable_with(&'a self, rhs: &'a Self) -> Option<bool> {
if !self.dim_eq(rhs) {
return None;
}

Some(approx::ulps_eq!(&self.to_scalar(), &rhs.to_scalar()))
match (self.try_to_scalar(), rhs.try_to_scalar()) {
(Ok(lhs), Ok(rhs)) => Some(approx::ulps_eq!(lhs, rhs)),
(_, _) => None,
}
}
}

/// This trait exists to allow for `PartialOrd` implementations that check for
/// actual object equality, not semantic equality, which thus allows for proper
/// behavior of `Hash` (and thus, say, `HashMap`) for those objects. We can then
/// determine ordering for commensurable things using this trait's implementation.
///
pub trait CommensurableOrd<Rhs = Self>: DimEq<Rhs> {
fn commensurable_ord(&self, rhs: &Rhs) -> Option<Ordering>;
}

impl<T> CommensurableOrd for T
where
T: Dimensionable + ToScalar<f64>,
T: Dimensionable + TryToScalar<f64>,
{
fn commensurable_ord(&self, rhs: &Self) -> Option<Ordering> {
if !self.dim_eq(rhs) {
return None;
}

self.to_scalar().partial_cmp(&rhs.to_scalar())
match (self.try_to_scalar(), rhs.try_to_scalar()) {
(Ok(lhs), Ok(ref rhs)) => lhs.partial_cmp(rhs),
(_, _) => None,
}
}
}

Expand Down
Loading

0 comments on commit 0580827

Please sign in to comment.