LINUX.ORG.RU

Implicit function arguments в Rust

 


1

9

Привет, ЛОР.

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

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

★★★★★

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

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

а просто не писать

Куда не писать? Напиши подробнее, что именно ты имеешь ввиду.

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

Мне кажется, что конфиг имеет смысл только в районе функции main. А в более глубокие функции, значения из конфига передаются параметрами. Как-то странно, если у тебя через всё приложение просвистывает конфиг

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

А в более глубокие функции, значения из конфига передаются параметрами.

Да, можно и так, но это больше кода и, потенциально, больше проблем в будущем с его переписыванием, если где-то что-то понадобилось. Особенно если какое-то значение понадобится в функции, которая вызывается не напрямую из main, а где-то на втором-третьем уровне.

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

что такое «ReaderT».

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

Т.е. в данном примере конфиг будет передаваться дополнительным параметром повсюду, но для этого нужно написать всего полторы строчки кода.

Тут есть пример кода: http://blog.ssanj.net/posts/2014-09-23-A-Simple-Reader-Monad-Example.html

Обычная структура с const не подойдет?

Тут вопрос в том как структуру передавать дальше по стеку вызовов.

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

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

Еще немного и может им удастся изобрести инкапсуляцию и ООП

makoven ★★★★★
()

Я в хаскеле 0, но в rust переменная может попасть в функцию только через аргумент или из глобального пространства.

Лучший вариант - аргумент, ибо static/const в rust обязан быть thread-safe, из-за чего больше писанины.

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

Ну как раз задача ТС через наследование очень хорошо решается.

Через наследование она не решается.

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

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

Чем не наследование?

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

Ну как раз задача ТС через наследование очень хорошо решается.

Эм... А как ты уже установленные параметры-то собрался наследовать?

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

Правильно я понимаю, что ReaderT это чтобы писать в стиле объектного Си: method(opaque_struct, params...), но только «замкнуть» opaque_struct, чтоб не писать ее каждый раз?

То-есть типичное страдание по некоей целостной сущности со стейтом и поведением. В rust для этого есть трейты и self для доступа к стейту

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

Ну как сделать так, шоп у нас был один экземпляр Config, в котором выставлены нужные значения, а все потомки класса Config имеют к ним доступ?

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

ReaderT это чтобы писать в стиле объектного Си: method(opaque_struct, params...), но только «замкнуть» opaque_struct, чтоб не писать ее каждый раз?

Примерно. Только этот самый struct не обязательно opaque, и он будет передаваться неявно вниз по стеку вызовов.

В rust для этого есть трейты и self для доступа к стейту

Ты мне сейчас предлагаешь всю логику, зависащую от конфига, писать в impl Config {} штоле? Совсем наркоман?

TL;DR я хочу передавать конфиг вниз по стеку, но не хочу писать руками его передачу во все подряд функции. Так понятнее?

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

А какие проблемы?

Ну как тебе сказать... В том же C++ наследование определено для классов и структур. А не для экземпляров классов и структур :)

kirk_johnson ★☆
()

С точки зрения архитектуры есть такие варианты: 1. Глобальная переменная (плохо для тестирования), может быть плохо для многопоточности. 2. Threadlocal переменная. Может быть плохо для асинхронщины. 3. Прокидывать параметром/полем. Ну это скучно, но просто и работает. 4. Dependency Injection. Не знаю, как у раста с этим. 5. Implicit параметры. Разновидность 3 варианта, но прокидывается автоматически. Вроде у раста такого нет.

Я бы выбрал вариант 3 или 4.

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

Совсем наркоман?

Сгруппировать функции по сущностям. Сущность создавать entity::new(my_config). А внутри функций обращаться к конфигу через self

Хотя я бы так не делал, да. Херня получается. Лучше передавать в new только то, что ему действительно нужно

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

5. Implicit параметры. Разновидность 3 варианта, но прокидывается автоматически. Вроде у раста такого нет.

Я хочу примерно вот это, да.

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

Сущность создавать entity::new(my_config). А внутри функций обращаться к конфигу через self.

Т.е. ты предлагаешь по всюду раскидать копии конфига. Спасибо, но это немного не то. Проще тогда лишним параметром передавать.

hateyoufeel ★★★★★
() автор топика
Ответ на: комментарий от kirk_johnson
public class Base {
    protected static final Map<String, String> CONFIG = new HashMap<>();
}

public class Worker extend Base {
    public void work() {
        String server = CONFIG.get("server");
    }
}
Weres ★★★
()
Ответ на: комментарий от kirk_johnson

Я в курсе. Я и предлагал наследоваться от Config у нужного класса. Тогда получилась бы «такая штука, добавляющая к каждой функции, которая с ней объявлена, лишний невидимый аргумент, доступ к которому можно получить через специальный метод».

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

