-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Function clauses RFC #1564
Function clauses RFC #1564
Conversation
This seems like a lot of additional language complexity for very little gain. |
Another big drawback is that when looking at the source code for a function, it could be easy to think that the primary function definition is the only one if you don’t realise that one of its arguments has a refutable pattern (e.g., if you skim over the function definition, or it uses an enum pattern that you assumed was a tuple struct pattern). This could make it hard to understand code you didn’t write (or indeed code that you did write but forgot the details of), potentially allowing bugs to slip by. Requiring an extra keyword for multi-definition functions (e.g., |
An alternate option, that may reduce the syntactic overhead of stating the types every time, avoid being as bad as requiring an enclosing match, and also clarify the situation regarding attributes is to possibly take an idea from the Perl 6 community. Specifically, they support defining a 'proto' function which must have a If the proto function contains all of the type signatures, and the multi function bodies simply carry refutable matches, this could work well. Function attributes would sit on the proto function, etc. Interestingly, that same syntax could easily be extended in the future to handle function specialization based on target attributes, as was recently added in GCC. However, I agree that it's not a good trade-off to make it this time. |
@AndrewBrinker I agree, which is the main reason I wasn't going to submit it. @P1start From experience with Elixir, I can say with pretty good assurance that I've never not noticed another clause for a function. @eternaleye Yeah, header signatures did pop into my head for a moment, but they're backwards compatible with this. |
Oooh, here's an odd thought. fn foo(Option<i32>, Result<(),()>) -> i32 {
fn self(Some( x ), Ok(())) => {
x
}
...
} In favor:
fn foo(args, here) {
match (args, here) {
(args, here) => {
body
}
}
} Against:
Another option: fn foo(Option<i32>, Result<(),()>) -> i32 {
fn (Some( x ), Ok(())) => {
x
}
...
} This avoids the "unshadowed |
This is similar to the suggestions in this thread where this: fn foo(x: Option<int>, y: Option<int>) -> Option<int> {
match (x, y) {
(Some(x), Some(y)) => Some(x + y),
(Some(x), None) => Some(x),
_ => None
}
} could be shortened to: match fn foo(Option<int>, Option<int>) -> Option<int> {
(Some(x), Some(y)) => Some(x + y),
(Some(x), None) => Some(x),
_ => None
} There are other suggestions where they chain different keywords together essentially extending the idea and allowing somewhat generic keyword chaining: for v in foo.iter() {
match v {
(Some(x), Some(y)) => println!("Total: {}", x+y),
(Some(x), None) => println!("x: {}", x),
_ => println!("no x")
}
}
for match v in foo.iter() {
(Some(x), Some(y)) => println!("Total: {}", x+y),
(Some(x), None) => println!("x: {}", x),
_ => println!("no x")
} @glaebhoerl Had some opinions on how the syntax should be ordered. I kinda think this looks nicer than the proposed solution for what it's worth. |
I always thought having something similar to @mdinger's Not really a fan of the Erlang/Elixir style though. It's easier for them to get away with because they don't have types to deal with, or having to cover each pattern. |
On IRC, Jose Valim (founder of Elixir) was discussing alternate ways of doing function clauses in Elixir because they're verbose too. So given that, the Elixir syntax version is definitely dead in the water, so an alternate syntax is definitely preferable. |
At least for me, reading functions defined in this way is readable and less verbose than using a match inside the function. Here we can take inspiration from Haskell (and Erlang). before :: Maybe Foo -> ()
before optional_foo =
case optional_foo of
Just foo -> consume foo
Nothing -> ()
after :: Maybe Foo -> ()
after (Just foo) = consume foo
after Nothing = () In Haskell, it is very common to define recursive functions (mentioned in motivations already) this way. https://en.wikibooks.org/wiki/Haskell/Pattern_matching It also has a nice mathematical feel to it, hence the readability. So a solid +1 on this RFC from me. Even syntactic sugar can be useful sometimes. If this is implemented in Rust, it shouldn't be called overloading in the docs, as that requires different arguments (arity/types) and instead just pattern matching. https://en.wikipedia.org/wiki/Function_overloading |
It's worth noting that Elm considers this bad style and has even removed it from the language. (There might be better links but that's the only one I found.) |
@glaebhoerl that's strange, it is considered good style in haskell =) |
In Haskell everything is based around functions thus pattern matching on arguments is a core concept. |
I don't even like this in Haskell, |
@Havvy, How about an alternative solution? Instead of baking in support for function / arity overloading we do it in a macro-esque way that simply (heh) desugars it into a trait implementation. fn new_to_string!(x: u8) -> String {
x.to_string()
}
fn new_to_string!(x: u32) -> String {
x.to_string()
} desugars to: trait NewToString {
fn new_to_string(self) -> String;
}
impl NewToString for u8 {
fn new_to_string(self) -> String {
self.to_string()
}
}
impl NewToString for u32 {
fn new_to_string(self) -> String {
self.to_string()
}
}
fn new_to_string<T: NewToString>(x: T) -> String {
x.new_to_string()
} That's a third of the characters (115 vs 331) to write the same thing. We would need a different (but similar) syntax to that because it would conflict with macros, but you get the idea. This way we know from the source that what we're doing is not function overloading but instead generating a trait implementation. We also don't increase the complexity of the language/compiler, and are simply providing something that helps with creating traits. Snake case for the function converts into pascal case for the trait implementation. Currently, macro_rules! overload {
(fn $name:ident($($arg:ident: $ty:ty)*) -> $result:ty $block:block) => ();
(fn $name:ident($($arg:ident: $ty:ty)*) $block:block) => ()
}
For syntax, maybe trait fn? trait fn new_to_string(x: u32) -> String { ... } or a fn! macro? fn! new_to_string(x: u32) -> String { ... } |
@Zvxy That's something completely different (actual overloading), and is backwards compatible with pretty much any function clause syntax. Function clauses are basically a marriage of |
@Havvy, oh derp. I didn't notice you didn't include arity overloading in your rfc.
Heh. |
Hmm, I have a general concern with how any of these syntax proposals would not be possible with closures... @mdinger You could actually combine refutable and irrefutable patterns using that syntax. match fn foo(Bar{baz, ..}: Bar) -> Result<Baz, Baz> {
(Bar{qux: Some(_)}) -> Ok(baz),
_ -> Err(baz)
} I have no clue if that would actually be useful. |
Just to explore the alternative of
(Obviously not the most realistic example. Examples are hard.) (Another option is to just omit the scrutinee to denote a match-lambda, with an intuition based on partial application, but this was deemed not obvious enough by the GHC folks (hence
.) Anyway this doesn't help as much as it does in Haskell, because in Haskell you can do
But that's still a bit eh. Maybe if it were allowed to omit the type annotation and write
instead... what if we also had
I'm not sure if any of these feel really convincing to me... |
@glaebhoerl: Along the lines of omitting the scrutinee on inline ones, what about As far as definition goes, I still think |
While |
After some discussion yesterday in the @rust-lang/lang meeting, we've decided to close this RFC for prioritization reasons. The core idea here is very good; I think most of us have wanted some sort of shorthand for a I have opened a postponement issue here: #1577 (so please feel free to continue discussion there) |
Not that this has any bearing on this RFC (since it was closed in 2016), but in case anyone reads this in 2018 and beyond... I'd like to take back everything I said and align myself with @Ericson2314's comment:
I think: after :: Maybe Foo -> ()
after (Just foo) = consume foo
after Nothing = () is bad style and I would never write Haskell that way these days. |
I wasn't going to submit this, thinking personally that the drawbacks outweigh the benefits, but I've had others actually say they like it, so figured I'd listen to the IRC community and actually submit it.