rust-mdo icon indicating copy to clipboard operation
rust-mdo copied to clipboard

Improve mdo syntax

Open Boscop opened this issue 7 years ago • 9 comments

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

Boscop avatar Oct 26 '17 22:10 Boscop

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 <-.

TeXitoi avatar Oct 27 '17 06:10 TeXitoi

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 _ = {...}

Boscop avatar Oct 27 '17 21:10 Boscop

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.

TeXitoi avatar Oct 28 '17 15:10 TeXitoi

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)?

Boscop avatar Oct 29 '17 20:10 Boscop

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 avatar Oct 31 '17 22:10 TeXitoi

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

TeXitoi avatar Oct 31 '17 22:10 TeXitoi

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<T>>?

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?

Boscop avatar Nov 01 '17 04:11 Boscop

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

TeXitoi avatar Nov 01 '17 21:11 TeXitoi

Ah, yes.

Boscop avatar Nov 02 '17 03:11 Boscop