Implementing a "true" Functor in Rust

Inspired by these posts

and reading through Monads and GATs in nightly Rust (from December 2020), I wondered if there's a way to implement functors in Rust.

Michael Snoyman writes in his post on Monads and GATs:

Logically, we know that for a Option<A> implementation, we'd like Wrapped to be a Option<B> kind of thing. But the GAT implementation does not enforce it. (By contrast, the HKT approach in Haskell does enforce this.)

So how can we enforce that a functor's map function will always take a Wrapper<A> and a F: FnMut(A) -> B, and return a Wrapper<B> (and not some other type Foo, or std::io::Result<Wrapper<B>>, for example)? I think it's possible to solve this with a type constructor and an IsType<T> helper trait which is implemented for T (and only for T).

This is my attempt. Note that my Functor is a type constructor, while the actual type that is constructed with the functor (e.g. Vec<i32>) implements a trait that I named Wrapped (i.e. what I call Wrapped in my code is named Functor in the cited blog post).

/// Helper module allowing to specify concrete types in bounds
pub mod type_bound {
    mod sealed {
        pub trait IsType<T: ?Sized> {}
        impl<T: ?Sized> IsType<T> for T {}
    }

    /// Trait `IsType<T>` is implemented if and only if `Self` is `T`
    pub trait IsType<T: ?Sized>: sealed::IsType<T> {
        /// Convert from `T` to `Self` (no-op)
        fn identity_from(x: T) -> Self
        where
            Self: Sized,
            T: Sized;
        /// Convert from `Self` to `T` (no-op)
        fn identity_into(self) -> T
        where
            Self: Sized,
            T: Sized;
    }

    impl<T: ?Sized> IsType<T> for T {
        fn identity_from(x: T) -> Self
        where
            Self: Sized,
            T: Sized,
        {
            x
        }
        fn identity_into(self) -> T
        where
            Self: Sized,
            T: Sized,
        {
            self
        }
    }
}

pub mod functor {
    use super::type_bound::IsType;

    /// A type constructor that is a functor
    pub trait Functor {
        type Wrapped<Unwrapped>: Wrapped;
    }

    /// A type constructed by a functor (e.g. `Option<T>` or `Vec<T>`)
    pub trait Wrapped
    where
        Self: Sized,
        Self: IsType<
            <Self::Functor as Functor>::Wrapped<Self::Unwrapped>,
        >,
    {
        /// Functor that can be used to construct `Self`
        type Functor: Functor;

        /// Type passed to `<Self::Functor as Functor>::Wrapped`
        /// (e.g. `i32`) to construct `Self` (e.g. `Vec<i32>`)
        type Unwrapped;

        /// Replaces inner type and value by applying a mapping function
        fn map<B, F>(
            self,
            f: F,
        ) -> <Self::Functor as Functor>::Wrapped<B>
        where
            F: FnMut(Self::Unwrapped) -> B;
    }
}

use self::functor::{Functor, Wrapped};

/// Some example implementations for [`Functor`] and [`Wrapped`]
pub mod functor_impls {
    use super::*;

    pub struct OptionFunctor;
    impl Functor for OptionFunctor {
        type Wrapped<Unwrapped> = Option<Unwrapped>;
    }
    impl<A> Wrapped for Option<A> {
        type Functor = OptionFunctor;
        type Unwrapped = A;
        fn map<B, F>(self, f: F) -> Option<B>
        where
            F: FnMut(A) -> B,
        {
            Option::map(self, f)
        }
    }

    pub struct VecFunctor;
    impl Functor for VecFunctor {
        type Wrapped<Unwrapped> = Vec<Unwrapped>;
    }
    impl<A> Wrapped for Vec<A> {
        type Functor = VecFunctor;
        type Unwrapped = A;
        fn map<B, F>(self, f: F) -> Vec<B>
        where
            F: FnMut(A) -> B,
        {
            self.into_iter().map(f).collect()
        }
    }
}

fn convert_inner<T, B>(
    wrapped: T,
) -> <T::Functor as Functor>::Wrapped<B>
where
    T: Wrapped,
    B: From<T::Unwrapped>,
{
    wrapped.map(Into::into)
}

fn double_wrapped_i32<T>(
    wrapped: T,
) -> <T::Functor as Functor>::Wrapped<i32>
where
    T: Wrapped<Unwrapped = i32>,
{
    wrapped.map(|x| x * 2)
}

fn main() {
    let v: Vec<i32> = vec![1, 2, 3];
    let w: Vec<f64> = convert_inner(v);
    println!("w = {w:?}");
    let d: Vec<i32> = double_wrapped_i32(vec![11, 12]);
    println!("d = {d:?}");
}

(Playground)

Output:

w = [1.0, 2.0, 3.0]
d = [22, 24]

Happy to hear some feedback on my idea. Note that this is mostly a mental exercise and I don't see any real-world use case as of yet.

Edit: In my later versions (see posts below), I got rid of Functor and renamed Wrapped to Functor.

5 Likes

