LINUX.ORG.RU

Implicit function arguments в Rust

 


1

9

Привет, ЛОР.

Расскажи мне, как в Rust принято таскать read-only данные типа конфигурации? В идеале я хочу аналог ReaderT из Haskell, т.к. он позволяет легко тестировать код без свистоплясок глобальными переменными и прочих странных вещей. Для Rust я похожего не нашёл.

Неужели нужно городить глобальные переменные либо руками каждой фукнции писать лишний аргумент?

★★★★★

Последнее исправление: hateyoufeel (всего исправлений: 2)

Ответ на: комментарий от hateyoufeel

То, что так можно сделать не означает, что так нужно делать.

Про ФП, вообще-то, можно сказать то же самое. Прятать Config за ReaderT, а потом пытаться стебаться с ООП... Ну, у каждого голова болит по своему.

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

Прятать Config за ReaderT, а потом пытаться стебаться с ООП...

Про ООП тут начали говорить сами фанаты ООП, они же начали предлагать свои странные костыли. Я не стебусь, я ужасаюсь этому б%#@&му цирку с конями, в котором вместо коней — хромой пони, а вместо дам — карлик-трансвестит.

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

Я, конечно, говнокодер, и с этим не спорю. Но я всё же не понимаю, чем плоха константная глобальная переменная с конфигурацией?

В чём смысл передавать ссылку на одну и туже переменную во все функции, и чем это, по сути, будет отличаться от глобальной переменной?

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

Но я всё же не понимаю, чем плоха константная глобальная переменная с конфигурацией?

Тем, что она должна инициализироваться в рантайме, а значит не может быть константной.

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

В крестах у неё может быть спецификатор const. extern const ... в заголовочном файле, подключать его во всех модулях, где нужна конфигурация.

Предполагаю, что в расте тоже можно подобное сделать.

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

Но мы ведь умнички и не делаем 100500 методов в классе, а делаем много разных объектов разного типа, каждому из которых теперь при инициализации нужно конфиг в щачло пихать.

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

Я не стебусь, я ужасаюсь этому б%#@&му цирку с конями, в котором вместо коней — хромой пони, а вместо дам — карлик-трансвестит.

В общем-то, тоже самое можно сказать и про пожелание из стартового сообщения темы. Тому, кто пишет на Haskell, этот маразм с ReaderT кажется классным решением, но для тех, кто на Haskell не пишет, это такой же цирк уродов.

По сути же темы, действительно интересно, можно ли в Rust-е получить нужный вам эффект каким-то другим способом. Ибо попытка притащить решение из ООП вряд ли будет лаконичной и удобной.

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

Но мы ведь умнички и не делаем 100500 методов в классе, а делаем много разных объектов разного типа, каждому из которых теперь при инициализации нужно конфиг в щачло пихать.

Ну так замечательно же богатство выбора:

  • либо таскаем аргумент config через все методы;
  • либо пихаем его в каждый конструктор, тогда методы будут иметь неявную ссылку на свой собственный экземпляр config-а;
  • либо привязываемся к тому или иному варианту глобального стейта (в зависимости от ЯП это могут быть либо обычные глобальные переменные, либо статические члены класса, либо вариации на тему синглетонов).

В рамках ООП других вариантов не видно. Ну а в Rust-е и ООП нормального нет, так что там все как-то печальнее должно быть, на первый взгляд.

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

В крестах у неё может быть спецификатор const. extern const ... в заголовочном файле, подключать его во всех модулях, где нужна конфигурация.

В Rust нет заголовочных файлов. Ты сейчас предлагаешь грязный хак, я правильно понимаю?

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

В общем-то, тоже самое можно сказать и про пожелание из стартового сообщения темы. Тому, кто пишет на Haskell, этот маразм с ReaderT кажется классным решением, но для тех, кто на Haskell не пишет, это такой же цирк уродов.

Ты сейчас утверждаешь, что передача конфига аргументом в функцию — цирк уродов? Чувак, иногда лучше жевать чем говорить.

