Skip to content

Commit

Permalink
refactor(variadics): Improvements prepping for release (#974)
Browse files Browse the repository at this point in the history
- Adds the "spread"/"splat" `...` syntax to the three variadics macros.
- Adds `#[sealed]` traits.
- Adds testing of error messages.
- Improves docs: `README.md` and Rust docs.
  • Loading branch information
MingweiSamuel authored Dec 22, 2023
1 parent 060de2b commit 7e65a08
Show file tree
Hide file tree
Showing 42 changed files with 1,453 additions and 939 deletions.
1,466 changes: 819 additions & 647 deletions Cargo.lock

Large diffs are not rendered by default.

35 changes: 14 additions & 21 deletions hydroflow/src/scheduled/handoff/handoff_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use ref_cast::RefCast;
use sealed::sealed;
use variadics::Variadic;
use variadics::{variadic_trait, Variadic};

use super::Handoff;
use crate::scheduled::graph::HandoffData;
Expand Down Expand Up @@ -140,24 +140,17 @@ where
}
}

/// A variadic list of Handoff types, represented using a lisp-style tuple structure.
///
/// This trait is sealed and not meant to be implemented or used directly. Instead tuple lists (which already implement this trait) should be used, for example:
/// ```ignore
/// type MyHandoffList = (VecHandoff<usize>, (VecHandoff<String>, (TeeingHandoff<u32>, ())));
/// ```
/// The [`var_expr!`](variadics::var_expr) macro simplifies usage of this kind:
/// ```ignore
/// type MyHandoffList = var_expr!(VecHandoff<usize>, VecHandoff<String>, TeeingHandoff<u32>);
/// ```
#[sealed]
pub trait HandoffList: Variadic {}
#[sealed]
impl<H, L> HandoffList for (H, L)
where
H: 'static + Handoff,
L: HandoffList,
{
variadic_trait! {
/// A variadic list of Handoff types, represented using a lisp-style tuple structure.
///
/// This trait is sealed and not meant to be implemented or used directly. Instead tuple lists (which already implement this trait) should be used, for example:
/// ```ignore
/// type MyHandoffList = (VecHandoff<usize>, (VecHandoff<String>, (TeeingHandoff<u32>, ())));
/// ```
/// The [`var_expr!`](crate::var) macro simplifies usage of this kind:
/// ```ignore
/// type MyHandoffList = var_expr!(VecHandoff<usize>, VecHandoff<String>, TeeingHandoff<u32>);
/// ```
#[sealed]
pub variadic<T> HandoffList where T: 'static + Handoff {}
}
#[sealed]
impl HandoffList for () {}
2 changes: 1 addition & 1 deletion hydroflow/tests/compile-fail/surface_demuxenum_notenum.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use hydroflow::{hydroflow_syntax, var_args};
use hydroflow::hydroflow_syntax;

fn main() {
struct Shape {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use hydroflow::util::demux_enum::DemuxEnum;
use hydroflow::{hydroflow_syntax, var_args};
use hydroflow::hydroflow_syntax;

fn main() {
#[derive(DemuxEnum)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,3 @@ error: Output connection conflicts with above ($DIR/tests/compile-fail/surface_d
|
21 | my_demux[Square] -> for_each(std::mem::drop);
| ^^^^^^

warning: unused import: `var_args`
--> tests/compile-fail/surface_demuxenum_port_duplicate.rs:2:35
|
2 | use hydroflow::{hydroflow_syntax, var_args};
| ^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use hydroflow::util::demux_enum::DemuxEnum;
use hydroflow::{hydroflow_syntax, var_args};
use hydroflow::hydroflow_syntax;

fn main() {
#[derive(DemuxEnum)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,3 @@ error: Output port from `demux_enum(..)` must be specified and must be a valid i
|
22 | my_demux -> for_each(std::mem::drop);
| ^^^^^^^^

warning: unused import: `var_args`
--> tests/compile-fail/surface_demuxenum_port_elided.rs:2:35
|
2 | use hydroflow::{hydroflow_syntax, var_args};
| ^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use hydroflow::util::demux_enum::DemuxEnum;
use hydroflow::{hydroflow_syntax, var_args};
use hydroflow::hydroflow_syntax;

fn main() {
#[derive(DemuxEnum)]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use hydroflow::util::demux_enum::DemuxEnum;
use hydroflow::{hydroflow_syntax, var_args};
use hydroflow::hydroflow_syntax;

fn main() {
#[derive(DemuxEnum)]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use hydroflow::util::demux_enum::DemuxEnum;
use hydroflow::{hydroflow_syntax, var_args};
use hydroflow::hydroflow_syntax;

fn main() {
#[derive(DemuxEnum)]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use hydroflow::util::demux_enum::DemuxEnum;
use hydroflow::{hydroflow_syntax, var_args};
use hydroflow::hydroflow_syntax;

fn main() {
#[derive(DemuxEnum)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,3 @@ error: Output connection conflicts with above ($DIR/tests/compile-fail/surface_d
|
21 | my_demux[Square] -> for_each(std::mem::drop);
| ^^^^^^

warning: unused import: `var_args`
--> tests/compile-fail/surface_demuxenum_wrongenum.rs:2:35
|
2 | use hydroflow::{hydroflow_syntax, var_args};
| ^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use hydroflow::util::demux_enum::DemuxEnum;
use hydroflow::{hydroflow_syntax, var_args};
use hydroflow::hydroflow_syntax;

fn main() {
#[derive(DemuxEnum)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,3 @@ error: Output connection conflicts with above ($DIR/tests/compile-fail/surface_d
|
17 | my_demux[Square] -> for_each(std::mem::drop);
| ^^^^^^

warning: unused import: `var_args`
--> tests/compile-fail/surface_demuxenum_wrongitems.rs:2:35
|
2 | use hydroflow::{hydroflow_syntax, var_args};
| ^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use hydroflow::{hydroflow_syntax, var_args};
use hydroflow::hydroflow_syntax;

#[hydroflow::main]
async fn main() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ error[E0308]: mismatched types
| |_____- arguments to this function are incorrect
|
note: function defined here
--> $CARGO/tokio-1.32.0/src/time/interval.rs
--> $CARGO/tokio-1.35.1/src/time/interval.rs
|
| pub fn interval(period: Duration) -> Interval {
| ^^^^^^^^
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ expression: "df.meta_graph().unwrap().to_dot(&Default::default())"
digraph {
node [fontname="Monaco,Menlo,Consolas,&quot;Droid Sans Mono&quot;,Inconsolata,&quot;Courier New&quot;,monospace", style=filled];
edge [fontname="Monaco,Menlo,Consolas,&quot;Droid Sans Mono&quot;,Inconsolata,&quot;Courier New&quot;,monospace"];
n1v1 [label="(n1v1) source_iter(vec!((2, 'y'), (3, 'x'), (1, 'z')))", shape=invhouse, fillcolor="#88aaff"]
n1v1 [label="(n1v1) source_iter(vec![(2, 'y'), (3, 'x'), (1, 'z')])", shape=invhouse, fillcolor="#88aaff"]
n2v1 [label="(n2v1) sort_by_key(|(k, _v)| k)", shape=invhouse, fillcolor="#88aaff"]
n3v1 [label="(n3v1) for_each(|v| println!(\"{:?}\", v))", shape=house, fillcolor="#ffff88"]
n4v1 [label="(n4v1) handoff", shape=parallelogram, fillcolor="#ddddff"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ classDef pullClass fill:#8af,stroke:#000,text-align:left,white-space:pre
classDef pushClass fill:#ff8,stroke:#000,text-align:left,white-space:pre
classDef otherClass fill:#fdc,stroke:#000,text-align:left,white-space:pre
linkStyle default stroke:#aaa
1v1[\"(1v1) <code>source_iter(vec!((2, 'y'), (3, 'x'), (1, 'z')))</code>"/]:::pullClass
1v1[\"(1v1) <code>source_iter(vec![(2, 'y'), (3, 'x'), (1, 'z')])</code>"/]:::pullClass
2v1[\"(2v1) <code>sort_by_key(|(k, _v)| k)</code>"/]:::pullClass
3v1[/"(3v1) <code>for_each(|v| println!(&quot;{:?}&quot;, v))</code>"\]:::pushClass
4v1["(4v1) <code>handoff</code>"]:::otherClass
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ digraph {
node [fontname="Monaco,Menlo,Consolas,&quot;Droid Sans Mono&quot;,Inconsolata,&quot;Courier New&quot;,monospace", style=filled];
edge [fontname="Monaco,Menlo,Consolas,&quot;Droid Sans Mono&quot;,Inconsolata,&quot;Courier New&quot;,monospace"];
n1v1 [label="(n1v1) source_iter([()])", shape=invhouse, fillcolor="#88aaff"]
n2v1 [label="(n2v1) for_each(|()| {\l println!(\l \"Current tick: {}, stratum: {}\", context.current_tick(), context\l .current_stratum()\l )\l})\l", shape=house, fillcolor="#ffff88"]
n2v1 [label="(n2v1) for_each(|()| {\l println!(\l \"Current tick: {}, stratum: {}\",\l context.current_tick(),\l context.current_stratum(),\l )\l})\l", shape=house, fillcolor="#ffff88"]
n1v1 -> n2v1
subgraph "cluster n1v1" {
fillcolor="#dddddd"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ classDef pushClass fill:#ff8,stroke:#000,text-align:left,white-space:pre
classDef otherClass fill:#fdc,stroke:#000,text-align:left,white-space:pre
linkStyle default stroke:#aaa
1v1[\"(1v1) <code>source_iter([()])</code>"/]:::pullClass
2v1[/"<div style=text-align:center>(2v1)</div> <code>for_each(|()| {<br> println!(<br> &quot;Current tick: {}, stratum: {}&quot;, context.current_tick(), context<br> .current_stratum()<br> )<br>})</code>"\]:::pushClass
2v1[/"<div style=text-align:center>(2v1)</div> <code>for_each(|()| {<br> println!(<br> &quot;Current tick: {}, stratum: {}&quot;,<br> context.current_tick(),<br> context.current_stratum(),<br> )<br>})</code>"\]:::pushClass
1v1-->2v1
subgraph sg_1v1 ["sg_1v1 stratum 0"]
1v1
Expand Down
68 changes: 45 additions & 23 deletions relalg/testdata/compile/compile
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ compile
----
fn main() {
let __values_1 = vec![
vec![ScalarExpr::Literal(Datum::Int(1i64)).eval(& Vec::new()),
ScalarExpr::Literal(Datum::Int(2i64)).eval(& Vec::new()),
ScalarExpr::Literal(Datum::Int(3i64)).eval(& Vec::new())]
vec![
ScalarExpr::Literal(Datum::Int(1i64)).eval(&Vec::new()),
ScalarExpr::Literal(Datum::Int(2i64)).eval(&Vec::new()),
ScalarExpr::Literal(Datum::Int(3i64)).eval(&Vec::new()),
],
]
.into_iter();
for row in __values_1 {
Expand All @@ -24,9 +26,11 @@ compile
----
fn main() {
let __values_2 = vec![
vec![ScalarExpr::Literal(Datum::Int(1i64)).eval(& Vec::new()),
ScalarExpr::Literal(Datum::Int(2i64)).eval(& Vec::new()),
ScalarExpr::Literal(Datum::Int(3i64)).eval(& Vec::new())]
vec![
ScalarExpr::Literal(Datum::Int(1i64)).eval(&Vec::new()),
ScalarExpr::Literal(Datum::Int(2i64)).eval(&Vec::new()),
ScalarExpr::Literal(Datum::Int(3i64)).eval(&Vec::new()),
],
]
.into_iter();
let __filter_1 = __values_2
Expand All @@ -53,12 +57,16 @@ compile
----
fn main() {
let __values_2 = vec![
vec![ScalarExpr::Literal(Datum::Int(1i64)).eval(& Vec::new()),
ScalarExpr::Literal(Datum::Int(2i64)).eval(& Vec::new()),
ScalarExpr::Literal(Datum::Int(3i64)).eval(& Vec::new())],
vec![ScalarExpr::Literal(Datum::Int(4i64)).eval(& Vec::new()),
ScalarExpr::Literal(Datum::Int(5i64)).eval(& Vec::new()),
ScalarExpr::Literal(Datum::Int(6i64)).eval(& Vec::new())]
vec![
ScalarExpr::Literal(Datum::Int(1i64)).eval(&Vec::new()),
ScalarExpr::Literal(Datum::Int(2i64)).eval(&Vec::new()),
ScalarExpr::Literal(Datum::Int(3i64)).eval(&Vec::new()),
],
vec![
ScalarExpr::Literal(Datum::Int(4i64)).eval(&Vec::new()),
ScalarExpr::Literal(Datum::Int(5i64)).eval(&Vec::new()),
ScalarExpr::Literal(Datum::Int(6i64)).eval(&Vec::new()),
],
]
.into_iter();
let __filter_1 = __values_2
Expand Down Expand Up @@ -96,22 +104,36 @@ compile
----
fn main() {
let __values_2 = vec![
vec![ScalarExpr::Literal(Datum::Int(1i64)).eval(& Vec::new()),
ScalarExpr::Literal(Datum::Int(2i64)).eval(& Vec::new()),
ScalarExpr::Literal(Datum::Int(3i64)).eval(& Vec::new())],
vec![ScalarExpr::Literal(Datum::Int(4i64)).eval(& Vec::new()),
ScalarExpr::Literal(Datum::Int(5i64)).eval(& Vec::new()),
ScalarExpr::Literal(Datum::Int(6i64)).eval(& Vec::new())]
vec![
ScalarExpr::Literal(Datum::Int(1i64)).eval(&Vec::new()),
ScalarExpr::Literal(Datum::Int(2i64)).eval(&Vec::new()),
ScalarExpr::Literal(Datum::Int(3i64)).eval(&Vec::new()),
],
vec![
ScalarExpr::Literal(Datum::Int(4i64)).eval(&Vec::new()),
ScalarExpr::Literal(Datum::Int(5i64)).eval(&Vec::new()),
ScalarExpr::Literal(Datum::Int(6i64)).eval(&Vec::new()),
],
]
.into_iter();
let __project_1 = __values_2
.map(|row| {
vec![
ScalarExpr::Plus(Box::new(ScalarExpr::ColRef(0usize)),
Box::new(ScalarExpr::Literal(Datum::Int(1i64)))).eval(& row),
ScalarExpr::Plus(Box::new(ScalarExpr::Literal(Datum::Int(5i64))),
Box::new(ScalarExpr::Plus(Box::new(ScalarExpr::ColRef(1usize)),
Box::new(ScalarExpr::ColRef(2usize))))).eval(& row)
ScalarExpr::Plus(
Box::new(ScalarExpr::ColRef(0usize)),
Box::new(ScalarExpr::Literal(Datum::Int(1i64))),
)
.eval(&row),
ScalarExpr::Plus(
Box::new(ScalarExpr::Literal(Datum::Int(5i64))),
Box::new(
ScalarExpr::Plus(
Box::new(ScalarExpr::ColRef(1usize)),
Box::new(ScalarExpr::ColRef(2usize)),
),
),
)
.eval(&row),
]
});
for row in __project_1 {
Expand Down
6 changes: 6 additions & 0 deletions variadics/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,9 @@ edition = "2021"
license = "Apache-2.0"
documentation = "https://docs.rs/variadics/"
description = "Variadic generics on stable Rust using tuple lists"

[dependencies]
sealed = "0.5"

[dev-dependencies]
trybuild = "1.0"
74 changes: 74 additions & 0 deletions variadics/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Variadics

Variadic generics in stable Rust

## Variadic Generics?

Variadic generics are one of the most discussed potential Rust features. They would enable
traits, functions, and data structures to be generic over variable length tuples of arbitrary
types.

Currently you can only implement generic code for tuples of a specific length. If you want to
handle tuples of varying lengths you must write a separate implementation for each length. This
leads to the notorious limitation that traits in Rust generally
[only apply for tuples up to length 12](https://doc.rust-lang.org/std/primitive.tuple.html#impl-From%3C(T,+T,+T,+T,+T,+T,+T,+T,+T,+T,+T,+T)%3E-for-%5BT;+12%5D).

Variadic generics allow generic code to handle tuples of any length.

## Tuple lists

Although variadic generics fundamentally require changing the Rust compiler, we can emulate
pretty well with tuple lists.

Any tuple `(A, B, C, D)` can be mapped to (and from) a recursive tuple `(A, (B, (C, (D, ()))))`.

Each element consists of a nested pair `(Item, Rest)`, where `Item` is tuple element and `Rest`
is the rest of the list. For last element `Rest` is a unit tuple `()`. Unlike regular flat
tuples, these recursive tuples can be effectively reasoned about in stable Rust.

You may recognize this fundamental structure from [`cons` lists in Lisp](https://en.wikipedia.org/wiki/Cons#Lists)
as well as [`HList`s in Haskell](https://hackage.haskell.org/package/HList/docs/Data-HList-HList.html).

This crate calls these lists "variadics" and provides traits and macros to allow simple,
ergonimc use of them.

## Usage

[`var_expr!`] creates variadic values/expressions,
[`var_type!`] creates variadic types,
and [`var_args!`] creates variadic patterns (used in unpacking arguments, on the left side of `let`
declarations, etc.).

These macros support the "spread" syntax `...`, also known as "splat". For example, `var_expr!(a, ...var_b, ...var_c, d)`
will concatenate `a`, the items of `var_b`, the items of `var_c` and `d` together into a single
variadic list.

## Acknowledgements

This crate is based on [`tuple_list` by VFLashM](https://github.com/VFLashM/tuple_list), which is MIT licensed:

<details>
<summary>MIT license</summary>

```text
Copyright (c) 2020 Valerii Lashmanov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE
```
</details>
Loading

0 comments on commit 7e65a08

Please sign in to comment.