Skip to content

Commit 0973cdd

Browse files
authored
Merge pull request #40 from ecstatic-morse/passthrough-leaper
Add a helper function for "passthrough" leapers
2 parents 5bda2f0 + dfda7fc commit 0973cdd

File tree

3 files changed

+74
-1
lines changed

3 files changed

+74
-1
lines changed

src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub use crate::{
2929
extend_with::ExtendWith,
3030
filter_anti::FilterAnti,
3131
filter_with::FilterWith,
32-
filters::{PrefixFilter, ValueFilter},
32+
filters::{passthrough, PrefixFilter, ValueFilter},
3333
Leaper, Leapers, RelationLeaper,
3434
},
3535
variable::Variable,

src/test.rs

+26
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,29 @@ fn leapjoin_from_extend() {
193193

194194
assert_eq!(variable.elements, vec![(2, 2), (2, 4)]);
195195
}
196+
197+
#[test]
198+
fn passthrough_leaper() {
199+
let mut iteration = Iteration::new();
200+
201+
let variable = iteration.variable::<(u32, u32)>("variable");
202+
variable.extend((0..10).map(|i| (i, i)));
203+
204+
while iteration.changed() {
205+
variable.from_leapjoin(
206+
&variable,
207+
(
208+
crate::passthrough(), // Without this, the test would fail at runtime.
209+
crate::PrefixFilter::from(|&(i, _)| i <= 20),
210+
),
211+
|&(i, j), ()| (2*i, 2*j),
212+
);
213+
}
214+
215+
let variable = variable.complete();
216+
217+
let mut expected: Vec<_> = (0..10).map(|i| (i, i)).collect();
218+
expected.extend((10..20).filter_map(|i| (i%2 == 0).then(|| (i, i))));
219+
expected.extend((20..=40).filter_map(|i| (i%4 == 0).then(|| (i, i))));
220+
assert_eq!(&*variable, &expected);
221+
}

src/treefrog.rs

+47
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,53 @@ pub(crate) mod filters {
190190
}
191191
}
192192

193+
pub struct Passthrough<Tuple> {
194+
phantom: ::std::marker::PhantomData<Tuple>,
195+
}
196+
197+
impl<Tuple> Passthrough<Tuple> {
198+
fn new() -> Self {
199+
Passthrough {
200+
phantom: ::std::marker::PhantomData,
201+
}
202+
}
203+
}
204+
205+
impl<'leap, Tuple> Leaper<'leap, Tuple, ()> for Passthrough<Tuple> {
206+
/// Estimates the number of proposed values.
207+
fn count(&mut self, _prefix: &Tuple) -> usize {
208+
1
209+
}
210+
/// Populates `values` with proposed values.
211+
fn propose(&mut self, _prefix: &Tuple, values: &mut Vec<&'leap ()>) {
212+
values.push(&())
213+
}
214+
/// Restricts `values` to proposed values.
215+
fn intersect(&mut self, _prefix: &Tuple, _values: &mut Vec<&'leap ()>) {
216+
// `Passthrough` never removes values (although if we're here it indicates that the user
217+
// didn't need a `Passthrough` in the first place)
218+
}
219+
}
220+
221+
/// Returns a leaper that proposes a single copy of each tuple from the main input.
222+
///
223+
/// Use this when you don't need any "extend" leapers in a join, only "filter"s. For example,
224+
/// in the following datalog rule, all terms in the second and third predicate are bound in the
225+
/// first one (the "main input" to our leapjoin).
226+
///
227+
/// ```prolog
228+
/// error(loan, point) :-
229+
/// origin_contains_loan_at(origin, loan, point), % main input
230+
/// origin_live_at(origin, point),
231+
/// loan_invalidated_at(loan, point).
232+
/// ```
233+
///
234+
/// Without a passthrough leaper, neither the filter for `origin_live_at` nor the one for
235+
/// `loan_invalidated_at` would propose any tuples, and the leapjoin would panic at runtime.
236+
pub fn passthrough<Tuple>() -> Passthrough<Tuple> {
237+
Passthrough::new()
238+
}
239+
193240
/// A treefrog leaper based on a predicate of prefix and value.
194241
/// Use like `ValueFilter::from(|tuple, value| ...)`. The closure
195242
/// should return true if `value` ought to be retained. The

0 commit comments

Comments
 (0)