Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove compat at-dot, at-view, and at-views macros #627

Merged
merged 1 commit into from
Aug 31, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,6 @@ Currently, the `@compat` macro supports the following syntaxes:

## New functions, macros, and methods

* `@views` takes an expression and converts all slices to views ([#20164]), while
`@view` ([#16564]) converts a single array reference to a view ([#20164]).

* `@__dot__` takes an expression and converts all assignments, function calls,
and operators to their broadcasting "dot-call" equivalents ([#20321]). In Julia 0.6, this
can be abbreviated `@.`, but that macro name does not parse in earlier Julia versions.
For this to work in older versions of Julia (prior to 0.5) that don't have dot calls,
you should instead use `@dotcompat`, which combines the `@__dot__` and `@compat` macros.

* [`normalize`](http://docs.julialang.org/en/latest/stdlib/linalg/?highlight=normalize#Base.normalize) and [`normalize!`](http://docs.julialang.org/en/latest/stdlib/linalg/?highlight=normalize#Base.normalize!), normalizes a vector with respect to the p-norm ([#13681])

* `redirect_stdout`, `redirect_stderr`, and `redirect_stdin` take an optional function as a first argument, `redirect_std*(f, stream)`, so that one may use `do` block syntax (as first available for Julia 0.6)
Expand Down Expand Up @@ -497,7 +488,6 @@ includes this fix. Find the minimum version from there.
`Compat <version>`

[#13681]: https://github.com/JuliaLang/julia/issues/13681
[#16564]: https://github.com/JuliaLang/julia/issues/16564
[#16986]: https://github.com/JuliaLang/julia/issues/16986
[#17302]: https://github.com/JuliaLang/julia/issues/17302
[#17323]: https://github.com/JuliaLang/julia/issues/17323
Expand All @@ -517,8 +507,6 @@ includes this fix. Find the minimum version from there.
[#19950]: https://github.com/JuliaLang/julia/issues/19950
[#20005]: https://github.com/JuliaLang/julia/issues/20005
[#20022]: https://github.com/JuliaLang/julia/issues/20022
[#20164]: https://github.com/JuliaLang/julia/issues/20164
[#20321]: https://github.com/JuliaLang/julia/issues/20321
[#20407]: https://github.com/JuliaLang/julia/issues/20407
[#20974]: https://github.com/JuliaLang/julia/issues/20974
[#21197]: https://github.com/JuliaLang/julia/issues/21197
Expand Down
206 changes: 6 additions & 200 deletions src/arraymacros.jl
Original file line number Diff line number Diff line change
@@ -1,201 +1,7 @@
# Julia 0.6 macros to aid in vectorization: @view, @views, @__dot__ (@.),
# backported from Julia 0.6.

# prior to julia#20247, the replace_ref_end! macro had hygiene bugs
if VERSION < v"0.6.0-dev.2406"
function trailingsize(A, n)
s = 1
for i=n:ndims(A)
s *= size(A,i)
end
return s
end
replace_ref_end!(ex) = replace_ref_end_!(ex, nothing)[1]
# replace_ref_end_!(ex,withex) returns (new ex, whether withex was used)
function replace_ref_end_!(ex, withex)
used_withex = false
if isa(ex,Symbol) && ex == :end
withex === nothing && error("Invalid use of end")
return withex, true
elseif isa(ex,Expr)
if ex.head == :ref
ex.args[1], used_withex = replace_ref_end_!(ex.args[1],withex)
S = isa(ex.args[1],Symbol) ? ex.args[1]::Symbol : gensym(:S) # temp var to cache ex.args[1] if needed
used_S = false # whether we actually need S
# new :ref, so redefine withex
nargs = length(ex.args)-1
if nargs == 0
return ex, used_withex
elseif nargs == 1
# replace with endof(S)
ex.args[2], used_S = replace_ref_end_!(ex.args[2],:($endof($S)))
else
n = 1
J = endof(ex.args)
for j = 2:J-1
exj, used = replace_ref_end_!(ex.args[j],:($size($S,$n)))
used_S |= used
ex.args[j] = exj
if isa(exj,Expr) && exj.head == :...
# splatted object
exjs = exj.args[1]
n = :($n + length($exjs))
elseif isa(n, Expr)
# previous expression splatted
n = :($n + 1)
else
# an integer
n += 1
end
end
ex.args[J], used = replace_ref_end_!(ex.args[J],:($trailingsize($S,$n)))
used_S |= used
end
if used_S && S !== ex.args[1]
S0 = ex.args[1]
ex.args[1] = S
ex = Expr(:let, ex, :($S = $S0))
end
else
# recursive search
for i = eachindex(ex.args)
ex.args[i], used = replace_ref_end_!(ex.args[i],withex)
used_withex |= used
end
end
end
ex, used_withex
end
end

if !isdefined(Base, Symbol("@view"))
macro view(ex)
if Meta.isexpr(ex, :ref)
ex = replace_ref_end!(ex)
if Meta.isexpr(ex, :ref)
ex = Expr(:call, view, ex.args...)
else # ex replaced by let ...; foo[...]; end
assert(Meta.isexpr(ex, :let) && Meta.isexpr(ex.args[1], :ref))
ex.args[1] = Expr(:call, view, ex.args[1].args...)
end
Expr(:&&, true, esc(ex))
else
throw(ArgumentError("Invalid use of @view macro: argument must be a reference expression A[...]."))
end
end
export @view
end

if !isdefined(Base, Symbol("@views"))
maybeview(A, args...) = getindex(A, args...)
maybeview(A::AbstractArray, args...) = view(A, args...)
maybeview(A::AbstractArray, args::Number...) = getindex(A, args...)
maybeview(A) = getindex(A)
maybeview(A::AbstractArray) = getindex(A)

_views(x) = x
function _views(ex::Expr)
if ex.head in (:(=), :(.=))
# don't use view for ref on the lhs of an assignment,
# but still use views for the args of the ref:
lhs = ex.args[1]
Expr(ex.head, Meta.isexpr(lhs, :ref) ?
Expr(:ref, map(_views, lhs.args)...) : _views(lhs),
_views(ex.args[2]))
elseif ex.head == :ref
Expr(:call, maybeview, map(_views, ex.args)...)
else
h = string(ex.head)
# don't use view on the lhs of an op-assignment a[i...] += ...
if last(h) == '=' && Meta.isexpr(ex.args[1], :ref)
lhs = ex.args[1]

# temp vars to avoid recomputing a and i,
# which will be assigned in a let block:
a = gensym(:a)
i = [gensym(:i) for k = 1:length(lhs.args)-1]

# for splatted indices like a[i, j...], we need to
# splat the corresponding temp var.
I = similar(i, Any)
for k = 1:length(i)
if Meta.isexpr(lhs.args[k+1], :...)
I[k] = Expr(:..., i[k])
lhs.args[k+1] = lhs.args[k+1].args[1] # unsplat
else
I[k] = i[k]
end
end

Expr(:let,
Expr(first(h) == '.' ? :(.=) : :(=), :($a[$(I...)]),
Expr(:call, Symbol(h[1:end-1]),
:($maybeview($a, $(I...))),
map(_views, ex.args[2:end])...)),
:($a = $(_views(lhs.args[1]))),
[:($(i[k]) = $(_views(lhs.args[k+1]))) for k=1:length(i)]...)
else
Expr(ex.head, map(_views, ex.args)...)
end
end
end

macro views(x)
esc(_views(replace_ref_end!(x)))
end
export @views
end

# we can't define @. because that doesn't parse in Julia < 0.6, but
# we can define @__dot__, which is what @. is sugar for:
if !isdefined(Base, Symbol("@__dot__"))
dottable(x) = false # avoid dotting spliced objects (e.g. view calls inserted by @view)
dottable(x::Symbol) = !Base.isoperator(x) || first(string(x)) != '.' || x == :.. # don't add dots to dot operators
dottable(x::Expr) = x.head != :$
undot(x) = x
function undot(x::Expr)
if x.head == :.=
Expr(:(=), x.args...)
elseif x.head == :block # occurs in for x=..., y=...
Expr(:block, map(undot, x.args)...)
else
x
end
end
__dot__(x) = x
function __dot__(x::Expr)
dotargs = map(__dot__, x.args)
if x.head == :call && dottable(x.args[1])
Expr(:., dotargs[1], Expr(:tuple, dotargs[2:end]...))
elseif x.head == :$
x.args[1]
elseif x.head == :let # don't add dots to "let x=... assignments
Expr(:let, dotargs[1], map(undot, dotargs[2:end])...)
elseif x.head == :for # don't add dots to for x=... assignments
Expr(:for, undot(dotargs[1]), dotargs[2])
elseif (x.head == :(=) || x.head == :function || x.head == :macro) &&
Meta.isexpr(x.args[1], :call) # function or macro definition
Expr(x.head, x.args[1], dotargs[2])
else
head = string(x.head)
if last(head) == '=' && first(head) != '.'
Expr(Symbol('.',head), dotargs...)
else
Expr(x.head, dotargs...)
end
end
end
macro __dot__(x)
esc(__dot__(x))
end
macro dotcompat(x)
esc(_compat(__dot__(x)))
end
export @__dot__, @dotcompat
else
# in 0.6, use the __dot__ function from Base.Broadcast
macro dotcompat(x)
esc(_compat(Base.Broadcast.__dot__(x)))
end
export @dotcompat
# TODO deprecate
# this was defined for use with Julia versions prior to 0.5
# (see https://github.com/JuliaLang/Compat.jl/pull/316)
macro dotcompat(x)
esc(_compat(Base.Broadcast.__dot__(x)))
end
export @dotcompat
97 changes: 4 additions & 93 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -297,106 +297,17 @@ eval(Expr(
@test !isabstracttype(ConcreteFoo200061)
@test !isabstracttype(StridedArray)

# @view and @views tests copied from Base
let X = reshape(1:24,2,3,4), Y = 4:-1:1
@test isa(@view(X[1:3]), SubArray)

# TODO remove these tests when deprecating @dotcompat
let X = reshape(1:24,2,3,4)
@test X[1:end] == @dotcompat (@view X[1:end]) # test compatibility of @. and @view
@test X[1:end-3] == @view X[1:end-3]
@test X[1:end,2,2] == @view X[1:end,2,2]
@test reshape(X[1,2,1:end-2],2) == @view X[1,2,1:end-2]
@test reshape(X[1,2,Y[2:end]],3) == @view X[1,2,Y[2:end]]
@test reshape(X[1:end,2,Y[2:end]],2,3) == @view X[1:end,2,Y[2:end]]

u = (1,2:3)
@test reshape(X[u...,2:end],2,3) == @view X[u...,2:end]
@test reshape(X[(1,)...,(2,)...,2:end],3) == @view X[(1,)...,(2,)...,2:end]

# the following tests fail on 0.5 because of bugs in the 0.5 Base.@view
# macro (a bugfix is scheduled to be backported from 0.6: julia#20247)
if !isdefined(Base, Symbol("@view")) || VERSION ≥ v"0.6.0-dev.2406"
# test macro hygiene
let size=(x,y)-> error("should not happen"), Base=nothing
@test X[1:end,2,2] == @view X[1:end,2,2]
end

# test that side effects occur only once
let foo = typeof(X)[X]
@test X[2:end-1] == @view (push!(foo,X)[1])[2:end-1]
@test foo == typeof(X)[X, X]
end
end

# test @views macro
@views @compat let f!(x) = x[1:end-1] .+= x[2:end].^2
x = [1,2,3,4]
f!(x)
@test x == [5,11,19,4]
@test isa(x[1:3],SubArray)
@test x[2] === 11
@test Dict((1:3) => 4)[1:3] === 4
x[1:2] .= 0
@test x == [0,0,19,4]
x[1:2] .= 5:6
@test x == [5,6,19,4]
f!(x[3:end])
@test x == [5,6,35,4]
x[Y[2:3]] .= 7:8
@test x == [5,8,7,4]
@views let
x = [5,8,7,4]
@dotcompat x[([3],)..., ()...] += 3 # @. should convert to .+=, test compatibility with @views
@test x == [5,8,10,4]
i = Int[]
# test that lhs expressions in update operations are evaluated only once:
x[push!(i,4)[1]] += 5
@test x == [5,8,10,9] && i == [4]
x[push!(i,3)[end]] += 2
@test x == [5,8,12,9] && i == [4,3]
@dotcompat x[3:end] = 0 # make sure @. works with end expressions in @views
@test x == [5,8,0,0]
end
# same tests, but make sure we can switch the order of @compat and @views
@compat @views let f!(x) = x[1:end-1] .+= x[2:end].^2
x = [1,2,3,4]
f!(x)
@test x == [5,11,19,4]
@test isa(x[1:3],SubArray)
@test x[2] === 11
@test Dict((1:3) => 4)[1:3] === 4
x[1:2] .= 0
@test x == [0,0,19,4]
x[1:2] .= 5:6
@test x == [5,6,19,4]
f!(x[3:end])
@test x == [5,6,35,4]
x[Y[2:3]] .= 7:8
@test x == [5,8,7,4]
@dotcompat x[([3],)..., ()...] += 3 # @. should convert to .+=, test compatibility with @views
@test x == [5,8,10,4]
i = Int[]
# test that lhs expressions in update operations are evaluated only once:
x[push!(i,4)[1]] += 5
@test x == [5,8,10,9] && i == [4]
x[push!(i,3)[end]] += 2
@test x == [5,8,12,9] && i == [4,3]
@dotcompat x[3:end] = 0 # make sure @. works with end expressions in @views
@test x == [5,8,0,0]
end
@views @test isa(X[1:3], SubArray)
@test X[1:end] == @views X[1:end]
@test X[1:end-3] == @views X[1:end-3]
@test X[1:end,2,2] == @views X[1:end,2,2]
@test reshape(X[1,2,1:end-2],2) == @views X[1,2,1:end-2]
@test reshape(X[1,2,Y[2:end]],3) == @views X[1,2,Y[2:end]]
@test reshape(X[1:end,2,Y[2:end]],2,3) == @views X[1:end,2,Y[2:end]]
@test reshape(X[u...,2:end],2,3) == @views X[u...,2:end]
@test reshape(X[(1,)...,(2,)...,2:end],3) == @views X[(1,)...,(2,)...,2:end]

# test macro hygiene
let size=(x,y)-> error("should not happen"), Base=nothing
@test X[1:end,2,2] == @views X[1:end,2,2]
end
end

# @. (@__dot__) tests, from base:
let x = [4, -9, 1, -16]
@test [2, 3, 4, 5] == @dotcompat(1 + sqrt($sort(abs(x))))
Expand Down