在Rust中实现计算图

时间:2018-10-24 00:01:06

标签: rust

我正在尝试在Rust中实现类似于ReactiveX的(例如,与RxJava / RxJS和它的朋友类似)图形引擎,但仅适用于同步计算,重点是中间值缓存。有点像标准库迭代器,但是向后。

我必须实现一个可观察/观察者模式的玩具原型: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框架的实现中,由于该值将存在于父闭包中,因此无需显式地将值作为struct字段携带即可。

我在Rust中花了很多时间思考和制作原型,但是似乎无法像上面的示例中那样,找到一种既快捷又“不错”的人机工程学解决方案。例如,使用fork()(用于拆分流),所需的语法类型立即意味着向父级多次可变借用,即使子级后来被移动了?

我认为可行的核心思想(在上面的操场中实现)是:观察者概念-基本上,一个特性要求实现者实现on_next(&mut self, value: T),以便任何FnMut(T) -> ()可以自动实现。然后,“叉子”就是包含Box<Observer>列表的东西,以便它可以调用它们。也许“流/可观察”可以接受将订阅它的观察者(以可变的方式?)。

无论如何,也许我在这里碰到一个众所周知的墙,或者对此有更好的解决方案(或者也许已经在某个地方实现了)-任何想法都将不胜感激。

1 个答案:

答案 0 :(得分:0)

有可能。基本上,this works

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>>>>和许多生命周期标记...