PistonDevelopers / dyon

A rusty dynamically typed scripting language
Apache License 2.0
1.74k stars 56 forks source link

Pattern matching #360

Open bvssvni opened 7 years ago

bvssvni commented 7 years ago

Related to https://github.com/PistonDevelopers/dyon/issues/304

Idea taken from https://github.com/PistonDevelopers/dyon/issues/359, but might be useful in general.

The idea is to add pattern matching to Dyon, a bit similar to Rust. Since Dyon is dynamically typed there is a ?= operator instead of :=.

The ?= operator checks the run-time type at run-time and reports an error if it is wrong. The type checker checks the static types and reports an error if the code never gets called.

If pattern

The ?= operator can be used with if:

x := [1, 2]
if [a, b] ?= x {
     ...
}

Try pattern

It can also be used directly in a function that returns err:

fn foo() -> res {
    x := [1, 2]
    [a, b] ?= x
    ...
}

The following code gives an error because it is exhaustive:

a := 1
b ?= a // ERROR: Use `:=` instead

Match pattern

A match expression similar to Rust:

x := [1, 2]
match x {
    [a, b] => { ... }
    _ => { ... }
}

The match requires _ or a variable since it can not be exhaustively checked.

Type is checked on all arms like with an if-expression.

bvssvni commented 7 years ago

Matching against array

An array pattern fails if it does not match exactly:

x := [1, 2, 3]
if [a, b] ?= x {
    // never runs because the number of items are not the same
    ...
}

To match against an array with a tail, add , .. at the end:

x := [1, 2, 3]
if [a, b, ..] ?= x {
    // runs because a tail is allowed
    ...
}

To bind against the tail, use ..<name>. This does a shallow clone of the items, inheriting the lifetime of the array.

x := [1, 2, 3]
if [_, _, ..a] ?= x {
    println(a) // prints `[3]`
}

Can also put the tail before the items:

x := [1, 2, 3]
if [..a, b] ?= x {
    println(a) // prints `[1, 2]`
    println(b) // prints `3`
}

In general, one can put items before and after the tail. The tail can be empty, so the following runs:

x := [1, 2, 3, 4]
if [a, b, ..c, d, e] ?= x {
    println(c) // prints `[]`
}

Rules for the array tail:

bvssvni commented 7 years ago

Matching against object

An array pattern fails if it does not match exactly:

x := {a: 1, b: 2, c: 3}
if {a: a, b: b} ?= x {
    // never runs because there are other items
    ...
}

To match against an object with extra items, add , .. at the end:

x := {a: 1, b: 2, c: 3}
if {a: a, b: b, ..} ?= x {
    // runs because extra items are allowed
    ...
}

One can also use same name for the variable as for the key:

x := {a: 1, b: 2, c: 3}
if {a, b, c} ?= x {
    ...
}

To bind against the extra items (the object tail), use ..<name>. This does a shallow clone of the items, inheriting the lifetime of the object.

x := {a: 1, b: 2, c: 3}
if {a: _, b: _, ..xc} ?= x {
    println(xc) // prints `{c: 3}`
}

Rules for the object tail:

bvssvni commented 7 years ago

Matching against vec4

Can be used with := if there are no constants in the pattern, which makes the type checker checks the type of the right expression is a vec4.

A vec4 pattern succeeds even when there are more non-zero components than in the pattern:

x := (1, 2, 3)
(a, b) := x

To match against zero, use 0s:

x := (1, 2, 3)
if (a, b, 0, 0) ?= x {
    // never runs because `z` is non-zero
    ...
}

Swizzling pattern tells where to put components in created vectors:

x := (1, 2, 3, 4)
(yx a, x b, w c) := x
println(a) // prints `(2, 1)`
println(b) // prints `(3, 0)`
println(c) // prints `(0, 0, 0, 4)`

Rules for swizzling pattern:

bvssvni commented 7 years ago

Matching against link

An link pattern fails if it does not match exactly:

x := link {1 2 3}
if link {a b} ?= x {
    // never runs because the number of items are not the same
    ...
}

To match against a link with a tail, add , .. at the end:

x := link {1 2 3}
if link {a b ..} ?= x {
    // runs because a tail is allowed
    ...
}

To bind against the tail, use ..<name>.

x := link {1 2 3"rd"}
if link {_ _ ..a} ?= x {
    println(a) // prints `3rd`
}

Can also put the tail before the items:

x := link {1","2 3}
if link {..a b} ?= x {
    println(a) // prints `1,2`
    println(b) // prints `3`
}

In general, one can put items before and after the tail. The tail can be empty, so the following runs:

x := link {1 2 3 4}
if link {a b ..c d e} ?= x {
    println(c) // prints ``
}

Rules for the link tail:

bvssvni commented 7 years ago

Match against constant

When matching against a constant, both run-time type and equality is checked.

bvssvni commented 7 years ago

Matching against expression

Put a ? in front of the expression to check for equality:

a := 1
x := [1, 2]
if [?a, b] ?= x {
    ...
}

This also works well with grab expressions in closures:

fn foo(a: f64) -> \([f64]) -> bool {
    return \(x) = if [?grab a, ..] ?= x { true } else { false }
}
bvssvni commented 7 years ago

Match against regular expression

A string can be matched against a regular expression. More discussion https://github.com/PistonDevelopers/dyon/issues/284