#3 way OR comparison in typenum

78 messages · Page 1 of 1 (latest)

winter coral
#

I am using typenum for a (for fun) project after converting from generic_const_exprs.
I need to be able to specify that two generics are either equal or at least one of them is equal to 1.

I used to do this with ```rust

const fn compatible(a: usize, b: usize) -> bool {
a == 1 || b == 1 || a == b
}

struct Assert<bool>;

trait True;
trait False;

impl True for Assert<true>;
impl False for Assert<false>;

impl<const A: usize, const B: usize> MyTrait<MyStruct<B>> for MyStruct<A>
where
Assert<{compatible(A, B)}>: True
{
...
}```

I am unsure how to implement this now that I have converted from const generics to typenum.

#

I looked at the source of typenum as they have a IsLessOrEqual trait.


pub trait IsLessOrEqual<Rhs = Self> {
    /// The type representing either `True` or `False`
    type Output: Bit;
}

impl<A, B> IsLessOrEqual<B> for A
where
    A: Cmp<B> + IsLessOrEqualPrivate<B, Compare<A, B>>,
{
    type Output = <A as IsLessOrEqualPrivate<B, Compare<A, B>>>::Output;
}

pub trait IsLessOrEqualPrivate<Rhs, Cmp> {
    type Output: Bit;

    fn is_less_or_equal_private(self, _: Rhs, _: Cmp) -> Self::Output;
}

impl<A, B> IsLessOrEqualPrivate<B, Less> for A {
    type Output = True;

    #[inline]
    fn is_less_or_equal_private(self, _: B, _: Less) -> Self::Output {
        B1
    }
}
impl<A, B> IsLessOrEqualPrivate<B, Equal> for A {
    type Output = True;

    #[inline]
    fn is_less_or_equal_private(self, _: B, _: Equal) -> Self::Output {
        B1
    }
}
impl<A, B> IsLessOrEqualPrivate<B, Greater> for A {
    type Output = False;

    #[inline]
    fn is_less_or_equal_private(self, _: B, _: Greater) -> Self::Output {
        B0
    }
}
#

This looked promising however when i tried to extend this principle to a 3-way OR comparison as follows I had an issue with confilicting trait impls.

use typenum::*;

pub trait EqualOrEitherOne<Rhs = Self> {
    type Output: Bit;
}

impl<A, B> EqualOrEitherOne<B> for A
where
    A: Cmp<B> + EqualOrEitherOneInner<B, Compare<A, B>>,
{
    type Output = <A as EqualOrEitherOneInner<B, Equal>>::Output;
}

pub trait EqualOrEitherOneInner<Rhs, Cmp> {
    type Output: Bit;
}

// Self == 1
impl<A, B> EqualOrEitherOneInner<A, B> for U1 {
    type Output = True;
}

// Rhs == 1
impl<A, B> EqualOrEitherOneInner<U1, B> for A {
    type Output = True;
}

// Self == Rhs
impl<A, B> EqualOrEitherOneInner<B, Equal> for A {
    type Output = True;
}```
#

I am unsure how to proceed from here.

#

@hallow swift I have moved from the previous question to here as I felt this deserved it's own question

winter coral
#

I'm starting to think this may not be possible

edgy pawn
#

Define a trait Or<X: Bit>: Bit that turns two bits into its logical or. Define it for all four combinations of true and false. Then, apply this trait twice to three equals check

#

Actually, you probably don't need to implement an Or yourself. The bit types already implement BitOr

quasi aspen
#

^

#

@winter coral Or<Or<A, B>, C>

edgy pawn
#

Oh yeah, use that.

winter coral
#

I think I need to go to bed this isn't making any sense

#

I looked at Or from typenum but that seems to be a type

#

So i am unsure how use it as a trait bound

#

IE```rust

impl<T, A, B> MyTrait for T
where
A: ArrayLength,
B: ArrayLength,
(SOME TRAIT COND STATING A == B || A == 1 || B == 1)
{
...
}```

winter coral
#

Or<<A as IsEqual<B>>::Output, Or<<A as IsEqual<U1>>::Output, <B as IsEqual<U1>>::Output>> this is type not a trait sadly

quasi aspen
#

Remember that you can specify associated types in a trait bound, and IsEqual has an associated type Output with the result

winter coral
#

Just to confirm , when you say Or<Or<A, B>, C> do you mean Or<Or<<A as IsEqual<B>>::Output, <A as IsEqual<U1>>::Output>, <B as IsEqual<U1>>::Output>?

#

If so I am getting too many errors to post here

#

@quasi aspen This is what I am trying ```rust
use generic_array::ArrayLength;
use typenum::*;

