Add `apply` extension method for all types to std

With this method we can write chains like this:

fn process_new(vec: Vec<String>) -> Option<i32> {
    vec
        .into_iter()
        .map(|x| x.parse::<i32>())
        .collect::<Result<Vec<_>, _>>().ok()?
        .into_iter()
        .sum::<i32>()
        .apply(Some) // Usage of `apply` method
}

instead of this:

fn process_old(vec: Vec<String>) -> Option<i32> {
    Some(vec // We must write `Some(` at start of chain
        .into_iter()
        .map(|x| x.parse::<i32>())
        .collect::<Result<Vec<_>, _>>().ok()?
        .into_iter()
        .sum::<i32>()) // and `)` at end of chain
}

This method works like & operator in Haskell. I think it might be useful for functional chains like this.

There was similar method called also, it allows to run methods that returns () and requires &self or &mut self:

let val = 8.also_mut(|it| *it *= 2);
assert_eq!(val, 16);

There also deref version of this method to make calls like this:

let sorted = vec![3, 2, 1].also_deref_mut(<[_]>::sort);
assert_eq!(sorted, vec![1, 2, 3]);

or

let pushed = vec![1, 2, 3].also_mut(|v| v.push(4));
assert_eq!(pushed, vec![1, 2, 3, 4]);

apply method can be implemented by this code:

trait Apply: Sized {
    fn apply<Res, F: FnOnce(Self) -> Res>(self, f: F) -> Res {
        f(self)
    }
}

impl<T: ?Sized> Apply for T {}

All examples and implementation of this methods you can find here.

I think it is useful and should be added to std. Thougths?

3 Likes

There are external crates for this -- I know of apply and tap.

3 Likes

One thing that's come up before is that if we were ever to get postfix forms of things (like C# recently did), then

.also_mut(|it| *it *= 2)

could be spelt

.match { it => *it *= 2 }

EDIT: follow-up point out that this would actually be

.match { mut it => { it *= 2; it } }

So really it's a better match for apply. (Which actually fits better for that demonstration anyway -- 8.apply(|it| it * 2) makes more sense than 8.also_mut(|it| *it *= 2) to me anyway, which is match { it => it * 2 }.

3 Likes

also_mut has signature T -> (&mut T -> ()) -> T, so I don't think .match can replace it (you would need to write .match { mut it => { f(&mut it); it } } which it not really convenient).

Though .match can be replacement for apply (signature A -> (A -> B) -> B).

To be honest, I'd always prefer a method for that over the postifx match. Especially in the mutable dereferencing case… mutating through the matched reference really seems to be in bad taste.

1 Like

Yuck. The first read, "also mut(ate) it (by dereferencing) it and multiplying it by 2". The second reads, "match it (by dereferencing) it and multiplying it by 2".

The first much more accurately reflects the intent, whereas the second requires much more cognitive load to understand what it is trying to do.

2 Likes

Sorry, I got that one wrong.

This case would be .match { it => it * 2 }, which is more like apply, and seems pretty readable to me.

The also_mut example would be more like .match { mut v => { v.sort(); v } }.