TeXitoi / rust-mdo

Monadic do notation for rust using macro and duck typing
Do What The F*ck You Want To Public License
226 stars 9 forks source link

Improve mdo syntax #4

Open Boscop opened 6 years ago

Boscop commented 6 years ago

Why not use <- (instead of =<<) just like haskell? (This crate also uses <-.)

TeXitoi commented 6 years ago

Because of rust macro system constraint, we can't put < after a pattern. Hado use an indent and not a pattern, that's why it can use <-, but hado can't do (a, b) <- expr, mdo can.

In pre rust 1.0, mdo was using <-.

Boscop commented 6 years ago

Ok, thanks for the explanation.

Btw, what's the recommended way to do embed non-monadic code in mdo! {} that uses the previous symbols? Currently I'm doing let _ = {...}

TeXitoi commented 6 years ago

Can you point to your usage of mdo, I'd like to read them and maybe propose some syntax to ease the usage of mdo.

I think you can't do better than what you're doing now, but we can improve mdo.

Boscop commented 6 years ago

Here is an example that contains the let _ = {} pattern for non-monadic code:

if let Some(data) = mdo! {
    places =<< place::Place::all_of_customer(customer_id).ok();
    trucks =<< truck::Truck::all_of_customer(customer_id).ok();
    truck_types =<< TruckType::all_of_customer(customer_id).ok();
    contained_products =<< ContainedProduct::all_of_customer(customer_id).ok();
    first_contained_product =<< contained_products.first();
    maybe_customer =<< Customer::find(customer_id).ok();
    customer =<< maybe_customer;
    drivers =<< Driver::all_of_customer(customer_id).ok();
    first_place =<< places.first().cloned();
    tablets =<< Tablet::all_of_customer(customer_id).ok();
    let origin = first_place.location();
    let drivers = drivers.into_iter().filter_map(|user| Driver::find(user.id).ok().and_then(|x| x)).collect::<Vec<_>>();
    let _ = {
        use std::iter::repeat;
        for (&driver, maybe_tablet) in drivers.iter().zip(tablets.into_iter().map(Some).chain(repeat(None))) {
            let tablet = maybe_tablet.or_else(|| {
                use diesel::Connection;
                use db;
                use models::tablet::*;
                let conn = &db::conn();
                conn.transaction(move || -> error::Result<_> {
                    let mut rng = thread_rng();
                    let unique_id = rng.gen_ascii_chars().take(25).collect::<String>();
                    let tablet_id = Tablet::create_from(NewTablet {
                        unique_id: &unique_id,
                        device_name: "fake demo device"
                    })?;
                    let tablet = Tablet::find_conn(conn, tablet_id)?.expect("bug");
                    tablet.update(&UpdateTablet::customer(Some(customer_id)))?;
                    Ok(tablet)
                }).ok()
            }).expect("tablet");
            tablet::set_logged_in(driver, Json(tablet.id));
        }
    };
    ret ret(CustomerData {
        customer,
        contained_product_id: first_contained_product.id,
        product_id: first_contained_product.product_id,
        truck_data: {
            drivers.into_iter().zip(&trucks).filter_map(|(driver, truck)|
                mdo! {
                    _ =<< truck.set_driver(driver.user_id).ok();
                    ret ret((truck.id, TruckData {
                        driver,
                        route: vec![],
                        truck: Truck::new(0, Vec2::new(0., 0.)),
                    }))
                }
            ).collect()
        },
        places: places.into_iter().enumerate().map(|(i, p)| (p.id, to_vrp_place(&origin, p, i))).collect(),
        origin,
        truck_ids: trucks.into_iter().map(|t| t.id).collect(),
    })
} {
    customer_data.insert(customer_id, data);
}

Also, I often have this pattern: if let Some(x) = mdo! { ... x =<< foo(); ret ret(x) } { /* do something with x */ } (like in the above example). It would be nice if I could write it like mdo! { ... x =<< foo(); /* do something with x */ }. Right now I'd have to write mdo! { ... x =<< foo(); let _ = { /* do something with x */ }; ret ret(()) } which is awkward.

Another pattern I often have is: mdo! { maybe_x =<< foo(); x =<< maybe_x; ... } Is there a way to do that in one line? If not, it would be nice if such a way could be added..

And maybe ret ret(x) could be shortened to just ret(x)?

TeXitoi commented 6 years ago

Whaou! That's a heavy use of mdo 😄

Do you have an idea of a great syntax for the let _ = { ... } pattern? can't think of any just now.

if let Some(x) = mdo! { ... x =<< foo(); ret ret(x) } { /* do something with x */ } can be rewritten if let Some(x) = mdo! { ... ret foo() } { /* do something with x */ }

I think I'd personnally write mdo! { ... x =<< foo(); let _ = { /* do something with x */ }; ret ret(()) } as

if let Some(x) = mdo! { ...; ret foo() } {
    /* do something with x */
}

for mdo! { maybe_x =<< foo(); x =<< maybe_x; ... } I don't get it, foo() returns Option<Option<T>>?

I think you're right, ret ret(x) can be shortened to just ret(x) with just a special case.

TeXitoi commented 6 years ago

I forgot, you can write _ =<< expr; as ign expr;

Boscop commented 6 years ago

Whaou! That's a heavy use of mdo 😄

Before I was using mdo it was even heavier with lots of map/and_then and deep indentation.. :)

Do you have an idea of a great syntax for the let _ = { ... } pattern? can't think of any just now.

If it's possible with the way macro parsing works, if a statement is not of the form x =<< y; or ret .. it can be treated like normal non-monadic code as if it was written in let _ = { ... }.

if let Some(x) = mdo! { ... x =<< foo(); ret ret(x) } { / do something with x / } can be rewritten if let Some(x) = mdo! { ... ret foo() } { / do something with x / }

Yeah, but I meant the extra if let Some(x) = .. {..} around mdo!{}, like in my code above.

I think I'd personnally write mdo! { ... x =<< foo(); let _ = { / do something with x / }; ret ret(()) } as

if let Some(x) = mdo! { ...; ret foo() } { / do something with x / }

Yes, this is what I did in the above example. But what I'd prefer to write is: mdo!{ ...<deps>... x =<< foo(<deps>); /* do something with x */ } (no ret, and no let _ = {..})

for mdo! { maybe_x =<< foo(); x =<< maybe_x; ... } I don't get it, foo() returns Option<Option>?

I often have db functions returning Result<Option<T>>, (Result because it could be a db error, Option because there could be no query result) so I often have mdo! { maybe_x =<< foo().ok(); x =<< maybe_x; ... }. That's what I meant. Is there a way to chain both into one line?

TeXitoi commented 6 years ago

For Result<Option<T>> is something like mdo! { x =<< foo().ok().unwrap_or(None); ... } acceptable?

Boscop commented 6 years ago

Ah, yes.