Roger-luo / Moshi.jl

nextgen MLStyle: Generic Algebraic Data Type + Pattern Match
http://rogerluo.dev/Moshi.jl/
MIT License
57 stars 1 forks source link

Pattern matching to equal objects of a certain variant #10

Open mtfishman opened 4 weeks ago

mtfishman commented 4 weeks ago

Hi @Roger-luo, thanks for the nice package. I'm playing around with it for writing a symbolic tensor network library, it seems like a good fit for that and the combination of algebraic data types and pattern matching is very powerful.

I was hoping to pattern match to cases where the objects are equal and of a certain variant, is that possible? I tried the following:

using Moshi.Data: @data
using Moshi.Match: @match

@data Fruit begin
  struct Apple
    type::String
  end
  struct Orange
    type::String
  end
end

@match (Fruit.Apple("Honeycrisp"), Fruit.Apple("Honeycrisp")) begin
  (x::Fruit.Apple, x::Fruit.Apple) => "Same apples (both $x)"
  (x::Fruit.Apple, y::Fruit.Apple) => "Different apples ($x, $y)"
  _ => "No match"
end

but it returns "No match", i.e. it doesn't hit the (x::Fruit.Apple, x::Fruit.Apple) => ... branch.

Similarly, passing (Fruit.Apple("Honeycrisp"), Fruit.Apple("Fuji")) doesn't hit the (x::Fruit.Apple, y::Fruit.Apple) => ... branch.

I'm using:

(Moshi.jl) pkg> st Moshi
Status `...`
  [2e0e35c7] Moshi v0.3.3
mtfishman commented 4 weeks ago

Here's one way to do it:

@match (Fruit.Apple("Honeycrisp"), Fruit.Apple("Honeycrisp")) begin
  (Fruit.Apple(; type=x), Fruit.Apple(; type=x)) => "Same apples (both type $x)"
  (Fruit.Apple(; type=x), Fruit.Apple(; type=y)) => "Different apples (types $x, $y)"
  _ => "No match"
end

but it seems unfortunate to have to list all of the arguments if you want the entire object to match.

EDIT: One interesting thing I noticed with that approach is that if you do something like the following:

@match (Fruit.Apple("Honeycrisp"), Fruit.Apple("Honeycrisp")) begin
  (x::Fruit.Type, x::Fruit.Type) => "Same fruit (both $x)"
  (x::Fruit.Type, y::Fruit.Type) => "Different fruits ($x, $y)"
  (Fruit.Apple(; type=x), Fruit.Apple(; type=x)) => "Same apples (both type $x)"
  (Fruit.Apple(; type=x), Fruit.Apple(; type=y)) => "Different apples ($x, $y)"
  _ => "No match"
end

it matches to (x::Fruit.Type, x::Fruit.Type) => ... rather than (Fruit.Apple(; type=x), Fruit.Apple(; type=x)) => ..., though it matches to (Fruit.Apple(; type=x), Fruit.Apple(; type=x)) => ... if you do:

@match (Fruit.Apple("Honeycrisp"), Fruit.Apple("Honeycrisp")) begin
  (Fruit.Apple(; type=x), Fruit.Apple(; type=x)) => "Same apples (both type $x)"
  (Fruit.Apple(; type=x), Fruit.Apple(; type=y)) => "Different apples ($x, $y)"
  (x::Fruit.Type, x::Fruit.Type) => "Same fruit (both $x)"
  (x::Fruit.Type, y::Fruit.Type) => "Different fruits ($x, $y)"
  _ => "No match"
end

I guess the precedence is based on the order of the definitions and not the level of specificity of the pattern, i.e. it is more like a series of if-statements rather than how the Julia compiler finds methods.

mtfishman commented 4 weeks ago

Sorry for the noise, I realized another way to achieve this is with nested @match statements:

x = Fruit.Apple("Honeycrisp")
y = Fruit.Apple("Honeycrisp")

@match (x, y) begin
  (Fruit.Apple(), Fruit.Apple()) => @match (x, y) begin
    (x, x) => "Same apples"
    (x, y) => "Different apples"
  end
  _ => "No match"
end
Roger-luo commented 3 weeks ago

Hi Matt,

Thanks for your interest!

I'm playing around with it for writing a symbolic tensor network library, it seems like a good fit for that and the combination of algebraic data types and pattern matching is very powerful.

Yes, this package is built precisely for this type of application, as we chatted about last time in New York. However, this is only step 1. To achieve what we want, a general-purpose framework that does not imply any algebra on the symbolic expression needs to be built.

I guess the precedence is based on the order of the definitions and not the level of specificity of the pattern, i.e. it is more like a series of if-statements rather than how the Julia compiler finds methods.

Moshi.Match provides only syntax-based pattern matching. The underlying pattern language does not define which pattern is more specific, unlike the type system, where the more specific pattern is defined by subtyping. On the other hand, it does not aim to match all the patterns. Matching all the patterns requires extra runtime computation that breaks the promise of zero-overhead Moshi provides. Doing so requires a rule-based rewrite system built on top of Moshi, which is what I'm planning to work on next.

I was hoping to pattern match to cases where the objects are equal and of a certain variant, is that possible? I tried the following:

Yes, that's your last example if you don't want to match specific fields. You don't need to write nested @match tho.