I figured out that the type constructors OptionFunctor, VecFunctor etc. are not necessary. It's possible to get rid of the Functor trait and rename Wrapped to Functor (now using the same naming scheme as in the cited blog post):

/// A type constructed by a functor (e.g. `Option<T>` or `Vec<T>`)
pub trait Functor: IsType<Self::Wrapped<Self::Unwrapped>> {
    /// Same wrapper, but different inner type
    type Wrapped<Unwrapped>: Functor;
    /// Inner type
    type Unwrapped;

    /// Replaces inner type and value by applying a mapping function
    fn map<B, F>(self, f: F) -> Self::Wrapped<B>
    where
        F: FnMut(Self::Unwrapped) -> B;
}

(Playground)

Here, the IsType<Self::Wrapped<Self::Unwrapped>> supertrait (bound on Self) will ensure that the same type constructor (Functor::Wrapped) is used for the input and the output of the map function.

P.S.: Apparently Self: Sized can be omitted too.

P.P.S.: However, omitting Sized will make it impossible to use the identity_from and identity_into methods, which may become needed when Rust's type system can't figure out that the types T and T::Wrapped<T::Unwrapped> are identical. So better leave it in:

-pub trait Functor: IsType<Self::Wrapped<Self::Unwrapped>> {
+pub trait Functor: Sized + IsType<Self::Wrapped<Self::Unwrapped>> {

(Playground)

I haven't come up with an example yet, where these identity_from and identity_into methods are actually needed.


P.P.P.S.: But we certainly want to get rid of this T: Sized bound, as it's not necessary:

     /// Trait `IsType<T>` is implemented if and only if `Self` is `T`
     pub trait IsType<T: ?Sized>: sealed::IsType<T> {
         /// Convert from `T` to `Self` (no-op)
         fn identity_from(x: T) -> Self
         where
-            Self: Sized,
-            T: Sized;
+            Self: Sized;
         /// Convert from `Self` to `T` (no-op)
         fn identity_into(self) -> T
         where
-            Self: Sized,
-            T: Sized;
+            Self: Sized;
     }

(Playground)

1 Like

It looks good!

I just would rename IsType as Identity and use only one of its methods, since they are redundant. Also, several of your type bounds are useless, such as T: ?Sized when later on you bind Self to be sized: where Self: Sized;.

pub trait Identity<T> {
    fn identity(x: T) -> Self;
}

impl<T: Sized> Identity<T> for T {
  fn identity(x: T) -> Self {
    x
  }
}
1 Like

Nice! I played around a little with the code and tried to implement the join method from the blogpost, since it mentioned the problem you quoted.
I got it working with the following:

pub trait Monad: Functor {
    fn bind<B, F>(self, f: F) -> Self::Wrapped<B>
        where
            F: FnMut(Self::Unwrapped) -> Self::Wrapped<B>;
}

impl<A> Monad for Option<A> {
    fn bind<B, F>(self, f: F) -> Self::Wrapped<B>
        where
            F: FnMut(Self::Unwrapped) -> Self::Wrapped<B> {
                self.and_then(f)
            }
}

fn join<MOuter, MInner, A>(outer: MOuter) -> MOuter::Wrapped<A>
where
    MOuter: Monad<Unwrapped = MInner, Wrapped<A> = MInner>,
    MInner: Monad<Unwrapped = A, Wrapped<A> = MOuter::Wrapped<A>>,
{
    outer.bind::<A,_>(|inner| inner)
}

But I was wondering if the IsType<T> supertrait is even needed for this and it turns out, it is not needed. But I'm unsure of what the implications of this are, I just tried to massage the join bounds until it compiled :sweat_smile: I added the Wrapped<A> = MInner constraint and the type annotation for the bind call.

Edit: It seems like the Wrapped<A> = MOuter::Wrapped<A> constraint for MInner is not needed.
So the following

fn join<MOuter, MInner, A>(outer: MOuter) -> MOuter::Wrapped<A>
where
    MOuter: Monad<Unwrapped = MInner, Wrapped<A> = MInner>,
    MInner: Monad<Unwrapped = A>,
{
    outer.bind::<A,_>(|inner| inner)
}

also works.

1 Like

You don't need the sealed stuff to ensure that Wrapper and Unwrapped are consistent with Self.

pub trait IsSelf {}

impl<F: ?Sized> IsSelf for F
where
    F: Functor<Wrapped<<Self as Functor>::Unwrapped> = Self>,
{}

/// A type constructed by a functor (e.g. `Option<T>` or `Vec<T>`)
pub trait Functor: Sized + IsSelf {
3 Likes

Ooops, I fell down the rabbit hole of trying to write a working traverse from the Monads and GATs blogpost you linked.

I got it working via some cursed trait bounds :sweat_smile:

fn traverse<F, M, A, B, I>(iter: I, f: F) -> M::Wrapped<Vec<B>>
where
    F: FnMut(A) -> M,
    M: Applicative<Unwrapped = B>,
    I: IntoIterator<Item = A>,
    M::Wrapped<Vec<B>>: Applicative<Unwrapped = Vec<B>, Wrapped<B> = M>,
    M::Wrapped<Vec<B>>: Applicative<Wrapped<Vec<B>> = M::Wrapped<Vec<B>>>,
{
    let mut iter = iter.into_iter().map(f);

    let mut result: M::Wrapped<Vec<B>> = match iter.next() {
        Some(b) => b.map(|x| vec![x]),
        None => return M::wrap(Vec::new()),
    };

    for m in iter {
        result = result.lift_a2::<_,B,Vec<B>>(m, |mut vec: Vec<B>, b: B| {
            vec.push(b);
            vec
        });
    }

    result
}

Playground

and it works with and without the IsType supertrait!
What was missing in the approach of the blogpost was to properly constrain the <M::Wrapped<Vec<B>> as Applicative>::Wrapped GAT. First I tried it with either one of the above bounds, but I would either get an error that the return type of lift_2a is wrong or that the argument m has the wrong type. After trying a few things, among them changes to Applicative, I thought: "Can I just specify the Applicative bound twice with different GATs?". Turns out, you can and it solves the problem! :smiley:

3 Likes

FYI you have an unnecessary bound on join.

fn join<MOuter, MInner, A>(outer: MOuter) -> MOuter::Wrapped<A>
where
    MOuter: Monad<Unwrapped = MInner, Wrapped<A> = MInner>,
//    MInner: Monad<Unwrapped = A>,
{
    outer.bind::<A,_>(|inner| inner)
}
1 Like

Thanks for all the responses.

I haven't read through the Monad stuff yet, but I had time to take a closer look at the IsType trait.

The idea was that IsType<T> is implemented for every T (including !Sized) and only for T. Adding the extra bound on the conversion function was intentionally. So I wanted to have the trait implemented, even if the method wasn't available.

I think I generally agree here. Note, however, that there is #20671. This means if I would do…

pub trait Functor
where
    Self: Identity<Self::Wrapped<Self::Unwrapped>>,
    Self::Wrapped<Self::Unwrapped>: Identity<Self>,

…then only the first bound (on Self) will be implied by a T: Functor bound somewhere, and the second bound would need to be redundantly specified.

This is why I thought having two-way conversions might be a good idea. But I'm unsure about it yet.

Interesting! I tried to make this idea more abstract:

/// Trait allowing to include concrete types in bounds
pub trait Reflect {
    type This: ?Sized;
}

impl<T: ?Sized> Reflect for T {
    type This = Self;
}

I can then implement the Identity trait as follows:

/// Trait `Identity<T>` is implemented for all `T: Sized`
/// and allows conversion between `Self` and `T`
pub trait Identity<T>: Reflect<This = T> {
    fn from_same(this: T) -> Self;
    fn into_same(self) -> T;
}

impl<T> Identity<T> for T
where
    T: Reflect<This = T>,
{
    fn from_same(x: T) -> Self {
        x
    }
    fn into_same(self) -> T {
        self
    }
}

This allows me to create a function such like this one:

/// Convert from type `A` into `B` asserting that `A` and `B` are the
/// same type
pub fn identity<A, B>(x: A) -> B
where
    A: Identity<B>,
{
    x.into_same()
}

(Not sure about whether such a function would pose any useful value though.)

Interestingly, type inference copes with identity(1) (see Playground link below).

Finally, I can define the Functor as follows:

/// A type constructed by a functor (e.g. `Option<T>` or `Vec<T>`)
pub trait Functor: Identity<Self::Wrapped<Self::Unwrapped>> {
    /// Same wrapper, but different inner type
    type Wrapped<Unwrapped>: Functor;
    /// Inner type
    type Unwrapped;

    /// Replaces inner type and value by applying a mapping function
    fn map<B, F>(self, f: F) -> Self::Wrapped<B>
    where
        F: FnMut(Self::Unwrapped) -> B;
}

(Playground)

This implies Sized now. Note that the GAT Functor::Wrapped<T> is Sized anyway and making it ?Sized would create a lot of practical issues.

@RobinH I don't overlook the Monad and traverse stuff yet (and I'm having a hard time with it, as it's super abstract :sweat_smile:), but I wonder if the Identity trait could help to get rid of some of the bounds (which might aid usage, considering #20671). But I'm not sure if this is related at all. I don't really overlook the situation and/or your code yet.

The only example I could come up with, where the IsType / Identity conversion methods matter, is this somewhat artificial example:

fn clone_and_convert_inner<T, B>(
    wrapped: T,
) -> (T::Wrapped<T::Unwrapped>, T::Wrapped<B>)
where
    T: Clone + Functor,
    B: From<T::Unwrapped>,
{
    (wrapped.clone().into_same(), wrapped.map(Into::into))
}

Of course, I could just return (T, T::Wrapped<B>) instead, and then the into_same() wouldn't be needed (and would even cause an error). But I still suspect like there may be situations where something like .into_same() is required in real life.

Mhh, I don't think the Identity is really that helpful for simplifying trait bounds.

Given that the Identity supertrait on Functor should imply that for all functors F: F = F::Wrapped<F::Unwrapped>>, I would expect the following, where I changed the return type to T, to compile.

fn clone_and_convert_inner<T, B>(
    wrapped: T,
) -> (T, T::Wrapped<B>)
where
    T: Clone + Functor,
    B: From<T::Unwrapped>,
{
    (wrapped.clone().into_same(), wrapped.map(Into::into))
}

But, as you already said, this doesn't compile, but does if the into_same call is omitted.

While looking for some other implementations for these concepts on crates.io, I found the higher crate, which also implements Functor, Applicative, Monad and other things. It's Functor trait looks different than yours, but the problem the author outlines in the README applies to both. Unfortunately, currently it is not possible to implement Functor (either version) for types such as HashSet , as it would require additional B: Hash + Eq bounds on map.
(The crate also contains a traverse implementation with even more complex trait bounds :astonished:)

My idea behind Identity::from_same and Identity::into_same is to be able to convert between types that are known to be equal at compile time, but where the compiler doesn't notice that (as of yet).

Basically if you have

pub trait Functor: Identity<Self::Wrapped<Self::Unwrapped>> {
    type Wrapped<Unwrapped>: Functor;
    type Unwrapped;
    /* … */

Then the from_same and into_same methods help you converting between T and T::Wrapped<T::Unwrapped> when you have any type T: Functor. Both methods (from_same and into_same) to be able to perfom the conversion in both directions can be handy, because any where T: Functor clause implies T: Identity<T::Wrapped<T::Unwrapped>> but could not imply T::Wrapped<T::Unwrapped>: Identity<T> even if such bound was included in the trait defintion of Functor like this:

pub trait Functor
where
    Self: Identity<Self::Wrapped<Self::Unwrapped>>, // works okay!
    // But the following will require redundant trait bounds later,
    // see Rust issue #20671:
    Self::Wrapped<Self::Unwrapped>: Identity<Self>,
{
    type Wrapped<Unwrapped>: Functor;
    type Unwrapped;
    /* … */

Yes, these are the cases where the compiler ensures that the types are equal but doesn't notice they are equal. We need to manually specify from_same or into_same where required (and only where required).

Very interesting. I tried to think about the purpose of the additional lifetime in their Function trait and updated mine as follows (in addition to some renaming):

/// A type constructed by a functor (e.g. `Option<T>` or `Vec<T>`)
pub trait Functor<'a>: Identity<Self::Map<'a, Self::Inner>> {
    /// Same wrapper, but different inner type
    type Map<'b, Inner>: Functor<'b>
    where
        'a: 'b,
        Inner: 'b;
    /// Inner type
    type Inner: 'a;

    /// Replaces inner type and value by applying a mapping function
    fn map<'b, B, F>(self, f: F) -> Self::Map<'b, B>
    where
        'a: 'b,
        B: 'b,
        F: Fn(Self::Inner) -> B + 'b;
}

Now that's some horrible lifetime overhead, but it allows us to do this:

impl<'a, A> Functor<'a> for Box<dyn Iterator<Item = A> + 'a>
where
    A: 'a,
{
    type Map<'b, Inner> = Box<dyn Iterator<Item = Inner> + 'b>
    where
        'a: 'b,
        Inner: 'b;
    type Inner = A;
    fn map<'b, B, F>(self, f: F) -> Self::Map<'b, B>
    where
        'a: 'b,
        B: 'b,
        F: Fn(A) -> B + 'b,
    {
        Box::new(Iterator::map(self, f))
    }
}

This allows us to do some lazy mapping where the mapping function doesn't need to be 'static:

fn main() {
    let strings: Vec<String> =
        vec!["Hello".to_string(), "World".to_string()];
    let suffix: String = "!".to_string();
    let suffix_ref: &str = &suffix;
    let iter1: Box<dyn Iterator<Item = String> + 'static> =
        Box::new(strings.into_iter());
    let iter2: Box<dyn Iterator<Item = String> + '_> =
        Functor::map(iter1, |mut s| {
            println!("Working.");
            s.push_str(suffix_ref);
            s
        });
    println!("I'm lazy.");
    for x in iter2 {
        println!("{x}");
    }
}

Note that Functor::map and Iterator::map are not exactly interchangeable, because the return type is different (Functor::map returns a Box in this example).

This will generate:

I'm lazy.
Working.
Hello!
Working.
World!

Finally, I found a use case where from_same is needed (edit: see update below):

fn double_inner_i32<'a, T: Functor<'a, Inner = i32>>(wrapped: T) -> T {
    T::from_same(wrapped.map(|x| x * 2))
}

Here is the full Playground.


Update:

Actually the above example doesn't require from_same yet, as the compiler will perfectly deal with:

fn double_inner_i32<'a, T: Functor<'a, Inner = i32>>(
    wrapped: T,
) -> T::Map<'a, T::Inner> {
    wrapped.map(|x| x * 2)
}

But I think I can come up with an example where it can't be fixed:

fn map_if_true<'a, T, F>(wrapped: T, f: F, b: bool) -> T
where
    T: Functor<'a>,
    F: Fn(T::Inner) -> T::Inner + 'a,
{
    if b {
        T::from_same(wrapped.map(f))
    } else {
        wrapped
    }
}

Here we need the from_same method to aid the compiler in figuring out that T and T::Map<'a, T::Inner> are the same (which is ensured at compile time, but the compiler isn't aware of it).

(updated Playground)

1 Like

The problem here is that the Functor in Rust allows you to map a HashSet<A> to a HashSet<B>. We would need an implementation like that:

fn map<…> (self, f: F) -> HashSet<B>
where
    …
{
    self.into_iter().map(f).collect()
}

But for this to work, we need a bound B: Eq + Hash.

Now this makes me wonder if HashSet actually is a functor in the Rust world, because a HashSet<A> can't be universally mapped-over to any other HashSet<B>. :thinking:

1 Like

There is a way to write that function without T::from_same but with more complicated trait bounds.

fn map_if_true<'a, T, F, I: 'a>(wrapped: T, f: F, b: bool) -> T
where
    T: Functor<'a, Inner = I, Map<'a, I> = T>,
    F: Fn(T::Inner) -> T::Inner + 'a,
{
    if b {
        wrapped.map(f)
    } else {
        wrapped
    }
}

Playground

The type parameter I is as far as I can tell necessary, as T: Functor<'a, Map<'a, T::Inner> = T> leads to a cycle in the evaluation.

1 Like

Hypothesis: if we allow Vec<f64> to implement Functor, then HashSet<i32> can't be a Functor in Rust.

My point is: if we don't demand that the inner type implements Eq + Hash, then, assuming HashSet is a functor too, we can't univerally perform a map operation (and this ability is the essence of a functor, isn't it?).

Here is an alternative definition, where it's possible to overcome the limitations of the cited README (i.e. where it's possible to implement Functor for HashSet<T>), but at the price of, e.g., disallowing Vec<T> to always implement Functor, unless T: Eq + Hash.

/// A type constructed by a functor (e.g. `Option<T>` or `Vec<T>`)
pub trait Functor<'a>
where
    Self: Identity<Self::Map<'a, Self::Inner>>,
{
    /// Same wrapper, but different inner type
    type Map<'b, Inner>: Functor<'b>
    where
        'a: 'b,
        Inner: 'b + Eq + Hash;

    /// Inner type
    type Inner: 'a + Eq + Hash;

    /// Replaces inner type and value by applying a mapping function
    fn map<'b, B, F>(self, f: F) -> Self::Map<'b, B>
    where
        'a: 'b,
        B: 'b + Eq + Hash,
        F: Fn(Self::Inner) -> B + 'b;
}

impl<'a, A> Functor<'a> for HashSet<A>
where
    A: 'a + Eq + Hash,
{
    type Map<'b, Inner> = HashSet<Inner>
    where
        'a: 'b,
        Inner: 'b + Eq + Hash;
    type Inner = A;
    fn map<'b, B, F>(self, f: F) -> HashSet<B>
    where
        'a: 'b,
        B: 'b + Eq + Hash,
        F: Fn(A) -> B + 'b,
    {
        self.into_iter().map(f).collect()
    }
}

(Playground)

How is this problem handled in Haskell?

Oh cool!

Interesting, and clever solution.

The downside of the solution would be that the bound (including the type parameter) will be "infectious", I guess, when you want to call that function from another function. That's why I think having an Identity trait with from_same and into_same can help here: it makes you do some extra work once, but then the issue is solved. Consider:

fn foo<'a, T, F>(wrapped: T, f: F, b: bool) -> T
where
    T: Functor<'a>,
    F: Fn(T::Inner) -> T::Inner + 'a + Copy,
{
    /* does some other work here */
    map_if_true(wrapped, f, b)
}

(Playground)

With the version of map_if_true that doesn't use from_same, the foo function will require extra bounds too (as well as any generic function which calls foo (as well as any generic function which calls that function (and so on))):

fn foo<'a, T, F, I: 'a>(wrapped: T, f: F, b: bool) -> T
where
    T: Functor<'a>, // not sufficient
    // instead we need the following:
    T: Functor<'a, Inner = I, Map<'a, I> = T>,
    F: Fn(T::Inner) -> T::Inner + 'a + Copy,
{
    /* does some other work here */
    map_if_true(wrapped, f, b)
}

(Playground)

So I do think (at this point), that having such an Identity trait makes sense (if you want to go down the path of pushing Rust's type system that far).


Rethinking about that, I think that was a stupid solution. What about BTreeMap, which would require Ord etc. This would be a never ending problem!

Still wondering how this works in Haskell (see Functor in Haskell), but it's so many years ago since I did Haskell.


Looking at the Haskell definition, I wonder if it would be a good idea to rename Functor::map to Functor::fmap to avoid ambiguities and make some calls easier. (Playground)

I think I finally found a solution:

use std::collections::HashSet;
use std::hash::Hash;

/// A type constructed by a functor (e.g. `Option<T>` or `Vec<T>`)
pub trait Functor<'a, B>
where
    B: 'a,
{
    /// Same wrapper, but different inner type
    type Map<'b>: Functor<'b, B>
    where
        'a: 'b,
        B: 'b;

    /// Inner type
    type Inner: 'a;

    /// Replaces inner type and value by applying a mapping function
    fn fmap<'b, F>(self, f: F) -> Self::Map<'b>
    where
        'a: 'b,
        F: Fn(Self::Inner) -> B + 'b;
}

impl<'a, A, B> Functor<'a, B> for Option<A>
where
    A: 'a,
    B: 'a,
{
    type Map<'b> = Option<B>
    where
        'a: 'b,
        B: 'b;
    type Inner = A;
    fn fmap<'b, F>(self, f: F) -> Option<B>
    where
        'a: 'b,
        F: Fn(A) -> B + 'b,
    {
        self.map(f)
    }
}

impl<'a, A, B> Functor<'a, B> for Vec<A>
where
    A: 'a,
    B: 'a,
{
    type Map<'b> = Vec<B>
    where
        'a: 'b,
        B: 'b;
    type Inner = A;
    fn fmap<'b, F>(self, f: F) -> Vec<B>
    where
        'a: 'b,
        F: Fn(A) -> B + 'b,
    {
        self.into_iter().map(f).collect()
    }
}

impl<'a, A, B> Functor<'a, B> for HashSet<A>
where
    A: 'a + Eq + Hash,
    B: 'a + Eq + Hash,
{
    type Map<'b> = HashSet<B>
    where
        'a: 'b,
        B: 'b;
    type Inner = A;
    fn fmap<'b, F>(self, f: F) -> HashSet<B>
    where
        'a: 'b,
        F: Fn(A) -> B + 'b,
    {
        self.into_iter().map(f).collect()
    }
}

impl<'a, A, B> Functor<'a, B> for Box<dyn 'a + Iterator<Item = A>>
where
    A: 'a,
    B: 'a,
{
    type Map<'b> = Box<dyn 'b + Iterator<Item = B>>
    where
        'a: 'b,
        B: 'b;
    type Inner = A;
    fn fmap<'b, F>(self, f: F) -> Self::Map<'b>
    where
        'a: 'b,
        F: 'b + Fn(A) -> B,
    {
        Box::new(self.map(f))
    }
}

fn double<'a, 'b, T>(x: T) -> T
where
    'a: 'b,
    T: Functor<'a, i32, Inner = i32, Map<'b> = T>,
{
    x.fmap(|x| 2 * x)
}

fn map_if_true<'a, 'b, T, F, A>(wrapped: T, f: F, b: bool) -> T
where
    'a: 'b,
    A: 'a,
    T: Functor<'a, A, Inner = A, Map<'b> = T>,
    F: Fn(T::Inner) -> T::Inner + 'a,
{
    if b {
        wrapped.fmap(f)
    } else {
        wrapped
    }
}

fn main() {
    let mut x = HashSet::new();
    x.insert(5);
    x.insert(7);
    println!("x = {x:?}");
    x = double(x);
    println!("x = {x:?}");
    x = map_if_true(x, |x| 1000 * x, false);
    println!("x = {x:?}");
    x = map_if_true(x, |x| 1000 * x, true);
    println!("x = {x:?}");
    let y = vec![1, 2, 3];
    println!("y = {y:?}");
    let z = y.fmap(|v| (2 * v) as f64);
    println!("z = {z:?}");
}

(Playground)

Output:

x = {7, 5}
x = {10, 14}
x = {10, 14}
x = {10000, 14000}
y = [1, 2, 3]
z = [2.0, 4.0, 6.0]

Note that I had to remove the Identity trait (including the from_same and into_same methods) for now. I will try to add it again, but that seems difficult.


Closest I can get to it is this (inspired by @quinedot's trick above):

pub trait FunctorSelf<'a, Inner> {}

impl<'a, T: ?Sized, Inner> FunctorSelf<'a, Inner> for T
where
    Inner: 'a,
    T: Functor<'a, Inner, Map<'a> = T>,
{
}

/// A type constructed by a functor (e.g. `Option<T>` or `Vec<T>`)
pub trait Functor<'a, B>
where
    Self: FunctorSelf<'a, Self::Inner>,
    B: 'a,

(Playground)

This makes any Functor require that Functor<'a, Self::Inner>::Map<'a> is Self. But it doesn't help the compiler to infer certain bounds automatically later.


P.S.: Full example where I renamed Map to Mapped (because it's no longer taking a type parameter) and re-added the demonstration how Functor works on Boxed Iterators too: Playground.


P.P.S.: Finally figured out how to get rid of the "viral" bounds by using two associated types: Mapped (which maps to the type parameter given to the trait) and Map (which maps to a type parameter given to a GAT): (edit: I figured out later that one associated type is sufficient, see GitHub repository for latest updates)

/// A type constructed by a functor (e.g. `Option<T>` or `Vec<T>`)
pub trait Functor<'a, B>
where
    Self: Identity<Self::Map<'a, Self::Inner>>,
    B: 'a,
{
    /// Inner type
    type Inner: 'a;

    /// `Self` with inner type mapped to `B`
    /// (where `B` is a type parameter of this trait)
    type Mapped<'b>: Functor<'b, B> + Identity<Self::Map<'b, B>>
    where
        'a: 'b,
        B: 'b;

    /// `Self` with inner type mapped to `C`
    /// (where `C` is a type parameter of this GAT)
    type Map<'b, C>
    where
        'a: 'b,
        C: 'a;

    /// Replaces inner type and value by applying a mapping function
    fn fmap<'b, F>(self, f: F) -> Self::Mapped<'b>
    where
        'a: 'b,
        F: Fn(Self::Inner) -> B + 'b;
}

This allows:

-fn double<'a, 'b, T>(x: T) -> T
+fn double<'a, T>(x: T) -> T
 where
-    'a: 'b,
-    T: Functor<'a, i32, Inner = i32, Mapped<'b> = T>,
+    T: Functor<'a, i32, Inner = i32>,
 {
-    x.fmap(|x| 2 * x)
+    T::from_same(x.fmap(|x| 2 * x).into_same())
 }
 
-fn map_if_true<'a, 'b, T, A, F>(wrapped: T, f: F, b: bool) -> T
+fn map_if_true<'a, T, A, F>(wrapped: T, f: F, b: bool) -> T
 where
-    'a: 'b,
     A: 'a,
-    T: Functor<'a, A, Inner = A, Mapped<'b> = T>
+    T: Functor<'a, A, Inner = A>,
     F: Fn(T::Inner) -> T::Inner + 'a,
 {
     if b {
-        wrapped.fmap(f)
+        T::from_same(wrapped.fmap(f).into_same())
     } else {
         wrapped
     }
 }

(Playground) edit: simplified the bounds further (Playground)

The bound T: Functor<'a, B, Inner = A> basically means that T allows us to map the inner type from A to B.

Note that the from_same and into_same calls are not required. It's still possible to omit them at the price of adding extra trait bounds (which are always fulfilled, but the compiler won't know about that).

Now I feel like this:

:face_with_spiral_eyes:

Hope I wasn't making any mistake.


P.P.P.S.: The following trait being in scope (which has a blanket implementation), simplifies the from_same / into_same calls further:

pub trait FunctorSelf<'a, A>: Functor<'a, A>
where
    A: 'a,
{
    fn from_mapped(x: Self::Mapped<'a>) -> Self;
    fn into_mapped(self) -> Self::Mapped<'a>;
}

impl<'a, T, A> FunctorSelf<'a, A> for T
where
    A: 'a,
    T: Functor<'a, A, Inner = A>,
{
    fn from_mapped(mapped: Self::Mapped<'a>) -> Self {
        Self::from_same(mapped.into_same())
    }
    fn into_mapped(self) -> Self::Mapped<'a> {
        Self::Mapped::<'a>::from_same(self.into_same())
    }
}
 fn double<'a, T>(x: T) -> T
 where
     T: Functor<'a, i32, Inner = i32>,
 {
-    T::from_same(x.fmap(|x| 2 * x).into_same())
+    T::from_mapped(x.fmap(|x| 2 * x))
 }
 
 fn map_if_true<'a, T, A, F>(wrapped: T, f: F, b: bool) -> T
 where
     A: 'a,
     T: Functor<'a, A, Inner = A>,
     F: Fn(A) -> A + 'a,
 {
     if b {
-        T::from_same(wrapped.fmap(f).into_same())
+        T::from_mapped(wrapped.fmap(f))
     } else {
         wrapped
     }
 }

(Playground)


I made a crate fmap for future reference (and to aid playing with it locally as well as seeing the documentation online): Here is the Functor trait.


Sorry for all the incremental updates. I finally put up the code on GitHub, so it's easier to track any updates to my draft rather than using Playground :sweat_smile:. Meanwhile, I managed to implement Functor for all types in std::collections, including those which impose extra bounds on the inner type(s), such as BinaryHeap and others.

I found out that in some cases using a bound that restricts an associated type seem to come with side-effects. Consider:

pub trait Reflect {
    type This: ?Sized;
}

impl<T: ?Sized> Reflect for T {
    type This = Self;
}

mod sealed {
    pub trait Identity<T> {}
    impl<T> Identity<T> for T {}
}

pub trait Identity<T>: Sized + Reflect<This = T> { // this won't work…
//pub trait Identity<T>: Sized + sealed::Identity<T> {
    /// Convert from `T` into `Self` (no-op)
    fn from_same(this: T) -> Self;
    /// Convert from `Self` into `T` (no-op)
    fn into_same(self) -> T;
}

trait Foo
where
    Self: Identity<Self::A>,
    //Self: Identity<Self::B>, // …if using this
{
    type A;
    type B;
}

(Playground)

I need to replace Reflect<This = T> with sealed::Identity<T> in order for the example to compile if Foo has more than one Identity supertrait.

This is why I went back to using the sealed trait pattern. I wonder if there's a way around this that doesn't require sealed traits, but I don't think there is.

Do you mean Reflect? Because that's not the trait I provided which makes Wrapped and Unwrapped consistent.

Anyway, I don't see the point. E.g. there's no reason to have a type parameter on Identity if you're forcing it to be Self.

(Unless the point is "add enough projections and rustc will fail to normalize", in which case, yep it sure will. Takeaway: don't add unnecessary tautological projections.)

Yes, sorry, it's not the same thing you proposed but was an idea inspired by it.

I need it here (I think), which is used there, and my solution is that again.

And if I use an associated type instead of a type parameter, it can cause problems too:

pub trait Identity: Sized {
    type This;
    /// Convert from `This` into `Self` (no-op)
    fn from_same(this: Self::This) -> Self;
    /// Convert from `Self` into `This` (no-op)
    fn into_same(self) -> Self::This;
}

impl<T> Identity for T {
    type This = T;
    /// Convert from `T` into `Self` (no-op)
    fn from_same(this: Self::This) -> Self {
        this
    }
    /// Convert from `Self` into `T` (no-op)
    fn into_same(self) -> Self::This {
        self
    }
}


trait Foo
where
    Self: Identity<This = Self::A>,
    Self: Identity<This = Self::B>,
{
    type A;
    type B;
}

(Playground)

Anyway, I will keep thinking more on it and try to apply your original example to my current scenario. Sorry if I mixed things up. Maybe the issues are not related at all (anymore?). Will need some more time to think.


Well, yeah, I think the IsSelf trait doesn't solve things for me:

pub trait Identity: Sized {
    type This;
    /// Convert from `This` into `Self` (no-op)
    fn from_same(this: Self::This) -> Self;
    /// Convert from `Self` into `This` (no-op)
    fn into_same(self) -> Self::This;
}

impl<T> Identity for T {
    type This = T;
    /// Convert from `T` into `Self` (no-op)
    fn from_same(this: Self::This) -> Self {
        this
    }
    /// Convert from `Self` into `T` (no-op)
    fn into_same(self) -> Self::This {
        self
    }
}

trait IsSelf {}

impl<T: ?Sized> IsSelf for T
where
    T: Foo<A = T>,
{}

trait Foo: IsSelf
where
    Self: Identity<This = Self::A>,
{
    type A;
    fn foo(self) -> Self::A {
        //self // doesn't work
        self.into_same() // this is needed
    }
}

(Playground)

I'm saying get rid of all the pointless redirection. I see no point in having trait parameters or associated types that only have one solution, Self.

Once you do that, here's what's left:

pub trait Identity: Sized {
    /// Convert from `This = Self` into `Self` (no-op)
    fn from_same(this: Self) -> Self;
    /// Convert from `Self` into `This = Self` (no-op)
    fn into_same(self) -> Self;
}

impl<T> Identity for T {
    /// Convert from `T = Self` into `Self` (no-op)
    fn from_same(this: Self) -> Self {
        this
    }
    /// Convert from `Self` into `T = Self` (no-op)
    fn into_same(self) -> Self {
        self
    }
}

trait Foo: Identity {
    fn foo(self) -> Self {
        self
    }
}

Then a moment of reflection[1] later it's all "lol why do I need that even" and the resulting code is

// just use whatever variable you have, it's already Self

I admit I'm only loosely following along, but the only time I saw a use demonstrated for it was for Functor, which is why I suggested the more direct bound in the first place:

pub trait IsSelf {}

impl<F: ?Sized> IsSelf for F
where
    F: Functor<Wrapped<<Self as Functor>::Unwrapped> = Self>,
{}

All these recent things in Identity itself or in Foo depending on Identity but not Functor just seem to be exercises in finding the limits of rustc's normalization.


  1. get it? ↩︎

:sweat_smile:

The difficulty in my use case is that the associated type is a GAT, and there is only a type equivalence when a certain other type is used for the GATs type parameter. Boiled down, it's this:

pub trait Identity<T>: Sized {
    fn from_same(this: T) -> Self;
    fn into_same(self) -> T;
}

pub trait Functor<'a, B>
where
    Self: Identity<Self::Mapped<'a, Self::Inner>>,
    B: 'a,
{
    type Inner: 'a;
    type Mapped<'b, C>
    where
        'a: 'b,
        C: 'a;

    // when `B` is `Self::Inner`,
    // then the return type is equivalent to `Self`
    fn fmap<'b, F>(self, f: F) -> Self::Mapped<'b, B>
    where
        'a: 'b,
        F: 'b + Fn(Self::Inner) -> B;
}

(Playground)

The Identity approach with a type parameter seems to be the safest/cleanest solution I have found so far. (full implementation here)

What exactly is "normalization"? Figuring out which types are equivalent?


  1. get it? ↩︎