По сути же темы, действительно интересно, можно ли в Rust-е получить нужный вам эффект каким-то другим способом. Ибо попытка притащить решение из ООП вряд ли будет лаконичной и удобной.

Ну, do-нотацию в Rust уже закостылили макросами, но т.к. поддержки HKT нет и пока не предвидится, монад в духе Reader нам не видать. Можно попробовать накидать thread-safe хранилище String -> Any, и на месте делать тайпкаст, но это тоже ублюдочный костыль.

Вообще, в Rust много подобных проблем: подходы, которые считаются каноничными в других языках, в Rust не работают по каким-либо причинам, и приходится страдать. Например, в Rust нет человеческого Poll (mio::Poll работает не со всеми типами сокетов/файлов), и предлагается создавать по треду на fd со всеми вытекающими.

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

Ты сейчас утверждаешь, что передача конфига аргументом в функцию — цирк уродов? Чувак, иногда лучше жевать чем говорить.

Как-то у меня сложилось ощущение, что это не совсем простое протаскивание:

Это монадический трансформер. Если проще, это такая штука, добавляющая к каждой функции, которая с ней объявлена, лишний невидимый за счёт каррирования аргумент, доступ к которому можно получить через специальный метод.

Поскольку с Haskell-ом я сильно на «Вы», то не могли бы вы уточнить, этот самый аргумент Config в сигнатурах ваших функций будет присутствовать явно или все-таки его там не будет?

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

Как-то у меня сложилось ощущение, что это не совсем простое протаскивание:

Это совсем простое протаскивание, которое скрыто каррированием и небольшой магией.

Поскольку с Haskell-ом я сильно на «Вы», то не могли бы вы уточнить, этот самый аргумент Config в сигнатурах ваших функций будет присутствовать явно или все-таки его там не будет?

В сигнатурах будет. В списке аргументов — нет. Например:

data MyConfig = MyConfig { getNumber :: Int }

f :: Int -> Reader MyConfig Int
f i = do
    num <- asks getNumber
    return (i + num)

Тут главное удобство в том, что этот самый MyConfig будет неявно передаваться вниз по всему стеку вызовов. Ну и да, сигнатуры в Haskell писать не обязательно, и всё будет работать без них.

hateyoufeel ★★★★★
() автор топика
Последнее исправление: hateyoufeel (всего исправлений: 2)
Ответ на: комментарий от Ivan_qrt

Но я всё же не понимаю, чем плоха константная глобальная переменная с конфигурацией?

Потому, что глобальная переменная не аналог ReaderT. Ты можешь прокидывать с помощью ReaderT в функцию каждый раз разное значение (конфиг), сколько угодно раз.

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

Изначально странная идея каргокультить в Rust костыли из Haskell, придуманные для эмуляции императивщины в чистом языке. Рекомендованное решение - прокидывать иммутабельную ссылку на конфиг аргументом до первого стракта, там сохранять ссылку в поле и дальше использовать через self. Если очень лень, или если нужно иногда менять конфиг, можно сделать статический Mutex<T>. Собственно все, как в обычных императивных языках от С до Java, каким образом сюда умудрились приплести ООП и его отсутствие, я не понял.

предлагается создавать по треду на fd со всеми вытекающими.

Предлагается использовать Tokio, так-то.

nonimous
()
Ответ на: комментарий от eao197

Кстати, раз уж мы про C++ тут вспомнили, годный аналог будет выглядеть примерно так:

#include <iostream>

class Config { 
  public:
    Config() : addOrMultiply(true) { } 
    bool addOrMultiply;
};

static Config _conf;

Config& get_conf(void) { return _conf; }

int my_function(int a, int b, const Config& config = get_conf())
{
    if(config.addOrMultiply)
        return a + b;
    else
        return a * b;
}

