Skip to content

Pattern matching formalization #13

@liquidev

Description

@liquidev

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))

Metadata

Metadata

Assignees

No one assigned

    Labels

    featureThis issue is related to implementing a major feature of the compiler.language featureAdding a new feature into the language.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions