Skip to content

Commit

Permalink
Remove compat at-dot, at-view, and at-views macros
Browse files Browse the repository at this point in the history
These were added in #316 for Julia versions older than 0.6. The 
at-dotcompat macro is kept to avoid breakage.
  • Loading branch information
martinholters committed Aug 31, 2018
1 parent 96e41ae commit 72b1b37
Show file tree
Hide file tree
Showing 3 changed files with 10 additions and 305 deletions.
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

0 comments on commit 72b1b37

Please sign in to comment.