int main(void)
{
    std::cout << my_function(2, 3) << std::endl;
    return 0;
}
Аргумент функции можно спрятать препроцессором, чтобы не ошибиться лишний раз.

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

Изначально странная идея каргокультить в Rust костыли из Haskell, придуманные для эмуляции императивщины в чистом языке.

Нет, для эмуляции императивщины в хацкелле есть другие штуки, а Reader — всего лишь эксплуатация того факта, что функции тоже являются монадами.

Рекомендованное решение - прокидывать иммутабельную ссылку на конфиг аргументом до первого стракта, там сохранять ссылку в поле и дальше использовать через self.

Я надеюсь, ты осознаёшь, что не все функции нужно совать в impl {}, и делать это только ради конфига — далеко не самая лучшая идея.

Если очень лень, или если нужно иногда менять конфиг, можно сделать статический Mutex<T>.

А если я хочу добавить тестирование с мокингом этого самого конфига?

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

Ты сейчас предлагаешь грязный хак, я правильно понимаю?

Я пробовал понять, как это можно сделать в расте. Но нихрена не понял.

Я так понял, что в расте даже глобальный хэшмап не объявить просто так. Для этого предлагают макрос lazystatic. Всё остальное я не осилил. М.б. кто-нибудь разбирающийся в расте расскажет, возможно ли там иметь static const и mut ссылку на него в main().

Но мой вопрос был не о расте, а о том, почему все не любят глобальную конфигурацию.

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

Ну а в Rust-е и ООП нормального нет, так что там все как-то печальнее должно быть, на первый взгляд.

Будет почти тоже самое: трейт + static.

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

Ну вот поэтому рекомендованная практика - прокидывать конфиг явным аргументом, карго так делает, получается тот же StateT только без сахара, можно тестировать с мокингом. Все как обычных императивных языках.

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

Ну вот поэтому рекомендованная практика - прокидывать конфиг явным аргументом

Ты когда-нибудь слышал про принцип don't repeat yourself?

Все как обычных императивных языках.

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

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

Даже так как в С++ не выходит. После бурного, но непродолжительного секса с растовскими макросами у меня получилось вот это:


fn get_state() -> String {
    return "default state".to_string();
}

fn test(a: i32, state: String) {
    println!("{} {}", state, a);
}

macro_rules! call {
    ($name:ident, $($a: expr),* ) => { $name( $($a)*, get_state()) };
}

fn main() {
    let local_state = "local state".to_string();
    test(12, local_state);
    call!(test, 9);
}
На выходе:
local state 12
default state 9
Может в тред всё-таки придёт кто-нибудь, кто пишет на расте и сделает лучше.

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

подходы, которые считаются каноничными в других языках

Каноничные - это типа хаскеля? Большинство проблем раста упирается в no-GC и zero-cost abstractions.

Например, в Rust нет человеческого Poll

Казалось бы, при чём тут язык.

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

Ну т.е. тебе ответ-то и не нужен, ты тред создал чтобы пофанбойствовать. В следующий раз теги «вброс» и «срач» не забудь поставить.

nonimous
()
Ответ на: комментарий от RazrFalcon

Каноничные - это типа хаскеля?

Каноничные — это языки, фишки которых Rust заимствовал и которые Rust так или иначе должен заменить. У меня возникает ощущение, что разработчики Rust на начальных этапах не очень хорошо понимали, что они хотят или не хотят включать в язык, и в результате мы имеем то что имеем.

Большинство проблем раста упирается в no-GC и zero-cost abstractions.

Отсутствие implicit arguments — это какой из этих вариантов?

Казалось бы, при чём тут язык.

Стандартная библиотека — часть языка. Удивительное рядом.

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

Ну т.е. тебе ответ-то и не нужен

Что удивительно, это один из тех тредов, где меня действительно интересовал ответ, пока мне не начали про ООП рассказывать.

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

Потому, что глобальная переменная не аналог ReaderT. Ты можешь прокидывать с помощью ReaderT в функцию каждый раз разное значение (конфиг), сколько угодно раз.

