Thank you for comprehensive explanation. Now itās clean that we was talked about simplicity of different things: you was talked about simplicity of language itself, while I was talked about simplicity of code written in language.
And right, as language feature my proposal either with and without it is complicated and adds a lot of new concepts that either could be hard to learn and hard to implement.
But I donāt think that this complexity is so bad because itās the same as complexity of lifetimes: it either gives us superpower and we wouldnāt have too much problems with it when working with code. That said, snippets like a().b().[c().[f(it)].d(),g(it)].e() would never occur in real world and instead they would contain a lot of context alongside that would allow us to understand whatās going on.
In code this syntax would only reduce number of concepts. Thatās not visible on small examples, through. Better do demonstrate a bigger example here and I think Iāve found the perfect one.
Let consider the following snippet from druid GUI library:
fn main() {
druid_win_shell::init();
let mut file_menu = Menu::new();
file_menu.add_item(COMMAND_EXIT, "E&xit");
file_menu.add_item(COMMAND_OPEN, "O&pen");
let mut menubar = Menu::new();
menubar.add_dropdown(file_menu, "&File");
let mut run_loop = win_main::RunLoop::new();
let mut builder = WindowBuilder::new();
let mut state = UiState::new();
let foo1 = FooWidget.ui(&mut state);
let foo1 = Padding::uniform(10.0).ui(foo1, &mut state);
let foo2 = FooWidget.ui(&mut state);
let foo2 = Padding::uniform(10.0).ui(foo2, &mut state);
let button = Button::new("Press me").ui(&mut state);
let buttonp = Padding::uniform(10.0).ui(button, &mut state);
let button2 = Button::new("Don't press me").ui(&mut state);
let button2p = Padding::uniform(10.0).ui(button2, &mut state);
let root = Row::new().ui(&[foo1, foo2, buttonp, button2p],&mut state);
state.set_root(root);
state.add_listener(button, move |_: &mut bool, mut ctx| {
println!("click");
ctx.poke(button2, &mut "You clicked it!".to_string());
});
state.add_listener(button2, move |_: &mut bool, mut ctx| {
ctx.poke(button2, &mut "Naughty naughty".to_string());
});
state.set_command_listener(|cmd, mut ctx| match cmd {
COMMAND_EXIT => ctx.close(),
COMMAND_OPEN => {
let options = FileDialogOptions::default();
let result =ctx.file_dialog(FileDialogType::Open, options);
println!("result = {:?}", result);
}
_ => println!("unexpected command {}", cmd),
});
builder.set_handler(Box::new(UiMain::new(state)));
builder.set_title("Hello example");
builder.set_menu(menubar);
let window = builder.build().unwrap();
window.show();
run_loop.run();
}
And now let compare it with the same rewritten with this syntax:
fn main() {
druid_win_shell::init();
let file_menu = Menu::new(). [
add_item(COMMAND_EXIT, "E&xit"),
add_item(COMMAND_OPEN, "O&pen"),
];
let menubar = Menu::new(). [
add_dropdown(file_menu, "&File"),
];
let mut run_loop = win_main::RunLoop::new();
let mut builder = WindowBuilder::new();
let mut state = UiState::new();
let foo1 = FooWidget.ui(&mut state);
let foo1 = Padding::uniform(10.0).ui(foo1, &mut state);
let foo2 = FooWidget.ui(&mut state);
let foo2 = Padding::uniform(10.0).ui(foo2, &mut state);
let button = Button::new("Press me").ui(&mut state);
let buttonp = Padding::uniform(10.0).ui(button, &mut state);
let button2 = Button::new("Don't press me").ui(&mut state);
let button2p = Padding::uniform(10.0).ui(button2, &mut state);
let root = Row::new().ui(&[foo1, foo2, buttonp, button2p],&mut state);
state. [
set_root(root),
add_listener(button, move |_: &mut bool, mut ctx| {
println!("click");
ctx.poke(button2, &mut "You clicked it!".to_string());
}),
add_listener(button2, move |_: &mut bool, mut ctx| {
ctx.poke(button2, &mut "Naughty naughty".to_string());
}),
set_command_listener(|cmd, mut ctx| match cmd {
COMMAND_EXIT => ctx.close(),
COMMAND_OPEN => {
let options = FileDialogOptions::default();
let result =ctx.file_dialog(FileDialogType::Open,options);
println!("result = {:?}", result);
}
_ => println!("unexpected command {}", cmd),
}),
];
let window = builder. [
set_handler(Box::new(UiMain::new(state))),
set_title("Hello example"),
set_menu(menubar),
build().unwrap()
];
window.show();
run_loop.run();
}
In different parts of this code we donāt have:
- Unnecessary
mut bindings and annotations
- Builder patterns (many chained using this syntax methods donāt returns
self)
- Explicit referring to defined previously bindings
In sense of simplicity that example is very similar to your that removes unnecessary bindings and in return we have more simpler abstraction.
On my example in return we have:
- Clean definition of immutable items:
file_menu, menubar, foo1, button, etc
- Clean definition and usage of mutable parts:
run_loop, builder, state
- Clean separation of sequences of actions from imperative (possibly interfering) scopes
And as a side note: this example also explains why explicit receiver is redundant:
- The meaning of code almost always is obvious on local context
- Explicit receiver would name things twice (e.g.
state would be also referred as it)
- Explicit receiver would look the same in different contexts which would make code more obscure
Most likely it wouldnāt be used very often, so either it donāt adds too much complexity to code and either we can go without it. However, IMO when having ability to chain everything except external functions, the extended dot feature would look bizarrely incomplete.
Consider the same example where we modify file_menu in other function without it:
fn main() {
druid_win_shell::init();
let mut file_menu = Menu::new(). [
add_item(COMMAND_EXIT, "E&xit"),
add_item(COMMAND_OPEN, "O&pen"),
];
modify_menu(&mut file_menu);
let menubar = Menu::new(). [
add_dropdown(file_menu, "&File"),
];
let mut run_loop = win_main::RunLoop::new();
let mut builder = WindowBuilder::new();
let mut state = UiState::new();
let foo1 = FooWidget.ui(&mut state);
let foo1 = Padding::uniform(10.0).ui(foo1, &mut state);
let foo2 = FooWidget.ui(&mut state);
let foo2 = Padding::uniform(10.0).ui(foo2, &mut state);
let button = Button::new("Press me").ui(&mut state);
let buttonp = Padding::uniform(10.0).ui(button, &mut state);
let button2 = Button::new("Don't press me").ui(&mut state);
let button2p = Padding::uniform(10.0).ui(button2, &mut state);
let root = Row::new().ui(&[foo1, foo2, buttonp, button2p],&mut state);
state. [
set_root(root),
add_listener(button, move |_: &mut bool, mut ctx| {
println!("click");
ctx.poke(button2, &mut "You clicked it!".to_string());
}),
add_listener(button2, move |_: &mut bool, mut ctx| {
ctx.poke(button2, &mut "Naughty naughty".to_string());
}),
set_command_listener(|cmd, mut ctx| match cmd {
COMMAND_EXIT => ctx.close(),
COMMAND_OPEN => {
let options = FileDialogOptions::default();
let result =ctx.file_dialog(FileDialogType::Open,options);
println!("result = {:?}", result);
}
_ => println!("unexpected command {}", cmd),
}),
];
let window = builder. [
set_handler(Box::new(UiMain::new(state))),
set_title("Hello example"),
set_menu(menubar),
build().unwrap()
];
window.show();
run_loop.run();
}
Here, IMO all clarity of the whole main function is gone. You canāt be sure that file_menu isnāt modified somewhere below. When you see some &mut you on a moment think that file_menu may be on the right side. Thereās no more any clean separation of mutable state from immutable. And action that is run in sequence now is moved outside of brackets.
Thus, by keeping simplicity in one place we sacrificing in simplicity in another.
In such cases it would be a saviour, even if by itself itās not very clean. So, IMO increased language complexity here is an acceptable price to pay for it.