Я в курсе. Я и предлагал наследоваться от Config у нужного класса. Тогда получилась бы «такая штука, добавляющая к каждой функции, которая с ней объявлена, лишний невидимый аргумент, доступ к которому можно получить через специальный метод».

А какой смысл в этом, если там дефолтный для класса параметр?

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

но не хочу писать руками его передачу во все подряд функции

Можно функцию в макрос обернуть, но это извращение уже.

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

Смотря какой. Тут мы можем гарантировать потокобезопасность, ленивую инициализацию и константность.

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

ты предлагаешь по всюду раскидать копии конфига

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

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

Хуже. Я предлагаю вычленить из конфига нужное и передавать только это в конструктор. Вдруг придет в голову переиспользовать часть кода? И придется тебе воссоздавать ради одной функции всю структуру конфига

А потом на третьем уровне вложенности в функции fuckYourMum мне потребуется получить параметр hugeAss. И чо мне делать?

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

Есть код, который пишется для переиспользования, есть код, который пишется для решения конкретной задачи. Это разные подходы с разными требованиями. Не нужно везде пытаться писать код, который может быть когда-нибудь можно будет переиспользовать. Ты сразу вносишь все минусы ради возможных плюсов в будущем.

Ну и в твоём подходе правильно делать отдельный конфиг для твоего модуля (если это не 1-2 значения), делать адаптер для этого отдельного конфига от общего конфига и передавать уже этот отдельный конфиг. Т.е. куча работы и кода, который желательно помимо прочего тестировать и тд, ради эфемерного преимущества. Вот когда решишь переиспользовать, тогда и сделаешь рефакторинг.

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

Инкапсуляция есть, наследование есть. Что нужно, чтобы ООП стало причем?

Они тут есть по одной причине — в жабе нет глобальных переменных. Если бы они там были, то все это наследование стало бы слегка не нужным.

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

Доступ к глобальной переменной ограничить нельзя. В моём примере он ограничен - только для наследников класса. Это такая же глобальная переменная, как и ReaderT из оп поста.

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

есть код, который пишется для решения конкретной задачи

Тот самый, который вблизи main. И его как правило не так уж и много. Хотя, я проектов больше 20к строк не писал. Так что слова мои ничего не стоят

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

Это такая же глобальная переменная, как и ReaderT из оп поста.

Что ты несёшь, поехавший? ReaderT позволяет неявно передавать read-only значение, которое можно подменить без жопной боли в любом месте, а не даёт доступ к глобальному изменяемому массиву.

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

Доступ к глобальной переменной ограничить нельзя.

Кто сказал модули? Хотя ладно, даже в C это можно через #include'ы сделать.

В моём примере он ограничен - только для наследников класса. Это такая же глобальная переменная, как и ReaderT из оп поста.

Неа. Твою глобальную переменную можно отнаследовать любым другим классом и читать её из соседнего потока. И если тебе нужно ДВА состояния, то тебе нужно делать два разных класса с разным набором состояний.

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

отнаследовать любым другим классом

Это сделает его наследником, о чём я и говорил. Сравнение с ReaderT неудачное, признаю. Для неявной передачи параметров в ООП лучше просто использовать поля конкретного объекта. Но проблема описанная в вашем изначальном посте:

Ну как сделать так, шоп у нас был один экземпляр Config, в котором выставлены нужные значения, а все потомки класса Config имеют к ним доступ?

таки решена.

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

Ну как тебе сказать... В том же C++ наследование определено для классов и структур.

В C++ решение могло бы выглядеть как-то вот так:

#include <utility>

struct Config {
	int a_;
	int b_;
};

class Config_holder {
	const Config cfg_;
public :
	Config_holder(Config cfg) : cfg_(std::move(cfg)) {}

	const Config & config() const { return cfg_; }
};

class App : public Config_holder {
	int c_;
public :
	App(Config cfg, int c) : Config_holder(std::move(cfg)), c_(c) {}

	int f() {
		return c_ + config().a_;
	}
};

class Another_app : public Config_holder {
	int d_;
public :
	Another_app(Config cfg, int d) : Config_holder(std::move(cfg)), d_(d) {}

	int g() {
		return d_ + App{Config{config().a_ + 1, config().b_}, d_ - 3}.f();
	}
};

int main() {
	Another_app app{Config{3, 4}, 25};

	app.g();
}
Здесь config следовало бы протягивать только в конструкторы объектов App и Another_app, а все, что работает внутри этих объектов, уже не нуждается в дополнительном аргументе.

В принципе, аналогично можно было бы сделать и в Rust-а, полагаю, только там вместо наследования от Config_holder в App и AnotherApp нужно было бы просто инкапсулировать экземпляр структуры Config внутри структур App и AnotherApp.

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