Т.е. проблема в отсутствии в расте аргументов по умолчанию?

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

Каноничные — это языки, фишки которых Rust заимствовал и которые Rust так или иначе должен заменить.

Он заимствует всё, что быстро работает. Тот же хаскель в плане производительность даже рядом с rust не стоял.

Отсутствие implicit arguments — это какой из этих вариантов?

Ни один. «Большинство» != всё. Ваш КО.

Стандартная библиотека — часть языка.

И в каких системных языках есть poll в std?

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

Тот же хаскель в плане производительность даже рядом с rust не стоял.

https://benchmarksgame.alioth.debian.org/u64q/compare.php?lang=ghc&lang2=...

Разница меньше порядка, во многих случаях меньше двух раз, что довольно таки близко. И это на синтетических задачах. В реальности у Haskell может быть преимущество за счёт развитого рантайма и GC, но тут всё целиком зависит от задачи.

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

Аргумент функции можно спрятать препроцессором, чтобы не ошибиться лишний раз.

Похоже, я вас изначально неправильно понял. Вероятно, в такой постановке задачи для C++ я бы вообще никакого ООП не притягивал за уши. А делали бы какие-то финты ушами с помощью лямбд и вариадик-шаблонов.

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

В половине тестов - да, а во второй половине отставание в 4-8 раз с космическим потреблением памяти.

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

Он заимствует всё, что быстро работает. Тот же хаскель в плане производительность даже рядом с rust не стоял.

Но-но! Хаскель по скорости находится на уровне .NET и Java

dave ★★★★★
()
Ответ на: комментарий от Weres
($name:ident, $($a: expr),* )

Я использовал спецификатор «tt» вместо «ident» и «expr», с ними не получилось.
call!(test, 9) немногим лучше test(9, get_state())

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

Проблема в отсутствии в Rust HKT и сахара для монад.

А в данном случае они что дают-то? Что можно один раз подставить для функции конфиг, вызывать много раз, потом подставить другой конфиг, и опять вызывать много раз не передавая конфиг аргументом? Или я во что-то совсем не въезжаю?

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

А потом ты из нескольких потоков поменяешь/почитаешь Config и всё, приехали.

const там для кого стоит?

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

Проблема в отсутствии в Rust HKT и сахара для монад.

Конкретно тут HKT мало что дадут. Вот автоматическое каррирование — да.

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

Хех

trait Chain<V> {
    fn go(self, f: Box<Fn(Self)->V>) -> V;
}

impl<U, V> Chain<V> for U {
    fn go(self, f: Box<Fn(U)->V>) -> V {
        (*f)(self)
    }
}

fn lift_r<S, U: 'static, V: 'static>(f: Box<Fn(U)->V>) -> Box<Fn((U,S))->(V,S)>
{
    Box::new(move |(u, s)| ((*f)(u), s) )
}

fn mul2(v: i32) -> i32 {
    v*2
}

fn add_state(s: (i32, i32)) -> (i32, i32) {
    (*lift_r(Box::new(move |v| v + s.1)))(s)
}

fn bx<U: 'static, V: 'static>(f: fn(U)->V) -> Box<Fn(U)->V> {
    Box::new(move |u|f(u))
}

fn main() {
    let result = (2,3).go(lift_r(bx(mul2))).go(bx(add_state));
    println!("{:?}", result);
}

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

Что можно один раз подставить для функции конфиг, вызывать много раз, потом подставить другой конфиг, и опять вызывать много раз не передавая конфиг аргументом?

Там (и ниже по стеку) каждый раз передаётся конфиг аргументом, ты просто этого не видишь.

theNamelessOne ★★★★★
()
static mut _config: i32 = 0;

fn setconfig(n: i32) {
    unsafe {
        _config = n;
    }
}

fn config() -> i32 {
    unsafe {
        _config
    }
}

Всё просто, понятно и очевидно. Настоящий Zero-cost.

Deleted
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.