↑Programmieren in Rust
Bei Lazy Evaluation, auch Bedarfsauswertung genannt, wird ein Ausdruck nicht sofort ausgewertet, sondern erst dann, wenn der Wert auch benötigt wird.
Wir können Closures zur Implementation von Lazy Evaluation heranziehen.
macro_rules! lazy { ($x:expr) => {Lazy {value: Box::new(move || $x)}} } struct Lazy<T> { value: Box<dyn Fn()->T> } impl<T> Lazy<T> { fn force(&self) -> T {(self.value)()} }
Als konkretes Beispiel soll ein unendlicher Baum formuliert werden.
struct Tree{ left: Lazy<Tree>, right: Lazy<Tree>, value: i32 } fn tree(x: i32) -> Tree { Tree { value: x, left: lazy!{tree(x)}, right: lazy!{tree(x + 1)} } } fn main() { println!("{}", tree(0).right.force().left.force().value ); }
Der Baum hat demnach die folgende Gestalt:
0 / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ 0 1 / \ / \ / \ / \ / \ / \ / \ / \ 0 1 1 2 / \ / \ / \ / \ 0 1 1 2 1 2 2 3 / \ / \ / \ / \ / \ / \ / \ / \ .. ... .. .. ... .. .. ... .. .. ... ..
Auch if-Ausdrücke lassen sich mit Lazy Evaluation darstellen:
fn if_cond<T>(cond: bool, a: Lazy<T>, b: Lazy<T>) -> T { (if cond {a} else {b}).force() } fn fac(n: u32) -> u32 { if_cond(n == 0, lazy!{1}, lazy!{n*fac(n - 1)}) }
Betrachten wir das folgende Programm:
fn log(value: i32) -> i32 { println!("Auswertung"); value } fn main() { let x = lazy!{log(0)}; println!("{}", x.force()); println!("{}", x.force()); }
Hier findet die Berechnung des Wertes x
zweimal
statt. Bei referentiell transparenten Funktionen ist die Wiederholung
von Berechnungen eigentlich nicht notwendig. Diesem Umstand könnte man
so Rechnung tragen:
use std::cell::RefCell; macro_rules! lazy { ($x:expr) => {Lazy {cell: RefCell::new( Thunk::Calc(Box::new(move || $x)) )}} } enum Thunk<T> { Calc(Box<dyn Fn() -> T>), Value(T) } struct Lazy<T> { cell: RefCell<Thunk<T>> } impl<T: Clone> Lazy<T> { fn force(&self) -> T { let mut cell = self.cell.borrow_mut(); match &*cell { Thunk::Calc(calc) => { let value = calc(); *cell = Thunk::Value(value.clone()); value }, Thunk::Value(value) => { value.clone() } } } }
Es verbleiben noch zwei Probleme. Zum einen würde man Fn
gerne durch FnOnce
ersetzen. Zum anderen muss man
T: Clone
entferenen und stattdessen x.force()
vom Typ &T
bzw. &mut T
haben.