Skip to content

Commit 854bbe1

Browse files
committed
dont allow Tuple in VarName anymore but only allow Lens
1 parent e14ce7c commit 854bbe1

File tree

1 file changed

+94
-106
lines changed

1 file changed

+94
-106
lines changed

src/varname.jl

Lines changed: 94 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ julia> @varname x[:, 1][1+1]
2929
x[:,1][2]
3030
```
3131
"""
32-
struct VarName{sym, T}
32+
struct VarName{sym, T<:Lens}
3333
indexing::T
3434

35-
VarName{sym}(indexing=()) where {sym} = new{sym,typeof(indexing)}(indexing)
35+
VarName{sym}(indexing=IdentityLens()) where {sym} = new{sym,typeof(indexing)}(indexing)
3636
end
3737

3838
"""
@@ -48,7 +48,7 @@ julia> VarName(@varname(x[1][2:3]))
4848
x
4949
```
5050
"""
51-
function VarName(vn::VarName, indexing = ())
51+
function VarName(vn::VarName, indexing=IdentityLens())
5252
return VarName{getsym(vn)}(indexing)
5353
end
5454

@@ -94,7 +94,7 @@ Base.:(==)(x::VarName, y::VarName) = getsym(x) == getsym(y) && getindexing(x) ==
9494

9595
# Composition rules similar to the standard one for lenses, but we need a special
9696
# one for the "empty" `VarName{..., Tuple{}}`.
97-
Base.:(vn::VarName{sym,Tuple{}}, lens::Lens) where {sym} = VarName{sym}(lens)
97+
Base.:(vn::VarName{sym,<:IdentityLens}, lens::Lens) where {sym} = VarName{sym}(lens)
9898
Base.:(vn::VarName{sym,<:Lens}, lens::Lens) where {sym} = VarName{sym}(vn.indexing lens)
9999

100100
function Base.show(io::IO, vn::VarName{<:Any, <:Tuple})
@@ -217,34 +217,14 @@ function subsumes(u::VarName, v::VarName)
217217
return getsym(u) == getsym(v) && subsumes(u.indexing, v.indexing)
218218
end
219219

220-
subsumes(::Tuple{}, ::Tuple{}) = true # x subsumes x
221-
subsumes(::Tuple{}, ::Tuple) = true # x subsumes x[1]
222-
subsumes(::Tuple, ::Tuple{}) = false # x[1] does not subsume x
223-
function subsumes(t::Tuple, u::Tuple) # does x[i]... subsume x[j]...?
224-
return _issubindex(first(t), first(u)) && subsumes(Base.tail(t), Base.tail(u))
225-
end
226-
227-
const AnyIndex = Union{Int, AbstractVector{Int}, Colon}
228-
_issubindex_(::Tuple{Vararg{AnyIndex}}, ::Tuple{Vararg{AnyIndex}}) = false
229-
function _issubindex(t::NTuple{N, AnyIndex}, u::NTuple{N, AnyIndex}) where {N}
230-
return all(_issubrange(j, i) for (i, j) in zip(t, u))
231-
end
232-
233-
const ConcreteIndex = Union{Int, AbstractVector{Int}} # this include all kinds of ranges
234-
235-
"""Determine whether indices `i` are contained in `j`, treating `:` as universal set."""
236-
_issubrange(i::ConcreteIndex, j::ConcreteIndex) = issubset(i, j)
237-
_issubrange(i::Union{ConcreteIndex, Colon}, j::Colon) = true
238-
_issubrange(i::Colon, j::ConcreteIndex) = true
239-
240-
# E.g. `x`, `x[1]`, i.e. `u` is always subsumed by `t`
241-
subsumes(t::Tuple{}, u::Lens) = true
242-
subsumes(t::Lens, u::Tuple{}) = false
243-
244220
# Idea behind `subsumes` for `Lens` is that we traverse the two lenses in parallel,
245221
# checking `subsumes` for every level. This for example means that if we are comparing
246222
# `PropertyLens{:a}` and `PropertyLens{:b}` we immediately know that they do not subsume
247223
# each other since at the same level/depth they access different properties.
224+
# E.g. `x`, `x[1]`, i.e. `u` is always subsumed by `t`
225+
subsumes(t::IdentityLens, u::Lens) = true
226+
subsumes(t::Lens, u::IdentityLens) = false
227+
248228
subsumes(t::ComposedLens, u::ComposedLens) = subsumes(t.outer, u.outer) && subsumes(t.inner, u.inner)
249229

250230
# If `t` is still a composed lens, then there is no way it can subsume `u` since `u` is a
@@ -273,39 +253,114 @@ subsumes(t::ComposedLens{<:IndexLens}, u::IndexLens) = subsumes_index(t, u)
273253
# the indexing behavior must be considered jointly.
274254
# Therefore we must recurse until we reach something that is NOT
275255
# indexing, and then consider the sequence of indices leading up to this.
276-
function subsumes_index(t, u)
256+
"""
257+
subsumes_index(t::Lens, u::Lens)
258+
259+
Return `true` if the indexing represented by `t` subsumes `u`.
260+
261+
This is mostly useful for comparing compositions involving `IndexLens`
262+
e.g. `_[1][2].a[2]` and `_[1][2].a`. In such a scenario we do the following:
263+
1. Combine `[1][2]` into a `Tuple` of indices using [`combine_indices`](@ref).
264+
2. Do the same for `[1][2]`.
265+
3. Compare the two tuples from (1) and (2) using `subsumes_index`.
266+
4. Since we're still undecided, we call `subsume(@lens(_.a[2]), @lens(_.a))`
267+
which then returns `false`.
268+
269+
# Example
270+
```jldoctest; setup=:(using Setfield)
271+
julia> t = @lens(_[1].a); u = @lens(_[1]);
272+
273+
julia> subsumes_index(t, u)
274+
false
275+
276+
julia> subsumes_index(u, t)
277+
true
278+
279+
julia> # `IdentityLens` subsumes all.
280+
subsumes_index(@lens(_), t)
281+
true
282+
283+
julia> # None subsumes `IdentityLens`.
284+
subsumes_index(t, @lens(_))
285+
false
286+
287+
julia> AbstractPPL.subsumes(@lens(_[1][2].a[2]), @lens(_[1][2].a))
288+
false
289+
290+
julia> AbstractPPL.subsumes(@lens(_[1][2].a), @lens(_[1][2].a[2]))
291+
true
292+
```
293+
"""
294+
function subsumes_index(t::Lens, u::Lens)
277295
t_indices, t_next = combine_indices(t)
278296
u_indices, u_next = combine_indices(u)
279297

280-
# Check if the indices indicate that `t` subsumes `u`.
281-
if !subsumes(t_indices, u_indices)
298+
# If we already know that `u` is not subsumed by `t`, return early.
299+
if !subsumes_index(t_indices, u_indices)
282300
return false
283301
end
284302

285303
if t_next === nothing
286304
# Means that there's nothing left for `t` and either nothing
287305
# or something left for `u`, i.e. `t` indeed `subsumes` `u`.
288306
return true
289-
else
290-
# `t` only `subsumes` `u` if `u_next` is also nothing.
291-
if u_next === nothing
292-
return true
293-
else
294-
return false
295-
end
307+
elseif u_next === nothing
308+
# If `t_next` is not `nothing` but `u_ntext` is, then
309+
# `t` does not subsume `u`.
310+
return false
296311
end
297312

298-
# If neither is `nothing` we continue iterating.
313+
# If neither is `nothing` we continue.
299314
return subsumes(t_next, u_next)
300315
end
301316

317+
"""
318+
combine_indices(lens)
319+
320+
Return sequential indexing into a single `Tuple` of indices,
321+
e.g. `x[:][1][2]` becomes `((Colon(), ), (1, ), (2, ))`.
322+
323+
The result is compatible with [`subsumes_index`](@ref) for `Tuple` input.
324+
"""
302325
combine_indices(lens::Lens) = (), lens
303326
combine_indices(lens::IndexLens) = (lens.indices, ), nothing
304327
function combine_indices(lens::ComposedLens{<:IndexLens})
305328
indices, next = combine_indices(lens.inner)
306329
return (lens.outer.indices, indices...), next
307330
end
308331

332+
"""
333+
subsumes_index(left_index::Tuple, right_index::Tuple)
334+
335+
Return `true` if `right_index` is subsumed by `left_index`.
336+
337+
Currently _not_ supported are:
338+
- Boolean indexing, literal `CartesianIndex` (these could be added, though)
339+
- Linear indexing of multidimensional arrays: `x[4]` does not subsume `x[2, 2]` for a matrix `x`
340+
- Trailing ones: `x[2, 1]` does not subsume `x[2]` for a vector `x`
341+
- Dynamic indexing, e.g. `x[1]` does not subsume `x[begin]`.
342+
"""
343+
subsumes_index(::Tuple{}, ::Tuple{}) = true # x subsumes x
344+
subsumes_index(::Tuple{}, ::Tuple) = true # x subsumes x[1]
345+
subsumes_index(::Tuple, ::Tuple{}) = false # x[1] does not subsume x
346+
function subsumes_index(t::Tuple, u::Tuple) # does x[i]... subsume x[j]...?
347+
return _issubindex(first(t), first(u)) && subsumes_index(Base.tail(t), Base.tail(u))
348+
end
349+
350+
const AnyIndex = Union{Int, AbstractVector{Int}, Colon}
351+
_issubindex_(::Tuple{Vararg{AnyIndex}}, ::Tuple{Vararg{AnyIndex}}) = false
352+
function _issubindex(t::NTuple{N, AnyIndex}, u::NTuple{N, AnyIndex}) where {N}
353+
return all(_issubrange(j, i) for (i, j) in zip(t, u))
354+
end
355+
356+
const ConcreteIndex = Union{Int, AbstractVector{Int}} # this include all kinds of ranges
357+
358+
"""Determine whether indices `i` are contained in `j`, treating `:` as universal set."""
359+
_issubrange(i::ConcreteIndex, j::ConcreteIndex) = issubset(i, j)
360+
_issubrange(i::Colon, j::Colon) = true
361+
_issubrange(i::ConcreteIndex, j::Colon) = false
362+
_issubrange(i::Colon, j::ConcreteIndex) = true
363+
309364
"""
310365
concretize(l::Lens, x)
311366
@@ -471,70 +526,3 @@ function vsym(expr::Expr)
471526
end
472527
end
473528

