Repository: invenia/NamedDims.jl
Pull request #79 (2019-11-05 12:55:04)

isequal and ==
This declares that arrays with incompatible names are not equal. 

codecov[bot]: # [Codecov](https://codecov.io/gh/invenia/NamedDims.jl/pull/79?src=pr&el=h1) Report
> :exclamation: No coverage uploaded for pull request base (`master@e52cdc0`). [Click here to learn what that means](https://docs.codecov.io/docs/error-reference#section-missing-base-commit).
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/invenia/NamedDims.jl/pull/79/graphs/tree.svg?width=650&token=M5MW3xa0xM&height=150&src=pr)](https://codecov.io/gh/invenia/NamedDims.jl/pull/79?src=pr&el=tree)

```diff
@@            Coverage Diff            @@
##             master      #79   +/-   ##
=========================================
  Coverage          ?   86.31%           
=========================================
  Files             ?        8           
  Lines             ?      263           
  Branches          ?        0           
=========================================
  Hits              ?      227           
  Misses            ?       36           
  Partials          ?        0
```


| [Impacted Files](https://codecov.io/gh/invenia/NamedDims.jl/pull/79?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/name\_core.jl](https://codecov.io/gh/invenia/NamedDims.jl/pull/79/diff?src=pr&el=tree#diff-c3JjL25hbWVfY29yZS5qbA==) | `89.88% <100%> (ø)` | |
| [src/functions.jl](https://codecov.io/gh/invenia/NamedDims.jl/pull/79/diff?src=pr&el=tree#diff-c3JjL2Z1bmN0aW9ucy5qbA==) | `97.43% <100%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/invenia/NamedDims.jl/pull/79?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/invenia/NamedDims.jl/pull/79?src=pr&el=footer). Last update [e52cdc0...9e1d96b](https://codecov.io/gh/invenia/NamedDims.jl/pull/79?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/Project.toml b/Project.toml
index 9cff44d..2d30ed8 100644
--- a/Project.toml
+++ b/Project.toml
@@ -1,7 +1,7 @@
 name = "NamedDims"
 uuid = "356022a1-0364-5f58-8944-0da4b18d706f"
 authors = ["Invenia Technical Computing Corporation"]
-version = "0.2.13"
+version = "0.2.14"
 
 [deps]
 LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
diff --git a/src/functions.jl b/src/functions.jl
index 8703143..eb7a502 100644
--- a/src/functions.jl
+++ b/src/functions.jl
@@ -90,6 +90,13 @@ end
 ################################################
 # Non-dim Overloads
 
+for fun in (:(==), :isequal, :isapprox)
+    @eval function Base.$fun(a::NamedDimsArray{La}, b::NamedDimsArray{Lb}; kw...) where {La, Lb}
+        names_are_unifiable(La, Lb) || return false
+        return $fun(parent(a), parent(b); kw...)
+    end
+end
+
 # Array then perhaps other args
 for (mod, funs) in (
     (:Base, (:zero, :one, :copy, :empty!, :push!, :pushfirst!)),
diff --git a/src/name_core.jl b/src/name_core.jl
index 7460f1c..8d6f082 100644
--- a/src/name_core.jl
+++ b/src/name_core.jl
@@ -144,9 +144,12 @@ end
 
 """
     unify_names(a, b)
+    unify_names(a, b, cs...)
 
 Produces the merged set of names for tuples of names `a` and `b`,
 or an error if it is not possibly to unify them.
+Then continues with further names `cs`, if any.
+
 Two tuples of names can be unified they are the same length
 and if for each position the names are either the same, or one is a wildcard (`:_`).
 When combining wildcard with non-wildcard the resulting name is the non-wildcard.
@@ -160,16 +163,31 @@ For example:
 
 This is the type of name combination used for binary array operations.
 Where the dimensions of both arrays must be the same.
+
+See also `names_are_unifiable(a, b)`, which returns `true` instead of the merged names,
+and `false` instead of an error.
 """
 function unify_names(names_a, names_b)
-    # 0-Allocations if inputs are the same
-    # 0-Allocation, if has a `:_` see  `@btime (()->unify_names((:a, :b), (:a, :_)))()`
+    # @btime (()->unify_names((:a, :b), (:a, :_)))()
+    ret = try_unify_names(names_a, names_b)
+    if ret === nothing
+        incompatible_dimension_error(names_a, names_b)
+    else
+        return compile_time_return_hack(ret)
+    end
+end
+unify_names(a) = a
+unify_names(a, b, cs...) = unify_names(unify_names(a,b), cs...)
+# @btime (()->unify_names((:a, :b), (:a, :_), (:_, :b)))()
 
-    names_a === names_b && return names_a
+names_are_unifiable(names_a, names_b) = try_unify_names(names_a, names_b) !== nothing
 
-    # Error message should not include names until it is thrown, as othrwise
-    # the interpolation allocates and slows everything down a lot.
-    length(names_a) != length(names_b) && incompatible_dimension_error(names_a, names_b)
+function try_unify_names(names_a, names_b)
+    if names_a === names_b
+        return names_a
+    elseif length(names_a) !== length(names_b)
+        return nothing
+    end
 
     ret = ntuple(length(names_a)) do ii  # remove :_ wildcards
         a = getfield(names_a, ii)
@@ -177,17 +195,16 @@ function unify_names(names_a, names_b)
         a === :_ && return b
         b === :_ && return a
         a === b && return a
+        return false  # mismatch occured, we mark this with a non-Symbol result
+    end
 
-        return false  # mismatch occured, we mark this with a nonSymbol result
+    if ret isa Tuple{Vararg{Symbol}}
+        return compile_time_return_hack(ret)
+    else
+        return nothing
     end
-    ret isa Tuple{Vararg{Symbol}} || incompatible_dimension_error(names_a, names_b)
-    return compile_time_return_hack(ret)
 end
 
-unify_names(a) = a
-unify_names(a, b, cs...) = unify_names(unify_names(a,b), cs...)
-# @btime (()->unify_names((:a, :b), (:a, :_), (:_, :b)))()
-
 """
     unify_names_longest(a, b)
 
@@ -206,7 +223,7 @@ unify_names_longest(::Tuple{}, ::Tuple{}) = tuple()
 function unify_names_longest(names_a, names_b)
     # 0 Allocations: @btime (()-> unify_names_longest((:a,:b), (:a,)))()
 
-    length(names_a) == length(names_b) && return unify_names(names_a, names_b)
+    length(names_a) === length(names_b) && return unify_names(names_a, names_b)
     long, short = length(names_a) > length(names_b) ? (names_a, names_b) : (names_b, names_a)
     ret = ntuple(length(long)) do ii
         a = getfield(long, ii)
@@ -226,7 +243,7 @@ unify_names_shortest(::Tuple{}, ::Tuple{}) = ()
 function unify_names_shortest(names_a, names_b)
     # 0 Allocations: @btime (()-> unify_names_shortest((:a,:b), (:a,)))()
 
-    length(names_a) == length(names_b) && return unify_names(names_a, names_b)
+    length(names_a) === length(names_b) && return unify_names(names_a, names_b)
     long, short = length(names_a) > length(names_b) ? (names_a, names_b) : (names_b, names_a)
     ret = ntuple(length(short)) do ii
         a = getfield(long, ii)


Test patch:
diff --git a/test/functions.jl b/test/functions.jl
index c53071b..9d66608 100644
--- a/test/functions.jl
+++ b/test/functions.jl
@@ -260,6 +260,23 @@ using Statistics
         end
     end
 
+    @testset "equality" begin
+        nda = NamedDimsArray([10 20; 30 40], (:x, :y))
+        nda2 = NamedDimsArray([10 20; 30 40], (:x, :_))
+        nda3 = NamedDimsArray([10 20; 30 40], (:x, :z))
+        nda4 = NamedDimsArray([11 22; 33 44], (:x, :y))
+        ndv = NamedDimsArray([10, 20, 30], (:x,))
+
+        @testset "$eq" for eq in (Base.:(==), isequal, isapprox)
+            @test eq(nda, nda)
+            @test eq(nda, nda2)
+            @test eq(nda, nda3) == false
+            @test eq(nda, nda4) == false
+            @test eq(nda, ndv) == false
+        end
+        @test isapprox(nda, nda4; atol=2π)
+    end
+
 end  # Base
 
 @testset "Statistics" begin
diff --git a/test/name_core.jl b/test/name_core.jl
index fae4be3..90d2185 100644
--- a/test/name_core.jl
+++ b/test/name_core.jl
@@ -7,6 +7,7 @@ using NamedDims:
     dim_noerror,
     tuple_issubset,
     tuple_cat,
+    names_are_unifiable,
     order_named_inds,
     permute_dimnames,
     remaining_dimnames_from_indexing,
@@ -82,13 +83,15 @@ end
     if VERSION >= v"1.1"
         @test 0 == @allocated (()->unify_names_longest((:a, :b), (:a, :_, :c)))()
         @test 0 == @allocated (()->unify_names_shortest((:a, :b), (:a, :_, :c)))()
-        @test 0 == @allocated (()->unify_names((:a, :b), (:a, :_), (:_, :b)))()
+        @test 0 == @allocated (()->names_are_unifiable((:a, :b), (:a, :_)))()
+        @test 0 == @allocated (()->names_are_unifiable((:a, :b), (:a, :c)))()
     else
         @test_broken 0 == @allocated (()->unify_names_longest((:a, :b), (:a, :_, :c)))()
         @test_broken 0 == @allocated (()->unify_names_shortest((:a, :b), (:a, :_, :c)))()
-        @test_broken 0 == @allocated (()->unify_names((:a, :b), (:a, :_), (:_, :b)))()
+        @test_broken 0 == @allocated (()->names_are_unifiable((:a, :b), (:a, :_)))()
+        @test_broken 0 == @allocated (()->names_are_unifiable((:a, :b), (:a, :c)))()
     end
-
+    @test 0 == @allocated (()->names_are_unifiable((:a, :b), (:a, :b)))()
 end