#row #polymorphism #anonymous #structural

rovv

provide the anonymous row type (poor-man's row polymorphism) in rust

4 releases

0.2.1 May 27, 2021
0.2.0 Apr 9, 2021
0.1.1 Mar 30, 2021
0.1.0 Mar 30, 2021

#1774 in Rust patterns

MIT/Apache

20KB
270 lines

Overview

rovv is a crate to provide a "poor-man's" row-polymorphism for rust base on lens-rs.

What is row poly?

In programming language type theory, row polymorphism is a kind of polymorphism that allows one to write programs that are polymorphic on record field types (also known as rows, hence row polymorphism).

-- wikipedia

Considering a function in PureScript:

\r -> r.x

you can pass a record into the function above

{ x: 1 }

or even

{ x: 1, y: 2, z: 3 }

That is what "row-poly" means: a record can be passed into the function above as long as it contains a field .x.

The type of the function is:

{ x: a | l } -> a
-- The label `l` represents the rest fields of a record.

Now you can do the same(not exactly) in rust.

Usage

restrict the parameter r contains a field .x

fn take_x<T>(r: row! { x: T, .. }) -> T {
    r.view(optics!(x))
}

// &row! { x: T, .. } is ok
fn take_x_ref<T>(r: &row! { ref x: T, .. }) -> &T {
    r.view_ref(optics!(x))
}

// &mut row! { x: T, .. } is ok
fn take_x_mut<T>(r: &mut row! { mut x: T, .. }) -> &mut T {
    r.view_mut(optics!(x))
}

let foo = Foo { // have been derived Lens for Foo
    x: String::from("this is Foo"),
    y: 1234
}

let bar = Bar { // have been derived Lens for Bar
    x: 0,
    z: Some(1)
}

assert_eq!(&*take_x_ref(&foo), "this is Foo");
assert_eq!(take_x(bar), 0);

You can also describe a type may have a field:

fn or_take_y<T>(r: row! { y: T?, .. }) -> Option<T> {
    r.preview(optics!(y))
}

assert_eq!(or_take_y(foo), Some(1234));
assert_eq!(or_take_y(bar), None);

Desugar

The function take_x is equivalent to

fn take_x<T, R>(r: R) -> T
where
    R: Lens<Optics![x], T>
{
    r.view(optics!(x))
}

In fact the row! { .. } will be desugared to the impl trait, the placeholder of a type satisfied the lens trait. And the dyn_row! { .. } will be desugared to the dyn trait, the dynamic version of row!.

fn sum_field(n: i32) -> Box<dyn_row! { Ok: i32?, Err: String?, Some: ()? , ..}> {
    match n%3 {
        0 => Box::new(Result::<_, String>::Ok(0)),
        1 => Box::new(Result::<i32, _>::Err(String::from("no!"))),
        2 => Box::new(Some(())),
        _ => Box::new(Option::<()>::None)
    }
}

Limitations

  • Cannot pass the genreric arguments explicitly into the function when row! is used in argument position now, because the row! is the impl trait.
  • dyn_row! will lose some polymorphism e.g. dyn_row! { x: i32, .. } does not satisfy dyn_row! { ref x: i32, .. }, because the trait object cannot convert to the others, thought Trait1: Trait2.
  • Cannot move out of a field from a dyn_row!.

Cargo.toml

Please add the following in your Cargo.toml

[dependencies]
lens-rs = "0.3"
rovv = "0.2"

[package.metadata.inwelling]
lens-rs_generator = true
rovv = true

Enjoy it!

Dependencies

~1.2–1.8MB
~43K SLoC