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

Pattern matching formalization #13

Open
liquidev opened this issue Jan 3, 2022 · 1 comment
Open

Pattern matching formalization #13

liquidev opened this issue Jan 3, 2022 · 1 comment
Labels
feature This issue is related to implementing a major feature of the compiler. language feature Adding a new feature into the language.

Comments

@liquidev
Copy link
Member

liquidev commented Jan 3, 2022

Right now pattern matching is not very clearly defined, so this issue attempts to resolve that.

Matching against existing variables, vs introducing new variables in patterns

One problem I have with Rust's pattern matching, is that there's no way of matching against an existing variable. Each identifier introduces a new variable, eg. in Some(x), x is a new variable, in Thing { x: y }, y is also a new variable. Thus in tsuki I want to have a proper syntax for introducing variables into scope, vs matching against existing values.

Since bringing values into scope is a more common use case than matching existing variables, the syntax val x can be used to match against an existing variable.

let outer = 1
match thing
  Some(val outer) -> print("it's the outer value!")
  Some(inner) -> print(inner)
  Nil -> print("nothin' to see here")

The patterns

We should support a fairly limited, yet flexible set of patterns in the beginning.

  • The wildcard pattern _
    • Matches anything and discards it.
  • Literal patterns, such as 1, true, :my_atom, "Hello there."
    • Matches a value literally, using the equality operator ==.
  • Range patterns, such as 1..5, 1..<5.
    • Matches a value between a specified range.
  • Outer variable patterns, such as val abc
    • Matches a value and compares it to the one stored in the variable abc, using the equality operator ==.
  • Variable binding patterns, such as abc, var abc
    • Matches a value and introduces it into scope under a user-specified name and optional mutability.
    • Moves the matched value into the new variable in the process.
  • Tuple pattern, such as (x, var y).
    • Matches each tuple field.
  • Union variant patterns, such as Some(x), MyUnion.MyVariant(var a, var b)
    • Matches each of the variant's fields.
  • Object patterns, such as MyObject { some_field = x, another_field = var y }
    • Matches individual object fields.

Refutability

A pattern is irrefutable if it can be proved to always match, no matter the input. A pattern is refutable if it can be proved to not match sometimes, given a specific set of inputs. These are mutually exclusive, ie. a pattern that is not irrefutable is refutable, so only specifying one of these rules is enough.

A pattern is refutable if it contains any of the following patterns:

  • Literal patterns,
  • Variable patterns,
  • Union variant patterns, but only if the matched value's type is not the union variant type itself.

Any pattern that does not contain any of the aforementioned patterns is irrefutable.

Unifying assignment

With patterns introduced, assignment should be unified such that it uses pattern matching instead of some arbitrary keywords. Having assignment return the old value is quite a nice feature to have, so the existing = operator is not going anywhere. A new let statement could be introduced for matching. It would obsolete the existing val and var statements in favor of taking a pattern to match against on its left hand side. This code:

val x = 1
var y = 2
val _ = x

would instead be written as:

let x = 1
let var y = 2
let _ = x

Although it looks a little verbose at first, it is a lot more flexible, as it allows for matching against patterns:

let this_is_surely_some = Some(1)
let Some(one) = this_is_surely_some
let (x, y) = (1, 2)

let also becomes part of the if statement and while loop.

if let Some(x) = maybe_nil
  print(x)
while let Some(x) = my_iterator.next
  print(x)

As before, it should be possible to specify multiple patterns, all of which must match.

if let Some(x) = maybe_nil, let Nil = surely_nil
  ()

for loops with patterns

for loops should also use this pattern syntax, always expecting an irrefutable pattern before the in keyword.

for x in [1, 2, 3]
  print(x)

As demonstrated before, more complex matches can be made.

for (x, y) in [(1, 2), (2, 3), (3, 4)]
  print((x, y))
@liquidev liquidev added language feature Adding a new feature into the language. feature This issue is related to implementing a major feature of the compiler. labels Jan 4, 2022
@liquidev
Copy link
Member Author

liquidev commented Jan 5, 2022

Due to the syntax changes, a few other things will probably need to be changed:

  • Function parameters should become patterns
  • Object declaration syntax should omit the val for immutable fields

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature This issue is related to implementing a major feature of the compiler. language feature Adding a new feature into the language.
Projects
None yet
Development

No branches or pull requests

1 participant