Skip to content

Commit

Permalink
Base.Ordering: do not ever discard order parameter (#34719)
Browse files Browse the repository at this point in the history
Prior to this commit, the `order` parameter was discarded if
either `by` or `order` was passed. However, there is a sane
interpretation for most of these combinations, and we should
use that interpretation for least surprise.

The one case that doesn't have an obvious interpretation is when a
non-trivial `order` is passed together with an `lt` function; in that
case it is better to error out rather than let it pass silently.
  • Loading branch information
tkluck committed Feb 15, 2020
1 parent cd4de55 commit 8eb0f9f
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 13 deletions.
5 changes: 5 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ Standard library changes
------------------------
* The `@timed` macro now returns a `NamedTuple` ([#34149])
* New `supertypes(T)` function returns a tuple of all supertypes of `T` ([#34419]).
* Sorting-related functions such as `sort` that take the keyword arguments `lt`, `rev`, `order`
and `by` now do not discard `order` if `by` or `lt` are passed. In the former case, the
order from `order` is used to compare the values of `by(element)`. In the latter case,
any order different from `Forward` or `Reverse` will raise an error about the
ambiguity.

#### LinearAlgebra
* The BLAS submodule now supports the level-2 BLAS subroutine `hpmv!` ([#34211]).
Expand Down
2 changes: 1 addition & 1 deletion base/compiler/ssair/ir.jl
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@ function count_added_node!(compact::IncrementalCompact, @nospecialize(v))
end

function resort_pending!(compact)
sort!(compact.pending_perm, DEFAULT_STABLE, Order.By(x->compact.pending_nodes[x].pos))
sort!(compact.pending_perm, DEFAULT_STABLE, Order.By(x->compact.pending_nodes[x].pos, Order.Forward))
end

function insert_node!(compact::IncrementalCompact, before, @nospecialize(typ), @nospecialize(val), attach_after::Bool=false)
Expand Down
46 changes: 35 additions & 11 deletions base/ordering.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,14 @@ const DirectOrdering = Union{ForwardOrdering,ReverseOrdering{ForwardOrdering}}
const Forward = ForwardOrdering()
const Reverse = ReverseOrdering()

struct By{T} <: Ordering
struct By{T, O} <: Ordering
by::T
order::O
end

# backwards compatibility with VERSION < v"1.5-"
By(by) = By(by, Forward)

struct Lt{T} <: Ordering
lt::T
end
Expand All @@ -47,9 +51,12 @@ struct Perm{O<:Ordering,V<:AbstractVector} <: Ordering
data::V
end

ReverseOrdering(by::By) = By(by.by, ReverseOrdering(by.order))
ReverseOrdering(perm::Perm) = Perm(ReverseOrdering(perm.order), perm.data)

lt(o::ForwardOrdering, a, b) = isless(a,b)
lt(o::ReverseOrdering, a, b) = lt(o.fwd,b,a)
lt(o::By, a, b) = isless(o.by(a),o.by(b))
lt(o::By, a, b) = lt(o.order,o.by(a),o.by(b))
lt(o::Lt, a, b) = o.lt(a,b)

@propagate_inbounds function lt(p::Perm, a::Integer, b::Integer)
Expand All @@ -58,16 +65,18 @@ lt(o::Lt, a, b) = o.lt(a,b)
lt(p.order, da, db) | (!lt(p.order, db, da) & (a < b))
end

ordtype(o::ReverseOrdering, vs::AbstractArray) = ordtype(o.fwd, vs)
ordtype(o::Perm, vs::AbstractArray) = ordtype(o.order, o.data)
# TODO: here, we really want the return type of o.by, without calling it
ordtype(o::By, vs::AbstractArray) = try typeof(o.by(vs[1])) catch; Any end
ordtype(o::Ordering, vs::AbstractArray) = eltype(vs)

_ord(lt::typeof(isless), by::typeof(identity), order::Ordering) = order
_ord(lt::typeof(isless), by, order::Ordering) = By(by)
_ord(lt, by::typeof(identity), order::Ordering) = Lt(lt)
_ord(lt, by, order::Ordering) = Lt((x,y)->lt(by(x),by(y)))
_ord(lt::typeof(isless), by, order::Ordering) = By(by, order)

function _ord(lt, by, order::Ordering)
if order === Forward
return Lt((x, y) -> lt(by(x), by(y)))
elseif order === Reverse
return Lt((x, y) -> lt(by(y), by(x)))
else
error("Passing both lt= and order= arguments is ambiguous; please pass order=Forward or order=Reverse (or leave default)")
end
end

ord(lt, by, rev::Nothing, order::Ordering=Forward) = _ord(lt, by, order)

Expand All @@ -76,4 +85,19 @@ function ord(lt, by, rev::Bool, order::Ordering=Forward)
return rev ? ReverseOrdering(o) : o
end


# This function is not in use anywhere in Base but we observed
# use in sorting-related packages (#34719). It's probably best to move
# this functionality to those packages in the future; let's remind/force
# ourselves to deprecate this in v2.0.
# The following clause means `if VERSION < v"2.0-"` but it also works during
# bootstrap. For the same reason, we need to write `Int32` instead of `Cint`.
if ccall(:jl_ver_major, Int32, ()) < 2
ordtype(o::ReverseOrdering, vs::AbstractArray) = ordtype(o.fwd, vs)
ordtype(o::Perm, vs::AbstractArray) = ordtype(o.order, o.data)
# TODO: here, we really want the return type of o.by, without calling it
ordtype(o::By, vs::AbstractArray) = try typeof(o.by(vs[1])) catch; Any end
ordtype(o::Ordering, vs::AbstractArray) = eltype(vs)
end

end
2 changes: 1 addition & 1 deletion test/choosetests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function choosetests(choices = [])
"arrayops", "tuple", "reduce", "reducedim", "abstractarray",
"intfuncs", "simdloop", "vecelement", "rational",
"bitarray", "copy", "math", "fastmath", "functional", "iterators",
"operators", "path", "ccall", "parse", "loading", "gmp",
"operators", "ordering", "path", "ccall", "parse", "loading", "gmp",
"sorting", "spawn", "backtrace", "exceptions",
"file", "read", "version", "namedtuple",
"mpfr", "broadcast", "complex",
Expand Down
38 changes: 38 additions & 0 deletions test/ordering.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Test

import Base.Order: Forward, Reverse

# every argument can flip the integer order by passing the right value. Here,
# we enumerate a few of these combinations and check that all these flips
# compound so that in total we either have an increasing or decreasing sort.
for (s1, rev) in enumerate([true, false])
for (s2, lt) in enumerate([>, <, (a, b) -> a - b > 0, (a, b) -> a - b < 0])
for (s3, by) in enumerate([-, +])
for (s4, order) in enumerate([Reverse, Forward])
if iseven(s1 + s2 + s3 + s4)
target = [1, 2, 3]
else
target = [3, 2, 1]
end
@test target == sort([2, 3, 1], rev=rev, lt=lt, by=by, order=order)
end
end
end
end

@test [1 => 3, 2 => 5, 3 => 1] ==
sort([1 => 3, 2 => 5, 3 => 1]) ==
sort([1 => 3, 2 => 5, 3 => 1], by=first) ==
sort([1 => 3, 2 => 5, 3 => 1], rev=true, order=Reverse) ==
sort([1 => 3, 2 => 5, 3 => 1], lt= >, order=Reverse)

@test [3 => 1, 1 => 3, 2 => 5] ==
sort([1 => 3, 2 => 5, 3 => 1], by=last) ==
sort([1 => 3, 2 => 5, 3 => 1], by=last, rev=true, order=Reverse) ==
sort([1 => 3, 2 => 5, 3 => 1], by=last, lt= >, order=Reverse)


struct SomeOtherOrder <: Base.Order.Ordering end

@test_throws ErrorException sort([1, 2, 3], lt=(a, b) -> a - b < 0, order=SomeOtherOrder())

4 comments on commit 8eb0f9f

@nanosoldier
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Executing the daily package evaluation, I will reply here when finished:

@nanosoldier runtests(ALL, isdaily = true)

@nanosoldier
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something went wrong when running your job:

ProcessFailedException(Base.Process[Process(`git fetch --all`, ProcessExited(255))])

cc @maleadt

@nanosoldier
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Executing the daily benchmark build, I will reply here when finished:

@nanosoldier runbenchmarks(ALL, isdaily = true)

@nanosoldier
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @ararslan

Please sign in to comment.