474-
"""
475-
@vinds(expr)
476-
477-
Returns a tuple of tuples of the indices in `expr`.
478-
479-
## Examples
480-
481-
```jldoctest
482-
julia> @vinds x
483-
()
484-
485-
julia> @vinds x[1,1][2,3]
486-
((1, 1), (2, 3))
487-
488-
julia> @vinds x[:,1][2,:]
489-
((Colon(), 1), (2, Colon()))
490-
491-
julia> @vinds x[2:3,1][2,1:2]
492-
((2:3, 1), (2, 1:2))
493-
494-
julia> @vinds x[2:3,2:3][[1,2],[1,2]]
495-
((2:3, 2:3), ([1, 2], [1, 2]))
496-
```
497-
498-
!!! compat "Julia 1.5"
499-
Using `begin` in an indexing expression to refer to the first index requires at least
500-
Julia 1.5.
501-
"""
502-
macro vinds(expr::Union{Expr, Symbol})
503-
return vinds(expr)
504-
end
505-
506-
507-
"""
508-
vinds(expr)
509-
510-
Return the indexing part of the [`@varname`](@ref)-compatible expression `expr` as an expression
511-
suitable for input of the [`VarName`](@ref) constructor.
512-
513-
## Examples
514-
515-
```jldoctest
516-
julia> vinds(:(x[end]))
517-
:((((lastindex)(x),),))
518-
519-
julia> vinds(:(x[1, end]))
520-
:(((1, (lastindex)(x, 2)),))
521-
```
522-
"""
523-
function vinds end
524-
525-
vinds(expr::Symbol) = Expr(:tuple)
526-
function vinds(expr::Expr)
527-
if Meta.isexpr(expr, :ref)
528-
ex = copy(expr)
529-
@static if VERSION < v"1.5.0-DEV.666"
530-
Base.replace_ref_end!(ex)
531-
else
532-
Base.replace_ref_begin_end!(ex)
533-
end
534-
last = Expr(:tuple, ex.args[2:end]...)
535-
init = vinds(ex.args[1]).args
536-
return Expr(:tuple, init..., last)
537-
else
538-
error("Mis-formed variable name $(expr)!")
539-
end
540-
end

0 commit comments

Comments
 (0)