@tanriol, @nicoburns,
I understand rationale behind alternative syntax you proposed, it’s really simpler. But for me verbosity of this syntax probably in most of cases would be the reason to use temporary bindings instead. Either with _
or with it
or with any other placeholder it would be cumbersome just for very little benefit in learning experience.
Another thing I worry about is that (if I understand correctly) your proposals enhances method chains with full featured scope.
I see the following drawbacks with that approach:
- Syntax would be more easier to abuse, since nothing will restrict growing code inside of extended dot scope. I’ve ran multiple times into similar problems when programming in Kotlin and it probably was the worst my experience with Kotlin.
- It would be harder to explain what is
it
and why it’s not available e.g. inside of closures or another scopes. It also would be harder to understand from where it comes from.
- It would be harder to distinguish this syntax from regular closure or any other scope and this would make harder to understand intention of code.
- Because of hygiene it probably wouldn’t be possible to introduce fluent
with!()
macro, it would require to take it
explicitly.
And when talking in terms of examples:
/// 1
#[inline]
async fn request_user(self, user_id: String) -> Result<User> {
let url = format!("users/{}/profile", user_id);
let user = self.{ await _.request(url, Method::GET, None, true) }?
.map(|x| {
match x {
Ok(y) => y.do_some_important_stuff(),
_ => fallback(),
}
})
.res.{ await _.json::<UserResponse>() }?
.user
.into();
Ok(user)
}
/// 2
#[inline]
async fn request_user(self, user_id: String) -> Result<User> {
let url = format!("users/{}/profile", user_id);
let user = self.{ await it.request(url, Method::GET, None, true) }?
.map(|x| {
match x {
Ok(y) => y.do_some_important_stuff(),
_ => fallback(),
}
})
.res.{ await it.json::<UserResponse>() }?
.user
.into();
Ok(user)
}
/// 3
#[inline]
async fn request_user(self, user_id: String) -> Result<User> {
let url = format!("users/{}/profile", user_id);
let user = self.[await it.request(url, Method::GET, None, true)]?
.map(|x| {
match x {
Ok(y) => y.do_some_important_stuff(),
_ => fallback(),
}
})
.res.[await it.json::<UserResponse>()]?
.user
.into();
Ok(user)
}
/// 4
#[inline]
async fn request_user(self, user_id: String) -> Result<User> {
let url = format!("users/{}/profile", user_id);
let user = self.[await request(url, Method::GET, None, true)]?
.map(|x| {
match x {
Ok(y) => y.do_some_important_stuff(),
_ => fallback(),
}
})
.res.[await json::<UserResponse>()]?
.user
.into();
Ok(user)
}
1. Is the hardest to eye-parse because all things on it looks the same.
2. Brings some kind of context with it
, although it’s a bit hard to say from where it
comes from.
3. Makes more sense because it uses special scope that’s easy to identify and looks like some kind of metaprogramming (which we actually do), but anyway we can’t read it fluently because of it
that creates context that requires interruption.
4. is the simplest here because it reads as in plain declarative English, however you should understand how it works first (which IMO even could be done intuitively)