Report on variadic generics discussion at RustNL

Discussion on r/rust:

https://www.reddit.com/r/rust/comments/1d0coao/report_on_variadic_generics_discussion_at_rustnl/

3 Likes

Most of the time I've wanted variadics, the route I've gone is to macro-implement interconversion between tuples and hlists, and then use type-level recursion to actually implement the desired functionality for the hlists. Though I wouldn't exactly call it ergonomic, that's sufficient to allow arbitrary transformations (up to whatever tuple length is implemented, of course).

3 Likes

I wonder how applicable approach a-la ghc generics would be - http://dreixel.net/research/pdf/gdmh.pdf

Rust uses algebraic data types (ADT) meaning any struct is an algebraic product and any enum is a sum of products. So for any type you can automatically derive a representation type that consists only of about 7 different types and conversion functions between this type and representation type - both ways.

Next if you want to make some trait or a function that can be described via operations on individual fields - you implement your trait/function to work with representation type and convert the data user passes to the representation type.

With enough inlining (at least Haskell compiler) is able to completely optimize away this intermediate representation and generates code as efficient as it was implemented directly.

In addition to working as variadic generics this also gives access to things like structs/variant/field names, in case of Rust it should be possible to give access to doc strings.

If there's any interest I can try to write some sample code to how it might look like.

That kind of sounds like the first class types approach in the previous blog post? I count myself in this camp, but I don't have enough PL/type system expertise for that to mean much.

I don't think it's the same approach, but I wouldn't mind getting some of that as well. The idea here is to map datatypes to a generic representation and to work with that. Say I want a trait that adds up all the numbers in a struct:

// this is my original type
struct Point { x: usize, y: usize, z: usize }

compiler generates all of this

// This is an example representation, 
// Meta holds names for struct and top level doc comments,
// Prod is a simple structural thing that takes a  bunch of fields and makes them into a "binary tree" //  of types
// and K holds indication that this is a field, it can hold field name too 
type PointRep = Meta<NamesEtc, 
    Prod<
        Prod<K<usize>, K<usize>>, 
        K<usize>>;

fn to_rep(point: Point) -> PointRep { todo!() }
fn from_rep(rep: PointRep) -> Point { todo!() }

Trait or generic function developer writes all of this

// sample trait for grabbing numbers
trait NumGrab {
    fn sum(&self) -> usize
}

// Meta is a simple wrapper, we don't care about the info, only about the internal type
impl <T: NumGrab> NumGrab for Meta<T> {
    fn sum(&self) { self.0.sum() }
}

// Prod contains a structure, but we care about the fields
impl <A: NumGrab, B: NumGrab> NumGrab for Prod<A, B> {
    fn sum(&self) -> usize {
        self.0.sum() + self.1.sum()
    }
}

// K - field accessors, to grab numbers from them just call the method
impl <T: NumGrab> NumGrab for K<T> {
    fn sum(&self) -> usize {
        self.0.sum()
    }
}

// and that's the data we care about - implement it for whatever types you want to work with
impl NumGrab for usize {
    fn sum(&self) -> usize {
        *self
    }
}

End user writes something like this and it just works.

#[derive(Generic)]
struct Point {...}

fn main() {
    let point = Point { x: 10, y: 20, z: 30 };
    let rep = to_rep(point);
    let sum = rep.sum();
}

Making generic trait/variadic function comes with some boilerplate but no new syntax and it covers enums including empty and a bunch of other scenarios.

1 Like

what are people's thought's on making tuples into hlists somehow? it might not be the most user-friendly option, but at the same time i'm not sure variadic generics are ever very "user friendly", for the simple fact they add a significant amount of high-level complexity, much like regular generics.

What I wrote is a generalized version of "making tuples into hlists" - it turns any ADT into representation you can work generically and back, but also gives access to type info. I imagine it should be possible to make it more user friendly with one or more traits that target algebraic products specifically so calling a function on each tuple member is just using that trait...

1 Like