Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve mdo syntax #4

Open
Boscop opened this issue Oct 26, 2017 · 9 comments
Open

Improve mdo syntax #4

Boscop opened this issue Oct 26, 2017 · 9 comments

Comments

@Boscop
Copy link

Boscop commented Oct 26, 2017

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

@TeXitoi
Copy link
Owner

TeXitoi commented Oct 27, 2017

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 TeXitoi closed this as completed Oct 27, 2017
@Boscop
Copy link
Author

Boscop commented Oct 27, 2017

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
Copy link
Owner

TeXitoi commented Oct 28, 2017

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
Copy link
Author

Boscop commented Oct 29, 2017

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
Copy link
Owner

TeXitoi commented Oct 31, 2017

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 TeXitoi reopened this Oct 31, 2017
@TeXitoi TeXitoi changed the title Why not use <- instead of =<< ? Improve mdo syntax Oct 31, 2017
@TeXitoi
Copy link
Owner

TeXitoi commented Oct 31, 2017

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

@Boscop
Copy link
Author

Boscop commented Nov 1, 2017

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
Copy link
Owner

TeXitoi commented Nov 1, 2017

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

@Boscop
Copy link
Author

Boscop commented Nov 2, 2017

Ah, yes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants