LINUX.ORG.RU

Можно ли и как в Rust написать функцию partial (частичное применение аргументов функции)?

 


0

6

По питонячему примеру.
В частности, непонятно, что делать с отсутствием возможности указать переменное число аргументов. Делать как в Питоне и создавать callable тип, есть ли такая возможность?

★★★★★

Последнее исправление: cetjs2 (всего исправлений: 1)
Ответ на: комментарий от Dark_SavanT

Как решение частного случая — да. А вообще нет :). Например, откуда мне знать сигнатуру ожидаемой функции? А достаточно ли будет трейта Fn, как насчет FnOnce и FnMut?

Virtuos86 ★★★★★
() автор топика

Разобрался, как написать каррирующую функцию. Правда, нужна ночная сборка Раста, потому что атрибут feature не поддерживается стабильной сборкой, а так ничего:

fn foo(arg: i32, arg1: i32) -> i32 {
    arg + arg1
}

#[feature(conservative_impl_trait)]
fn curry_foo(func: fn(i32, i32) -> i32, arg: i32) -> impl Fn(i32) -> i32 {
    move |arg1| func(arg, arg1) // без `move' вернуть замыкание низзя!
}

fn main() {
    let bar = curry_foo(foo, 1)(2);
    println!("{}", c);
}

Пытался написать макрос, который бы генерировал всю цепочку замыканий автоматически, благо для замыканий типы проставлять не нужно, но честно признаюсь, что обосрался победить макросистему. Потому как макрос требуется рекурсивный, и нужно протаскивать каким-то образом через всю цепочку рекурсивных раскрытий все аргументы каррируемой функции: arg1, arg2, ... argN. Протаскивать аргументы не придумалось ничего лучше, кроме как передавая их в самом вызове макроса, но это создает другие проблемы: «unexpected end of macro invocation», то есть неоднозначности при использовании макроса, а именно получается возможная неоднозначность при записи его вызова.

Virtuos86 ★★★★★
() автор топика
Последнее исправление: Virtuos86 (всего исправлений: 1)
Ответ на: комментарий от RazrFalcon
>>> from functools import partial
>>> def foo(x, y, z):
...     print x + y + z
... 
>>> bar = partial(foo, 1, 2)
>>> bar(3)
6
>>> bazz = partial(foo, 1)
>>> bazz(2, 3)
6
>>> wtf = partial(bazz, 2)
>>> wtf(3)
6
>>> 
Virtuos86 ★★★★★
() автор топика

Если совсем по простому то можно так:

macro_rules! apply {
    ($func:ident, $( $x:expr ),* ; $( $y:ident ),* ) => (
        |$($y),*| {
            $func($($x),* , $($y),*)
        }
    )
}

fn sum(a: u32, b: u32, c: u32) -> u32 {
    a + b + c
}

fn main() {
    let foo = apply!(sum, 30 ; a, b);
    let ans = foo(10, 2);
    println!("Answer: {}", ans);
    
    let bar = apply!(sum, 30, 7 ; b);
    let ans = bar(6);
    println!("Answer: {}", ans);
}
pftBest ★★★★
()
Ответ на: комментарий от Virtuos86

Вообще там создается экземпляр класса partial (в Питоне так часто делают, что пишут классы, маскирующиеся под функции, благо нет никаких конвенций, как в других ЯП, предписывающих использовать capitalize имена для классов), который служит декоратором для передаваемой функции. Экземпляры этого класса callable и при вызове достают схороненную по ссылке функцию, конструируют финальный список аргументов, вызывают функцию и возвращают результат.

Virtuos86 ★★★★★
() автор топика
Ответ на: комментарий от red75prim

Мне показалось, что можно поправить версию pftBest, и я был прав: https://play.rust-lang.org/?gist=43a660c4e28fd50e40f91eab0f3cbcea&version...

macro_rules! apply {
    ($func:ident $(, $x:expr )* ; $( $y:ident ),* ) => (
        |$($y),*| {
            $func($($x,)* $($y,)*)
        }
    )
}

fn sum(a: u32, b: u32, c: u32) -> u32 {
    a + b + c
}

fn main() {
    let foo = apply!(sum, 30 ; a, b);
    let ans = foo(10, 2);
    println!("Answer: {}", ans);
    
    let bar = apply!(sum, 30, 7 ; b);
    let ans = bar(6);
    println!("Answer: {}", ans);
    
    let bazz = apply!(sum ; a, b, c);
    let ans = bazz(1, 2, 6);
    println!("Answer: {}", ans);
}

Virtuos86 ★★★★★
() автор топика
Последнее исправление: Virtuos86 (всего исправлений: 1)
Ответ на: комментарий от RazrFalcon

Строго говоря, те же аргументы по умолчанию покрывают большую часть юзкейсов, имхо:

def foo(a=1, b=2, c):
    ...
Хотя их в Расте нет.

Virtuos86 ★★★★★
() автор топика
Ответ на: комментарий от Virtuos86

Нажал кнопку play :)

Но серьезно, есть тыща способов сделать так что этого ворнинга не будет, и тогда прострел ноги гарантирован.

pftBest ★★★★
()
Ответ на: комментарий от pftBest

Нажал кнопку play :)

Э, не могу это повторить, потому что у меня теперь тоже запускается, но могу поклясться, что, когда я первый раз нажал на Play, оно заругалось из-за warn(unused_variables) на переменную b.

Но серьезно, есть тыща способов сделать так что этого ворнинга не будет, и тогда прострел ноги гарантирован.

Это я понял, надо иметь в виду. Макросы вообще-то бяка ещё та, как оказалось.

Virtuos86 ★★★★★
() автор топика
Ответ на: комментарий от MyTrooName

Плохой пример, код без partial не длиннее:

use std::ops::Add;

fn add<T: Add>(a: T, b: T) -> T::Output {
    a.add(b)
}

fn main() {
    let data = [1, 2, 3, 4, 5];
    let result = data.iter().map(|&x| add(x, 2));
    println!("{:?}", result.collect::<Vec<_>>());
}
pftBest ★★★★
()
Ответ на: комментарий от pftBest

он никогда не будет не длиннее по сравнению с замыканием. это просто сахар, не для краткости, а для концептуальности

хотя вообще-т список формальных параметров не дублируется

lambda x: add(2, x) vs partial(add, 2)

MyTrooName ★★★★★
()
Последнее исправление: MyTrooName (всего исправлений: 3)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.