thautwarm / MLStyle.jl

Julia functional programming infrastructures and metaprogramming facilities

Home Page:https://thautwarm.github.io/MLStyle.jl/latest/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

enum should use `===` to compare equivalence

Roger-luo opened this issue · comments

enum should use a different function to compare equivalence, otherwise the following code will stack overflow, even it's a valid code

@enum Fruit begin
    Apple
    Banana
    Orange
end

function Base.:(==)(lhs::Fruit, rhs::Fruit)
    @match (lhs, rhs) begin
        (&Apple, &Apple) => true
        (&Banana, &Banana) => true
        (&Orange, &Orange) => true
        _ => false
    end
end

Apple == Apple

This is causing issue for a more complicated case

We should make these enums also MLStyle-recognized enums:

using MLStyle
using MLStyle.AbstractPatterns: literal

@enum Fruit begin
    Apple
    Banana
    Orange
end

MLStyle.is_enum(::Fruit) = true
MLStyle.pattern_uncall(e::Fruit, _, _, _, _) = literal(e)


function Base.:(==)(lhs::Fruit, rhs::Fruit)
    @match (lhs, rhs) begin
        (Apple, Apple) => true
        (Banana, Banana) => true
        (Orange, Orange) => true
        _ => false
    end
end

Apple == Apple # true

Basically, & uses == equality. Customizing the &'s behavior via custom == is intentional.

Ok, but this problem exists for user-defined types, e.g the ADT in Expronicon has the following issue

using MLStyle
using Expronicon.ADT: @adt

@adt Foo begin
    Bar
    struct Baz
        args::Vector{Int}
    end
end

function Base.:(==)(lhs::Foo, rhs::Foo)
    @match (lhs, rhs) begin
        (Bar, Bar) => true
        (Baz(args), Baz(args)) => args == args
        _ => false
    end
end

Bar == Bar

where Bar is marked as is_enum and literal, but because this calls == to compare the variants of Foo this will cause a dispatch error.

I got it, this is a bug.

Bar::Foo is not a bitstype instance, so == is used. This is a bug, as other pattern matching languages would use tags to compare ADT cases instead of using equality.

=== will not work, because === gives false does not really imply unmatch. Enums are only semantically immutable (i.e., structural equality). However, mutable values that are semantically immutable inside the enum might not well conform === (e.g., Strings in early Julia version, IIRC <1.1, or any custom mutable values that defines == for semantically immutable).

Considering the same issue in other PM programming languages (not Python), we can find that equality are separated from pattern matching (unless for bit-only literal values). Your example is also a good pro to this.

I'm thinking about how to address this issue.
Using tags to compare enums can implemented in future for more legal semantics.
However, currently I need the fix to be backward-compatible.

Cases for how to translate "comparing via tags for enum values" into julia:

@match V begin
      P => ...
      _ => ...
end
  1. if P is bit-only, we compare it with V via === (supported by the current mechanism)
  2. if P is immutable (not bit-only, so may contains references), we check if P and V has the same type, and if their corresponding fields are equal by ==. (NOTE that the recursive comparison is not ===, so we CAN't use === here)
  3. if P is mutable, it's not a valid enum.

working on this.