thautwarm / MLStyle.jl

Julia functional programming infrastructures and metaprogramming facilities
https://thautwarm.github.io/MLStyle.jl/latest/
MIT License
402 stars 38 forks source link

Pattern with a repeated symbol should only match if all referenced values are equal #152

Open cmcaine opened 1 year ago

cmcaine commented 1 year ago

e.g.

function rockpaperscissors(a, b)
    @match (a, b) begin
        (x, x) => :draw
        (:rock, :scissors) => :left
        (:paper, :rock) => :left
        (:scissors, :rock) => :left
        _ => :right
    end
end

I think the (x, x) pattern above should only match if a == b. This matches the behaviour of elixir:

iex(1)> case {1, 2} do        
...(1)>   {x, x} -> :same     
...(1)>   _ -> :different
...(1)> end
:different
iex(2)> case {1, 1} do   
...(2)>   {x, x} -> :same
...(2)>   _ -> :different
...(2)> end
:same
thautwarm commented 1 year ago

What you want is unification, which is slightly different from pattern matching. Unification needs well-defined equality for values, but pattern matching does not.

The implication is, usually, a statically typed programming language reports an error for the pattern (x, x), because equality is not generally available in a general-purposed programming language.

Elixir and some pure functional programming language does not suffer from this, as they can always define a proper equality for every two values from the the language's value domain.

cmcaine commented 1 year ago

Thanks for replying!

I think I'm describing non-linear pattern matching rather than unification?

Perhaps MLStyle could use === to tell if the two values are identical? There will be some issues with pattern matching on vectors, but the docs could explain what's going on.

Or MLStyle could refuse to allow repeated symbols on the LHS of the match?

thautwarm commented 1 year ago

Yes, this should be called non-linear patterns which requires equality for the matching target. The proposal of using === seems working, and most of the reasons why Haskell rejected this (this thread) do not apply to MLStyle.

I think this is do-able, but I cannot evaluate how it would affect different scenarios. Maybe presenting this as an extension:

using MLStyle
using MLStyle.NonLinearPatterns

@match (10, 10, 2) begin
   (x, x, y) => ...
end
thautwarm commented 1 year ago

What you want can be achieved in one line, by changing the behaviour from shadowing to === comparison.

https://github.com/thautwarm/MLStyle.jl/blob/4773f8900bd534924d27d0082685ad79478c4301/src/AbstractPatterns/impl/BasicPatterns.jl#L98

The real issue is about how to control the scope of the NonLinearPatterns extension. I don't think it should be introduced into MLStyle directly.

cmcaine commented 1 year ago

From the user side it could be something like this?

using MLStyle
using MLStyle.NonLinearPatterns: @match

The later and more specific using will dominate the implicit import from MLStyle (so long as @match has not been used between the imports). MLStyle.NonLinearPatterns (or whatever module) could also provide the same macro under the name @nlmatch or whatever for people who want to use both.

or

using MLStyle
using MLStyle.NonLinearPatterns

@match NonLinearPattern value begin
# ...
end

From an implementation point of view, ex2tf and other functions could be changed to accept a first argument of the match style (a type or something) and them MLStyle.NonLinearPatterns or other modules could use multiple dispatch to override the bits that they care about? Depends how much of ex2tf and the other things are reusable, I suppose?

I don't understand much of the haskell mailing list discussion, so I can't comment on that.