Реализация вычислительного графа в Rust - PullRequest
0 голосов
/ 24 октября 2018

Я пытаюсь реализовать в Rust движок, похожий на ReactiveX (например, похожий на RxJava / RxJS и его друзья), но ориентированный только на синхронные вычисления, с акцентом на кэширование промежуточных значений.Это похоже на стандартные итераторы библиотеки, но в обратном направлении.

Мне нужно реализовать игрушечный прототип шаблона Observable / Observer: Playground - и, хотя в конце концов это не совсем то, чего я хочудемонстрирует общий дух вещей.

По сути, я бы хотел, чтобы код, похожий на этот (псевдокод):

let mut input = Input();
let mut x = input.map(|i| i * 2);
// unavoidable multiple mut borrow?..
let mut y = x.fork().map(|x| x + 3) // [must_use]
y.subscribe(|y| println!("y={}", y)); // moved
let mut z = x.fork().map(|x| x - 4).do_more_stuff(); // [must_use]
z.subscribe(|z| println!("z={}", z)); // moved
// some time later:
input.feed(42);

был развернут примерно до

|i| {
    let x = i * 2;
    let y = x + 3;
    println!("y={}", y);
    let z = do_more_stuff(x - 4);
    println!("z={}", z);
}

Здесь важен синтаксис, я знаю, как сделать это «в обратном направлении» или значительно изменить его, чтобы он работал, но я бы хотел сохранить синтаксис «сверху вниз», если это возможно, для удобства чтения, «вилки»'в частности.

Таким образом, вы можете думать о нем как о ориентированном графе с одним входом вверху, и каждый узел выполняет некоторые вычисления и передает результат (возможно, несколько раз, или, возможно, не передает его)до нижних уровней, потенциально «разветвляющихся», так что значение может быть передано нескольким узлам более низкого уровня без повторного вычисления.В самом низу находятся «подписчики / наблюдатели», которые могут слушать ценности и делать с ними что угодно.После создания этот граф (т. Е. Структура графа) является полностью неизменным.

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

Я потратил значительное количество времени на размышления и создание прототипов в Rust, но не могу найти хорошее решение, которое было бы быстрым и «красивым», то естьэргономичный, как в приведенных выше образцах.Например, с fork() (который разделяет поток) требуемый синтаксис сразу подразумевает множественные изменяемые заимствования для родителя, даже если потом ребенок перемещается?

Основные идеи (которые реализованына игровой площадке, связанной выше), которая, я думаю, сработает: концепция Observer - в основном черта, требующая от разработчика реализации on_next(&mut self, value: T), чтобы любой FnMut(T) -> () мог автоматически реализовать это.Затем «ветвь» - это то, что содержит список Box<Observer>, чтобы он мог их вызывать.Возможно, «поток / наблюдаемый», который может принять наблюдателя, который подпишется на него (изменяемым образом?).

В любом случае, может быть, я бью себя по голове о хорошо известную стену здесь или, может быть, тамЕсть лучшие решения для этого (или, возможно, это уже где-то реализовано) - любые мысли будут высоко оценены.

1 Ответ

0 голосов
/ 26 октября 2018

Это возможно.В основном, это работает :

let input = Input::<i64>::new();

let s1 = input.fork();
s1.subscribe(|x| {
    println!("s1: {}", x)
});

let s2 = input.fork();
s2.map(|x| x * 3).subscribe(|x| {
    println!("s2: {}", x)
});

let s3 = input.fork().map(|x| {
    let s3 = (x as f64) + 100.5;
    println!("s3: {}", s3);
    s3
}).share();

s3.fork().map(|x| x + 1.).subscribe(|x| println!("s4: {}", x));

s3.fork().map(|x| x + 2.).subscribe(|x| println!("s5: {}", x));

input.feed(1);
println!("---");
input.feed(2);

и выводит

s1: 1
s2: 3
s3: 101.5
s4: 102.5
s5: 103.5
---
s1: 2
s2: 6
s3: 102.5
s4: 103.5
s5: 104.5

В зависимости от реализации: Rc<RefCell<Vec<Box<Trait>>>> и множество маркеров времени жизни ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...