rust-mdo
rust-mdo copied to clipboard
Improve mdo syntax
Why not use <-
(instead of =<<
) just like haskell?
(This crate also uses <-
.)
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 <-
.
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 _ = {...}
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.
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)
?
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.
I forgot, you can write _ =<< expr;
as ign expr;
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?
For Result<Option<T>>
is something like mdo! { x =<< foo().ok().unwrap_or(None); ... }
acceptable?
Ah, yes.