struct MyStruct<A, B>
where
A: ArrayLength,
B: ArrayLength,
Or<Or<<A as IsEqual<B>>::Output, <A as IsEqual<U1>>::Output>, <B as IsEqual<U1>>::Output>:
IsEqual<B1, Output = B1>,
{
a: A,
b: B,
}```

#

As a minimal example

edgy pawn
#

Or<Or<Eq<A, B>, Eq<A, U1>>, Eq<B, U1>>: NonZero

winter coral
#

Hmm it seems that ArrayLength doesn't implement a lot of the traits from typenum

#
use generic_array::ArrayLength;
use typenum::*;

struct MyStruct<A, B>
where
    A: ArrayLength,
    B: ArrayLength,
    Or<Eq<A, B>, Or<Eq<A, U1>, Eq<B, U1>>>: NonZero,
{
    a: A,
    b: B,
}```
#

Gives:```rust
error[E0277]: the trait bound A: Cmp<B> is not satisfied
--> src/type_cmp.rs:4:1
|
4 | struct MyStruct<A, B>
| ^^^^^^^^^^^^^^^^^^^^^ the trait Cmp<B> is not implemented for A, which is required by A: IsEqual<B>
|
= note: required for A to implement IsEqual<B>
help: consider further restricting this bound
|
6 | A: ArrayLength + typenum::Cmp<B>,
| +++++++++++++++++

error[E0277]: the trait bound A: Cmp<UInt<UTerm, B1>> is not satisfied
--> src/type_cmp.rs:4:1
|
4 | struct MyStruct<A, B>
| ^^^^^^^^^^^^^^^^^^^^^ the trait Cmp<UInt<UTerm, B1>> is not implemented for A, which is required by A: IsEqual<UInt<UTerm, B1>>
|
= note: required for A to implement IsEqual<UInt<UTerm, B1>>
help: consider further restricting this bound
|
6 | A: ArrayLength + typenum::Cmp<typenum::UInt<typenum::UTerm, typenum::B1>>,
| ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

error[E0277]: the trait bound B: Cmp<UInt<UTerm, B1>> is not satisfied
--> src/type_cmp.rs:4:1
|
4 | struct MyStruct<A, B>
| ^^^^^^^^^^^^^^^^^^^^^ the trait Cmp<UInt<UTerm, B1>> is not implemented for B, which is required by B: IsEqual<UInt<UTerm, B1>>
|
= note: required for B to implement IsEqual<UInt<UTerm, B1>>
help: consider further restricting this bound
|
7 | B: ArrayLength + typenum::Cmp<typenum::UInt<typenum::UTerm, typenum::B1>>,
| ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

...```

winter coral
#

And adding these bounds seems to reveal more didden traits from typenum::private that need to be implemented

edgy pawn
#

You probably just want A: Unsigned, B: Unsigned

#

idk

#

Actually you should probably just add A: IsEqual<B>

#

And keep adding a huge pile of similar stuff until it works 🙃

#

A: IsEqual<B> confusingly means A and B are comparable.

winter coral
#

Ok, starting from rust struct MyStruct<A, B> where A: Unsigned + IsEqual<B>, B: Unsigned + IsEqual<A>, Eq<A, B>: NonZero, { a: A, b: B, }

#

This works

edgy pawn
#

You don't actually need Unsigned, I believe.

#

You only need A: IsEqual<B>, and not the other way around.

winter coral
#

Ok

#

Can confirm this works rust struct MyStruct<A, B> where A: IsEqual<B>, Eq<A, B>: NonZero, { a: A, b: B, }

#

Changing it to Or<Eq<A, B>, Eq<A, U1>>: NonZero, Gives a: ```rust
error[E0277]: no implementation for <A as typenum::IsEqual<B>>::Output | _
--> src/type_cmp.rs:3:1
|
3 | / struct MyStruct<A, B>
4 | | where
5 | | A: IsEqual<B>,
6 | | Or<Eq<A, B>, Eq<A, U1>>: NonZero,
... |
9 | | b: B,
10 | | }
| |_^ no implementation for <A as typenum::IsEqual<B>>::Output | _
|
= help: the trait std::ops::BitOr<_> is not implemented for `<A as typenum::IsEqual<B>>::Output````

#

Clearly I need IsEqual<A, U1> So I add that

#

Gives: ```rust
error[E0277]: no implementation for <A as typenum::IsEqual<B>>::Output | <A as typenum::IsEqual<typenum::UInt<typenum::UTerm, typenum::B1>>>::Output
--> src/type_cmp.rs:3:1
|
3 | / struct MyStruct<A, B>
4 | | where
5 | | A: IsEqual<B> + IsEqual<U1>,
6 | | Or<Eq<A, B>, Eq<A, U1>>: NonZero,
... |
9 | | b: B,
10 | | }
| |_^ no implementation for <A as typenum::IsEqual<B>>::Output | <A as typenum::IsEqual<typenum::UInt<typenum::UTerm, typenum::B1>>>::Output
|
= help: the trait std::ops::BitOr<<A as typenum::IsEqual<typenum::UInt<typenum::UTerm, typenum::B1>>>::Output> is not implemented for <A as typenum::IsEqual<B>>::Output
help: consider extending the where clause, but there might be an alternative better way to express this requirement
|
6 | Or<Eq<A, B>, Eq<A, U1>>: NonZero, <A as typenum::IsEqual<B>>::Output: std::ops::BitOr<<A as typenum::IsEqual<typenum::UInt<typenum::UTerm, typenum::B1>>>::Output>
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

#

Which I can fix woth the addition of this line Eq<A, B>: BitOr<Eq<A, U1>>,

#

Changeing the bound to Or<Eq<A, B>, Or<Eq<A, U1>, Eq<B, U1>>> Noe gives another big error

#

Which I can fix with rust struct MyStruct<A, B> where A: IsEqual<B> + IsEqual<U1>, B: IsEqual<U1>, Eq<A, U1>: BitOr<Eq<B, U1>>, Eq<A, B>: BitOr<Or<Eq<A, U1>, Eq<B, U1>>>, Or<Eq<A, B>, Or<Eq<A, U1>, Eq<B, U1>>>: NonZero, { a: A, b: B, }

quasi aspen
#

Your where list is slowly becoming ever more cursed

#

I love it

winter coral
#

Yep

#

But it works now

#
use std::ops::BitOr;

use typenum::*;

pub trait EqualOrEitherOne<Rhs = Self> {
    type Output: Bit;
}

impl<A, B> EqualOrEitherOne<B> for A
where
    A: IsEqual<B> + IsEqual<U1>,
    B: IsEqual<U1>,
    Eq<A, U1>: BitOr<Eq<B, U1>>,
    Eq<A, B>: BitOr<Or<Eq<A, U1>, Eq<B, U1>>>,
    Or<Eq<A, B>, Or<Eq<A, U1>, Eq<B, U1>>>: NonZero,
{
    type Output = True;
}

type Compat<A, B> = <A as EqualOrEitherOne<B>>::Output;```
quasi aspen
#

Nice :D

winter coral
#

And no the impl of my trait looks like this: ```rust
impl<T, U, V, A, B> Broadcast<Array1<U, B>, T, U, V> for Array1<T, A>
where
A: ArrayLength + Max<B>,
B: ArrayLength,
Compat<A, B>: NonZero,
<A as Max<B>>::Output: ArrayLength,
{
type Output = Array1<V, <A as Max<B>>::Output>;

...

}```

#

Looks like I will need 2 lines of where clauses per dimension

#

So an Array3 with an Array3 will need lines of where clauses

quasi aspen
#

rip

#

Honestly at this point I suggest a macro

winter coral
#

Yeah I was thinking that

#

I'm going to see how much I can stuff inside traits

#

Did you mean a declarative macro or proc macro?

#

Weirdly enough I have written 0 declarative macros but > 10 proc macros

winter coral
#

I would also expect this to work but it doesn't ```rust
trait Compatible<Rhs> {
type Output;
}

impl<A, B> Compatible<B> for A
where
A: ArrayLength + EqualOrEitherOne<B, Output = True> + Max<B>,
B: ArrayLength,
<A as Max<B>>::Output: ArrayLength,
{
type Output = True;
}

impl<T, U, V, A, B> Broadcast<Array1<U, B>, T, U, V> for Array1<T, A>
where
A: Compatible<B, Output = True>,
{
...
}

#

As I get errors like: ```rust
the trait ArrayLength is not implemented for B

#

Oh, you can't use : in declarative macros rust error: macro expansion ignores token `:` and any following

edgy pawn
#

Have you seen the ndarray crate? Maybe check out how they do it.

quasi aspen
strange sluice
#

I don't remember if it says that some of the input, starting at :, has not been used by the macro, or if it says that the macro expansion is only correct up to a : and then it's wrong

#

I think it's the latter?

winter coral
#
#[macro_export]
macro_rules! compatible {
    ($a:ty, $b:ty) => {
        $a : ArrayLength + EqualOrEitherOne<$b, Output = True> + Max<$b>,
        $b : ArrayLength,
        <$a as Max<$b>>::Output: ArrayLength,
    };
}```
winter coral
#

Any idea what is wrong with this? @quasi aspen

strange sluice
#

I vaguely remember they're not

winter coral
#

That would suck

#

Any idea if proc macros would work here? @strange sluice

strange sluice
winter coral
#

I think ill write a proc macro that does the whole thing then