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

[RFC] Separate hard and soft pattern matching. #96

Closed
faultyserver opened this issue Dec 23, 2017 · 6 comments
Closed

[RFC] Separate hard and soft pattern matching. #96

faultyserver opened this issue Dec 23, 2017 · 6 comments
Labels
rfc A request for comments from the community syntax Any issue relating to the syntax of Myst.
Milestone

Comments

@faultyserver
Copy link
Member

Summary

Split the current pattern matching syntax of =: into two distinct types: hard and soft. Hard pattern matching will be what the current behavior is: raising a MatchError when the match fails. Providing hard guarantees on data matches. Soft pattern matching would not raise an error, though the semantics of what should happen are not entirely clear.

This would be a syntax distinction, using a different operator to distinguish hard from soft matching.

Motivation

While working on some Spec library improvements, I've been trying to use pattern matching for conditionals, and I keep running into the issue where I want to continue execution of the body, even after a failed match. Instead, the current matcher implementation raises an error immediately and will start panicking up the stack.

I could just use equality, but then I don't get the benefits of destructuring and complex matching.

Suggested change

I think the current matching syntax could be split into two variants. The current behavior would be considered "hard matching", where an error is raised if the match fails at any point. A new behavior would be added using a distinct operator for soft matching, where instead of an error, execution continues after returning some falsey value.

I would suggest that soft matching keep the current match operator, =:, and hard matching be given a visually louder operator, such as =!. =! in particular looks very similar to =:, so it seems like a good fit, and the bang already has the connotation of being more "dangerous" or "careless".

Since the semantics of hard matching won't be different from what they are currently, I won't cover them again here. Instead, here are some examples of what I think soft matching should do:

def match(arg)
  when true =: arg
    :matched
  else
    :no_match
  end
end

match(true)  #=> :matched
match(false) #=> :no_match
list = [1, 2, 3]
[1, 2] =: list #=> nil

[a, b, c] =: [1, 2] =: list #=> nil

Remaining Questions

The return value of a failed soft match is debatable. Does it make sense to return nil? To support the semantics shown above with usage in when, the result needs to be a falsey value, so either nil or false. Returning false directly feels wrong, as a successful match does not return true directly, and I feel like boolean results should always be boolean results (just true or false, with no other possibilities). nil, on the other hand, implies failure without a boolean connotation.

Should variables created by a match still be created if the match fails? In the last example above, a, b, and c would all be created by a successful match. However, the match before it will fail, so it's match will always fail. Should these variables be created even in the case of a failed match? If so, what should their value be?

More importantly, if the match fails after the first element, a will have been created, so should it be destroyed? How could that be guaranteed, especially with non-locals as match variables (e.g. ivars).

@faultyserver faultyserver added rfc A request for comments from the community syntax Any issue relating to the syntax of Myst. labels Dec 23, 2017
@faultyserver
Copy link
Member Author

Maybe an alternative to creating separate match variants would be to have a separate construct for matching one value against multiple patterns. This would be the real equivalent to case...when in Ruby/Crystal and cond in Elixir.

I don't have any real suggestion for what the syntax should be yet. when chaining somewhat loses its value if this gets picked, as it'd almost always be easier to write a pattern-matched variant instead.

I definitely want to avoid the implicit magic of cond clauses in Elixir. They default to being one line long, but can also span multiple lines, without needing any bracing characters. This is just a bit odd to me. More than anything, I'd want a more explicit start to the match to better delineate where each clause starts.

I can't imagine choosing the case...when syntax for this either, since it would overlap the when/unless syntax too much to be visually unambiguous.

A match keyword might make sense as the starting keyword, but I'm not sure what the clauses should look like. Re-using -> from anonymous functions doesn't make too much sense either. Unless, of course, match is just a magic function that takes a function as a parameter:

match([1, 2, 3], fn
  ->([0, 0, 0]) { stuff }
end)

That feels like a lot of visual noise for what's essentially when [0, 0, 0] =: [1, 2, 3]; stuff; end. Rust uses the match keyword with all the same styling that Elixir has for cond. I don't really want to adopt that, but I do like the name, since that's exactly what the semantics are.

@faultyserver faultyserver added this to the Next milestone Dec 30, 2017
@faultyserver faultyserver removed this from the Next milestone Jan 27, 2018
@faultyserver
Copy link
Member Author

Something I've been thinking about recently is how pattern matching could be used to better address exception handling locally. While raise and rescue are great for propagating errors across long distances on the callstack, there are a lot of cases where errors can and should be handled immediately, even if that means raising a new, more contextually-relevant error.

One good example is attempting to open a file. If the file doesn't exist, or if there's some issue with trying to open the file, an appropriate exception will be raised. But, in almost every case, that error will end up being handled directly by the thing that calls File.open.

I don't have any more ideas for a good syntax for this, but it seems like a good match structure could fundamentally change how most errors are propagated and dealt with in Myst.

@Jens0512
Copy link
Member

Jens0512 commented Jan 29, 2018

Im very much against using != for hard pattern matching as it is in every (serious) programming language i know is used as a «Not equals» operator (1 != 2 # => true)

@faultyserver
Copy link
Member Author

The operator I was suggesting would be the other way around (=!), but that brings up a good point that I hadn't considered: that become very visually ambiguous with !=, which does already exist in the language.

Will definitely need to reconsider that one if we go with that route.

@faultyserver
Copy link
Member Author

As a more immediate solution, I'm inclined to say that =: should return nil, rather than raise an error. A harder version can be re-added later, or behavior can change once a better construct for soft matching is made.

Right now, I find pattern matching in anything but parameters relatively useless, since it's not really feasible to deal with the MatchError that comes up when the match fails. being able to just use a when...else is a lot nicer than having to define a new method that rescues MatchError.

@faultyserver
Copy link
Member Author

Rust uses the match keyword with all the same styling that Elixir has for cond. I don't really want to adopt that, but I do like the name, since that's exactly what the semantics are.

I'm going to backpedal on this one a bit: I think a magic match keyword makes decent sense. Something like this:

match x
  ->(true)  { do_foo }
  ->(false) { do_bar }
  ->(_)     { raise :woops }
end

feels pretty clean and really is just syntax sugar for the match proposal from before. The implementation would essentially be:

def match(val, &block)
  block(val)
end

where val is the argument given on the line with match, and block is the functor of clauses given inside the body.

faultyserver added a commit to faultyserver/myst that referenced this issue Mar 17, 2018
…essions.

`match` expressions essentially act as a syntax sugar for creating an anonymous function and immediately invoking it with some arguments.
@faultyserver faultyserver added this to the Next milestone Mar 17, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
rfc A request for comments from the community syntax Any issue relating to the syntax of Myst.
Projects
None yet
Development

No branches or pull requests

2 participants