From 1e267f8f8047d68e7c2e006ef0f86b6c02bc02b5 Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Tue, 18 May 2021 03:57:38 +0200 Subject: [PATCH 01/42] allowed use of Setfield.lens in VarName --- Project.toml | 1 + src/varname.jl | 13 ++++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Project.toml b/Project.toml index fc49be31..be53f09e 100644 --- a/Project.toml +++ b/Project.toml @@ -7,6 +7,7 @@ version = "0.1.4" [deps] AbstractMCMC = "80f14c24-f653-4e6a-9b94-39d6b0f70001" +Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46" StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" [compat] diff --git a/src/varname.jl b/src/varname.jl index 893b40ca..504b8062 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -1,3 +1,5 @@ +using Setfield + """ VarName{sym}(indexing::Tuple=()) @@ -26,10 +28,10 @@ julia> @varname x[:, 1][1+1] x[Colon(),1][2] ``` """ -struct VarName{sym, T<:Tuple} +struct VarName{sym, T} indexing::T - VarName{sym}(indexing::Tuple=()) where {sym} = new{sym,typeof(indexing)}(indexing) + VarName{sym}(indexing=()) where {sym} = new{sym,typeof(indexing)}(indexing) end """ @@ -89,7 +91,7 @@ getindexing(vn::VarName) = vn.indexing Base.hash(vn::VarName, h::UInt) = hash((getsym(vn), getindexing(vn)), h) Base.:(==)(x::VarName, y::VarName) = getsym(x) == getsym(y) && getindexing(x) == getindexing(y) -function Base.show(io::IO, vn::VarName) +function Base.show(io::IO, vn::VarName{<:Any, <:Tuple}) print(io, getsym(vn)) for indices in getindexing(vn) print(io, "[") @@ -98,6 +100,11 @@ function Base.show(io::IO, vn::VarName) end end +function Base.show(io::IO, vn::VarName{<:Any, <:Lens}) + print(io, getsym(vn)) + Setfield.print_application(io, vn.indexing) +end + """ Symbol(vn::VarName) From 286e211b1405d3a3a2811b37140fc160728ab4c3 Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Tue, 18 May 2021 04:44:47 +0200 Subject: [PATCH 02/42] implemented subsumes for lenses --- src/varname.jl | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/varname.jl b/src/varname.jl index 504b8062..f28389b2 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -1,4 +1,5 @@ using Setfield +import Setfield: PropertyLens, ComposedLens, IdentityLens, IndexLens """ VarName{sym}(indexing::Tuple=()) @@ -219,6 +220,31 @@ _issubrange(i::ConcreteIndex, j::ConcreteIndex) = issubset(i, j) _issubrange(i::Union{ConcreteIndex, Colon}, j::Colon) = true _issubrange(i::Colon, j::ConcreteIndex) = true +# Idea behind `subsumes` for `Lens` is that we traverse the two lenses in parallel, +# checking `subsumes` for every level. This for example means that if we are comparing +# `PropertyLens{:a}` and `PropertyLens{:b}` we immediately know that they do not subsume +# each other since at the same level/depth they access different properties. +subsumes(t::ComposedLens, u::ComposedLens) = subsumes(t.outer, u.outer) && subsumes(t.inner, u.inner) + +# If `t` is still a composed lens, then there is no way it can subsume `u` since `u` is a +# leaf of the "lens-tree". +subsumes(t::ComposedLens, u::PropertyLens) = false +# Here we need to check if `u.outer` (i.e. the next lens to be applied from `u`) is +# subsumed by `t`, since this would mean that the rest of the composition is also subsumed +# by `t`. +subsumes(t::PropertyLens, u::ComposedLens) = subsumes(t, u.outer) + +# For `PropertyLens` either they have the same `name` and thus they are indeed the same. +subsumes(t::PropertyLens{name}, u::PropertyLens{name}) where {name} = true +# Otherwise they represent different properties, and thus are not the same. +subsumes(t::PropertyLens, u::PropertyLens) = false + +# Indices subsumes if they are subindices, i.e. we just call `_issubindex`. +# FIXME: Does not support `DynamicIndexLens`. +# FIXME: Does not correctly handle cases such as `subsumes(x, x[:])` +# (but neither did old implementation). +subsumes(t::IndexLens, u::IndexLens) = _issubindex(t.indices, u.indices) + """ From eb65b764bd998b36b8ebac24b5a9bbb8e13fdb3c Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Tue, 18 May 2021 04:44:59 +0200 Subject: [PATCH 03/42] varname macro now uses lenses by default --- src/varname.jl | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/varname.jl b/src/varname.jl index f28389b2..e8eb39c3 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -279,13 +279,26 @@ julia> @varname(x[1,2][1+5][45][3]).indexing Julia 1.5. """ macro varname(expr::Union{Expr, Symbol}) - return esc(varname(expr)) + return varname(expr) end varname(sym::Symbol) = :($(AbstractPPL.VarName){$(QuoteNode(sym))}()) function varname(expr::Expr) - if Meta.isexpr(expr, :ref) - sym, inds = vsym(expr), vinds(expr) + if Meta.isexpr(expr, :ref) || Meta.isexpr(expr, :.) + sym = vsym(expr) + + # Need to recursively unwrap until we reach the outer-most variable. + # TODO: implement as recursion? + curexpr = expr + while !(curexpr.args[1] isa Symbol) + curexpr = curexpr.args[1] + end + + # Then we replace the variable with `_`, to get an expression we can + # use `lensmacro` on. + curexpr.args[1] = :_ + inds = Setfield.lensmacro(identity, expr) + return :($(AbstractPPL.VarName){$(QuoteNode(sym))}($inds)) else error("Malformed variable name $(expr)!") @@ -325,7 +338,7 @@ function vsym end vsym(expr::Symbol) = expr function vsym(expr::Expr) - if Meta.isexpr(expr, :ref) + if Meta.isexpr(expr, :ref) || Meta.isexpr(expr, :.) return vsym(expr.args[1]) else error("Malformed variable name $(expr)!") From b72243a9a0656f14104901e63de8da1a7238c5d0 Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Tue, 27 Jul 2021 16:59:49 +0100 Subject: [PATCH 04/42] added concretize method for instantiating a variable on a particular input --- src/varname.jl | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/varname.jl b/src/varname.jl index 468e81da..89223996 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -1,5 +1,5 @@ using Setfield -import Setfield: PropertyLens, ComposedLens, IdentityLens, IndexLens +import Setfield: PropertyLens, ComposedLens, IdentityLens, IndexLens, DynamicIndexLens """ VarName{sym}(indexing::Tuple=()) @@ -48,7 +48,7 @@ julia> VarName(@varname(x[1][2:3])) x ``` """ -function VarName(vn::VarName, indexing::Tuple = ()) +function VarName(vn::VarName, indexing = ()) return VarName{getsym(vn)}(indexing) end @@ -106,6 +106,8 @@ function Base.show(io::IO, vn::VarName{<:Any, <:Lens}) Setfield.print_application(io, vn.indexing) end +Setfield.print_application(io::IO, l::IndexLens) = print(io, "[", join(map(prettify_index, l.indices), ", "), "]") + prettify_index(x) = string(x) prettify_index(::Colon) = ":" @@ -247,7 +249,24 @@ subsumes(t::PropertyLens, u::PropertyLens) = false # (but neither did old implementation). subsumes(t::IndexLens, u::IndexLens) = _issubindex(t.indices, u.indices) +""" + concretize(l::Lens, x) + +Return `l` instantiated on `x`, i.e. any runtime information evaluated using `x`. +""" + +concretize(I::Lens, x) = I +concretize(I::DynamicIndexLens, x) = IndexLens(I.f(x)) +function concretize(I::ComposedLens, x) + x_inner = get(x, I.outer) + return ComposedLens(concretize(I.outer, x), concretize(I.inner, x_inner)) +end +""" + concretize(vn::VarName, x) +Return `vn` instantiated on `x`, i.e. any runtime information evaluated using `x`. +""" +concretize(vn::VarName, x) = VarName(vn, concretize(vn.indexing, x)) """ @varname(expr) From 122092dd53c619b9ee8a59115694b9883ca9b1d0 Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Tue, 27 Jul 2021 17:05:18 +0100 Subject: [PATCH 05/42] added some type-piracy because its a free world --- src/varname.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/varname.jl b/src/varname.jl index 89223996..5c05fc5f 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -106,7 +106,9 @@ function Base.show(io::IO, vn::VarName{<:Any, <:Lens}) Setfield.print_application(io, vn.indexing) end +# TODO: Should this really go here? Setfield.print_application(io::IO, l::IndexLens) = print(io, "[", join(map(prettify_index, l.indices), ", "), "]") +Setfield.print_application(io::IO, l::DynamicIndexLens) = print(io, l, "(_)") prettify_index(x) = string(x) prettify_index(::Colon) = ":" From 29e051bd912d753af4ff397849bd414acbf6b378 Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Tue, 27 Jul 2021 17:36:23 +0100 Subject: [PATCH 06/42] fixed and added doctests --- src/varname.jl | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/varname.jl b/src/varname.jl index 5c05fc5f..46715e89 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -267,6 +267,22 @@ end concretize(vn::VarName, x) Return `vn` instantiated on `x`, i.e. any runtime information evaluated using `x`. + +# Examples +```jldoctest +julia> x = (a = [1.0 2.0;], ); + +julia> vn = @varname(x.a[1, :]) +x.a[1, :] + +julia> AbstractPPL.concretize(vn, x) +x.a[1, :] + +julia> vn = @varname(x.a[1, end][:]); + +julia> AbstractPPL.concretize(vn, x) +x.a[1, 2][:] +``` """ concretize(vn::VarName, x) = VarName(vn, concretize(vn.indexing, x)) @@ -285,16 +301,16 @@ julia> @varname(x).indexing () julia> @varname(x[1]).indexing -((1,),) +(@lens _[1]) julia> @varname(x[:, 1]).indexing -((Colon(), 1),) +(@lens _[:, 1]) julia> @varname(x[:, 1][2]).indexing -((Colon(), 1), (2,)) +(@lens _[:, 1][2]) julia> @varname(x[1,2][1+5][45][3]).indexing -((1, 2), (6,), (45,), (3,)) +(@lens _[1, 2][6][45][3]) ``` !!! compat "Julia 1.5" From 6092b7cc095c39be1b75727211cd469d06aff43c Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Thu, 29 Jul 2021 19:15:30 +0100 Subject: [PATCH 07/42] fixed varname --- src/varname.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/varname.jl b/src/varname.jl index 46715e89..6df3bb3a 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -324,11 +324,12 @@ end varname(sym::Symbol) = :($(AbstractPPL.VarName){$(QuoteNode(sym))}()) function varname(expr::Expr) if Meta.isexpr(expr, :ref) || Meta.isexpr(expr, :.) - sym = vsym(expr) + expr_new = deepcopy(expr) + sym = vsym(expr_new) # Need to recursively unwrap until we reach the outer-most variable. # TODO: implement as recursion? - curexpr = expr + curexpr = expr_new while !(curexpr.args[1] isa Symbol) curexpr = curexpr.args[1] end @@ -336,7 +337,7 @@ function varname(expr::Expr) # Then we replace the variable with `_`, to get an expression we can # use `lensmacro` on. curexpr.args[1] = :_ - inds = Setfield.lensmacro(identity, expr) + inds = Setfield.lensmacro(identity, expr_new) return :($(AbstractPPL.VarName){$(QuoteNode(sym))}($inds)) else From 2259b30c11c1ba44ec392311c093b98746b1de1a Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Thu, 29 Jul 2021 19:15:41 +0100 Subject: [PATCH 08/42] fixed varname doctest --- src/varname.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/varname.jl b/src/varname.jl index 6df3bb3a..b9137a84 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -20,13 +20,13 @@ indexing expression through the [`@varname`](@ref) convenience macro. ```jldoctest julia> vn = VarName{:x}(((Colon(), 1), (2,))) -x[:,1][2] +x[:, 1][2] julia> vn.indexing ((Colon(), 1), (2,)) julia> @varname x[:, 1][1+1] -x[:,1][2] +x[:, 1][2] ``` """ struct VarName{sym, T} From 298e1fc06c2c81b3f92d719ab5ca301f5cc33957 Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Thu, 29 Jul 2021 19:16:21 +0100 Subject: [PATCH 09/42] allow specifying whether we want to concretize or not in varname --- src/varname.jl | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/varname.jl b/src/varname.jl index b9137a84..0042f167 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -321,8 +321,8 @@ macro varname(expr::Union{Expr, Symbol}) return varname(expr) end -varname(sym::Symbol) = :($(AbstractPPL.VarName){$(QuoteNode(sym))}()) -function varname(expr::Expr) +varname(sym::Symbol; concretize=true) = :($(AbstractPPL.VarName){$(QuoteNode(sym))}()) +function varname(expr::Expr; concretize=true) if Meta.isexpr(expr, :ref) || Meta.isexpr(expr, :.) expr_new = deepcopy(expr) sym = vsym(expr_new) @@ -339,7 +339,12 @@ function varname(expr::Expr) curexpr.args[1] = :_ inds = Setfield.lensmacro(identity, expr_new) - return :($(AbstractPPL.VarName){$(QuoteNode(sym))}($inds)) + # TODO: Can we do better, i.e. only check if we have `DynamicLens`? + return if concretize + :($(AbstractPPL.concretize)($(AbstractPPL.VarName){$(QuoteNode(sym))}($inds), $(esc(sym)))) + else + :($(AbstractPPL.VarName){$(QuoteNode(sym))}($inds)) + end else error("Malformed variable name $(expr)!") end From 9dd6cf77d39a677c9ff350b15bd1e6e982066058 Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Thu, 29 Jul 2021 19:16:48 +0100 Subject: [PATCH 10/42] need to escape indices --- src/varname.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/varname.jl b/src/varname.jl index 0042f167..578e9995 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -450,7 +450,7 @@ function vinds(expr::Expr) else Base.replace_ref_begin_end!(ex) end - last = Expr(:tuple, ex.args[2:end]...) + last = esc(Expr(:tuple, ex.args[2:end]...)) init = vinds(ex.args[1]).args return Expr(:tuple, init..., last) else From b94bcce94dd61a5efe0084c699892bbdd01f0e9d Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Thu, 29 Jul 2021 22:08:08 +0100 Subject: [PATCH 11/42] fixed show --- src/varname.jl | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/varname.jl b/src/varname.jl index 578e9995..ac6daa54 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -103,12 +103,22 @@ end function Base.show(io::IO, vn::VarName{<:Any, <:Lens}) print(io, getsym(vn)) - Setfield.print_application(io, vn.indexing) + _print_application(io, vn.indexing) end -# TODO: Should this really go here? -Setfield.print_application(io::IO, l::IndexLens) = print(io, "[", join(map(prettify_index, l.indices), ", "), "]") -Setfield.print_application(io::IO, l::DynamicIndexLens) = print(io, l, "(_)") +_print_application(io::IO, l::Lens) = Setfield.print_application(io, l) +function _print_application(io::IO, l::ComposedLens) + _print_application(io, l.outer) + _print_application(io, l.inner) +end +function _print_application(io::IO, l::ComposedLens{}) + _print_application(io, l.outer) + _print_application(io, l.inner) +end +_print_application(io::IO, l::IndexLens) = print(io, "[", join(map(prettify_index, l.indices), ","), "]") +# This is a bit weird but whatever. We're almost always going to +# `concretize` anyways. +_print_application(io::IO, l::DynamicIndexLens) = print(io, l, "(_)") prettify_index(x) = string(x) prettify_index(::Colon) = ":" From 9af36946b7dbe9aa0027c51ed3b04d1c44899e4c Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Thu, 29 Jul 2021 22:08:33 +0100 Subject: [PATCH 12/42] fix the subsume behavior --- src/varname.jl | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/src/varname.jl b/src/varname.jl index ac6daa54..779f452e 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -236,6 +236,9 @@ _issubrange(i::ConcreteIndex, j::ConcreteIndex) = issubset(i, j) _issubrange(i::Union{ConcreteIndex, Colon}, j::Colon) = true _issubrange(i::Colon, j::ConcreteIndex) = true +# E.g. `x`, `x[1]`, i.e. `u` is always subsumed by `t` +subsumes(t::Tuple{}, u::Lens) = true + # Idea behind `subsumes` for `Lens` is that we traverse the two lenses in parallel, # checking `subsumes` for every level. This for example means that if we are comparing # `PropertyLens{:a}` and `PropertyLens{:b}` we immediately know that they do not subsume @@ -259,7 +262,47 @@ subsumes(t::PropertyLens, u::PropertyLens) = false # FIXME: Does not support `DynamicIndexLens`. # FIXME: Does not correctly handle cases such as `subsumes(x, x[:])` # (but neither did old implementation). -subsumes(t::IndexLens, u::IndexLens) = _issubindex(t.indices, u.indices) +subsumes(t::IndexLens, u::IndexLens) = subsumes(t.indices, u.indices) +subsumes(t::ComposedLens{<:IndexLens}, u::ComposedLens{<:IndexLens}) = subsumes_index(t, u) +subsumes(t::IndexLens, u::ComposedLens{<:IndexLens}) = subsumes_index(t, u) +subsumes(t::ComposedLens{<:IndexLens}, u::IndexLens) = subsumes_index(t, u) + +# Since expressions such as `x[:][:][:][1]` and `x[1]` are equal, +# the indexing behavior must be considered jointly. +# Therefore we must recurse until we reach something that is NOT +# indexing, and then consider the sequence of indices leading up to this. +function subsumes_index(t, u) + t_indices, t_next = combine_indices(t) + u_indices, u_next = combine_indices(u) + + # Check if the indices indicate that `t` subsumes `u`. + if !subsumes(t_indices, u_indices) + return false + end + + if t_next === nothing + # Means that there's nothing left for `t` and either nothing + # or something left for `u`, i.e. `t` indeed `subsumes` `u`. + return true + else + # `t` only `subsumes` `u` if `u_next` is also nothing. + if u_next === nothing + return true + else + return false + end + end + + # If neither is `nothing` we continue iterating. + return subsumes(t_next, u_next) +end + +combine_indices(lens::Lens) = (), lens +combine_indices(lens::IndexLens) = (lens.indices, ), nothing +function combine_indices(lens::ComposedLens{<:IndexLens}) + indices, next = combine_indices(lens.inner) + return (lens.outer.indices, indices...), next +end """ concretize(l::Lens, x) From e26307fe60971eaf209f247da860b0dfc6cc98a6 Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Thu, 29 Jul 2021 22:08:48 +0100 Subject: [PATCH 13/42] fixed doctests --- src/varname.jl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/varname.jl b/src/varname.jl index 779f452e..0795584a 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -20,13 +20,13 @@ indexing expression through the [`@varname`](@ref) convenience macro. ```jldoctest julia> vn = VarName{:x}(((Colon(), 1), (2,))) -x[:, 1][2] +x[:,1][2] julia> vn.indexing ((Colon(), 1), (2,)) julia> @varname x[:, 1][1+1] -x[:, 1][2] +x[:,1][2] ``` """ struct VarName{sym, T} @@ -80,7 +80,7 @@ Return the indexing tuple of the Julia variable used to generate `vn`. ```jldoctest julia> getindexing(@varname(x[1][2:3])) -((1,), (2:3,)) +(@lens _[1][2:3]) julia> getindexing(@varname(y)) () @@ -326,15 +326,15 @@ Return `vn` instantiated on `x`, i.e. any runtime information evaluated using `x julia> x = (a = [1.0 2.0;], ); julia> vn = @varname(x.a[1, :]) -x.a[1, :] +x.a[1,:] julia> AbstractPPL.concretize(vn, x) -x.a[1, :] +x.a[1,:] julia> vn = @varname(x.a[1, end][:]); julia> AbstractPPL.concretize(vn, x) -x.a[1, 2][:] +x.a[1,2][:] ``` """ concretize(vn::VarName, x) = VarName(vn, concretize(vn.indexing, x)) @@ -357,10 +357,10 @@ julia> @varname(x[1]).indexing (@lens _[1]) julia> @varname(x[:, 1]).indexing -(@lens _[:, 1]) +(@lens _[Colon(), 1]) julia> @varname(x[:, 1][2]).indexing -(@lens _[:, 1][2]) +(@lens _[Colon(), 1][2]) julia> @varname(x[1,2][1+5][45][3]).indexing (@lens _[1, 2][6][45][3]) From fff6f1fb779c7321221faec9d90a8e81ec90319c Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Thu, 29 Jul 2021 22:09:03 +0100 Subject: [PATCH 14/42] made the construction of the lens for varname a bit nicer --- src/varname.jl | 56 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/src/varname.jl b/src/varname.jl index 0795584a..e3439291 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -370,27 +370,16 @@ julia> @varname(x[1,2][1+5][45][3]).indexing Using `begin` in an indexing expression to refer to the first index requires at least Julia 1.5. """ -macro varname(expr::Union{Expr, Symbol}) - return varname(expr) +macro varname(expr::Union{Expr, Symbol}, concretize=false) + return varname(expr; concretize=concretize) end -varname(sym::Symbol; concretize=true) = :($(AbstractPPL.VarName){$(QuoteNode(sym))}()) -function varname(expr::Expr; concretize=true) +varname(sym::Symbol; concretize=false) = :($(AbstractPPL.VarName){$(QuoteNode(sym))}()) +function varname(expr::Expr; concretize=false) if Meta.isexpr(expr, :ref) || Meta.isexpr(expr, :.) - expr_new = deepcopy(expr) - sym = vsym(expr_new) - - # Need to recursively unwrap until we reach the outer-most variable. - # TODO: implement as recursion? - curexpr = expr_new - while !(curexpr.args[1] isa Symbol) - curexpr = curexpr.args[1] - end - - # Then we replace the variable with `_`, to get an expression we can - # use `lensmacro` on. - curexpr.args[1] = :_ - inds = Setfield.lensmacro(identity, expr_new) + sym = vsym(expr) + # Convert `expr` into something `lensmacro` can parse. + inds = Setfield.lensmacro(identity, replace_basesym(expr, :_)) # TODO: Can we do better, i.e. only check if we have `DynamicLens`? return if concretize @@ -404,6 +393,37 @@ function varname(expr::Expr; concretize=true) end +""" + replace_basesym(expr, sub::Symbol) + +Return `expr` with the base symbol replaced, e.g. +`:(x[1].a[end])` results in `:(\$(sub).[1].a[end])` + +# Example +```jldoctest +julia> AbstractPPL.replace_basesym(:(x[1].a[end][:]), :_) +:(((_[1]).a[end])[:]) + +julia> AbstractPPL.replace_basesym(:(x), :_) +:x + +julia> AbstractPPL.replace_basesym(:(1), :_) +1 +``` + +""" +replace_basesym(x, sub::Symbol) = x +function replace_basesym(expr::Expr, sub::Symbol) + # Recursively replace_basesym the first argument, until the first + # argument is a `Symbol`, in which case we replace the first + # argument with `:_` and return the expression. + return if length(expr.args) > 0 && !(expr.args[1] isa Symbol) + Expr(expr.head, replace_basesym(expr.args[1], sub), expr.args[2:end]...) + else + Expr(expr.head, sub, expr.args[2:end]...) + end +end + """ @vsym(expr) From e47abbb82af20374e370682b19e097af6abfebfa Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Thu, 29 Jul 2021 22:12:02 +0100 Subject: [PATCH 15/42] fixed vinds --- src/varname.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/varname.jl b/src/varname.jl index e3439291..3dadd1e7 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -492,7 +492,7 @@ julia> @vinds x[2:3,2:3][[1,2],[1,2]] Julia 1.5. """ macro vinds(expr::Union{Expr, Symbol}) - return esc(vinds(expr)) + return vinds(expr) end @@ -523,7 +523,7 @@ function vinds(expr::Expr) else Base.replace_ref_begin_end!(ex) end - last = esc(Expr(:tuple, ex.args[2:end]...)) + last = Expr(:tuple, ex.args[2:end]...) init = vinds(ex.args[1]).args return Expr(:tuple, init..., last) else From 5328a6b1646183f7c1c9f39c34257f5667354894 Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Thu, 29 Jul 2021 22:12:31 +0100 Subject: [PATCH 16/42] removed duplicate method --- src/varname.jl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/varname.jl b/src/varname.jl index 3dadd1e7..a82d46c4 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -111,10 +111,6 @@ function _print_application(io::IO, l::ComposedLens) _print_application(io, l.outer) _print_application(io, l.inner) end -function _print_application(io::IO, l::ComposedLens{}) - _print_application(io, l.outer) - _print_application(io, l.inner) -end _print_application(io::IO, l::IndexLens) = print(io, "[", join(map(prettify_index, l.indices), ","), "]") # This is a bit weird but whatever. We're almost always going to # `concretize` anyways. From 924f45dbffbd859029515d142d113491797c5955 Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Thu, 29 Jul 2021 22:16:22 +0100 Subject: [PATCH 17/42] make concretize an argument instead of kwarg --- src/varname.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/varname.jl b/src/varname.jl index a82d46c4..80dc40c6 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -367,11 +367,11 @@ julia> @varname(x[1,2][1+5][45][3]).indexing Julia 1.5. """ macro varname(expr::Union{Expr, Symbol}, concretize=false) - return varname(expr; concretize=concretize) + return varname(expr, concretize=concretize) end -varname(sym::Symbol; concretize=false) = :($(AbstractPPL.VarName){$(QuoteNode(sym))}()) -function varname(expr::Expr; concretize=false) +varname(sym::Symbol, concretize=false) = :($(AbstractPPL.VarName){$(QuoteNode(sym))}()) +function varname(expr::Expr, concretize=false) if Meta.isexpr(expr, :ref) || Meta.isexpr(expr, :.) sym = vsym(expr) # Convert `expr` into something `lensmacro` can parse. From 79118ebaaa2464af9f6f7ff0babb585b149355f0 Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Thu, 29 Jul 2021 22:50:51 +0100 Subject: [PATCH 18/42] make concretize an argument instead of kwarg --- src/varname.jl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/varname.jl b/src/varname.jl index 80dc40c6..87422eef 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -336,10 +336,13 @@ x.a[1,2][:] concretize(vn::VarName, x) = VarName(vn, concretize(vn.indexing, x)) """ - @varname(expr) + @varname(expr[, concretize]) A macro that returns an instance of [`VarName`](@ref) given a symbol or indexing expression `expr`. +If `concretize` is `true`, the resulting expression will be wrapped in a [`concretize`](@ref) call. +This is useful if you for example want to ensure that no `Setfield.DynamicLens` is used. + The `sym` value is taken from the actual variable name, and the index values are put appropriately into the constructor (and resolved at runtime). @@ -367,7 +370,7 @@ julia> @varname(x[1,2][1+5][45][3]).indexing Julia 1.5. """ macro varname(expr::Union{Expr, Symbol}, concretize=false) - return varname(expr, concretize=concretize) + return varname(expr, concretize) end varname(sym::Symbol, concretize=false) = :($(AbstractPPL.VarName){$(QuoteNode(sym))}()) From 71af2e4577a2972b93c45a07875ede9ab319e651 Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Thu, 29 Jul 2021 22:51:07 +0100 Subject: [PATCH 19/42] fixed subsume for lenses --- src/varname.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/varname.jl b/src/varname.jl index 87422eef..a48eeee8 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -234,6 +234,7 @@ _issubrange(i::Colon, j::ConcreteIndex) = true # E.g. `x`, `x[1]`, i.e. `u` is always subsumed by `t` subsumes(t::Tuple{}, u::Lens) = true +subsumes(t::Lens, u::Tuple{}) = false # Idea behind `subsumes` for `Lens` is that we traverse the two lenses in parallel, # checking `subsumes` for every level. This for example means that if we are comparing @@ -258,7 +259,7 @@ subsumes(t::PropertyLens, u::PropertyLens) = false # FIXME: Does not support `DynamicIndexLens`. # FIXME: Does not correctly handle cases such as `subsumes(x, x[:])` # (but neither did old implementation). -subsumes(t::IndexLens, u::IndexLens) = subsumes(t.indices, u.indices) +subsumes(t::IndexLens, u::IndexLens) = _issubindex(t.indices, u.indices) subsumes(t::ComposedLens{<:IndexLens}, u::ComposedLens{<:IndexLens}) = subsumes_index(t, u) subsumes(t::IndexLens, u::ComposedLens{<:IndexLens}) = subsumes_index(t, u) subsumes(t::ComposedLens{<:IndexLens}, u::IndexLens) = subsumes_index(t, u) From e2ffdad42505f5f5cde80e61692c492152dc7ecd Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Thu, 29 Jul 2021 22:51:15 +0100 Subject: [PATCH 20/42] use using instead of import --- src/varname.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/varname.jl b/src/varname.jl index a48eeee8..5bc360c3 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -1,5 +1,5 @@ using Setfield -import Setfield: PropertyLens, ComposedLens, IdentityLens, IndexLens, DynamicIndexLens +using Setfield: PropertyLens, ComposedLens, IdentityLens, IndexLens, DynamicIndexLens """ VarName{sym}(indexing::Tuple=()) From 638bafc0728cd0f7765c72ecbc2ff8e166927af0 Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Thu, 29 Jul 2021 22:51:22 +0100 Subject: [PATCH 21/42] extend composition rules to varname --- src/varname.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/varname.jl b/src/varname.jl index 5bc360c3..93905796 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -92,6 +92,11 @@ getindexing(vn::VarName) = vn.indexing Base.hash(vn::VarName, h::UInt) = hash((getsym(vn), getindexing(vn)), h) Base.:(==)(x::VarName, y::VarName) = getsym(x) == getsym(y) && getindexing(x) == getindexing(y) +# Composition rules similar to the standard one for lenses, but we need a special +# one for the "empty" `VarName{..., Tuple{}}`. +Base.:∘(vn::VarName{sym,Tuple{}}, lens::Lens) where {sym} = VarName{sym}(lens) +Base.:∘(vn::VarName{sym,<:Lens}, lens::Lens) where {sym} = VarName{sym}(vn.indexing ∘ lens) + function Base.show(io::IO, vn::VarName{<:Any, <:Tuple}) print(io, getsym(vn)) for indices in getindexing(vn) From b4db7a4fec36b95938c64de3ef3a141fc1b241b0 Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Sat, 31 Jul 2021 10:28:35 +0100 Subject: [PATCH 22/42] use Setfield.parse_obj_lens and allow begin --- src/varname.jl | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/varname.jl b/src/varname.jl index 93905796..2540de2b 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -382,21 +382,35 @@ end varname(sym::Symbol, concretize=false) = :($(AbstractPPL.VarName){$(QuoteNode(sym))}()) function varname(expr::Expr, concretize=false) if Meta.isexpr(expr, :ref) || Meta.isexpr(expr, :.) - sym = vsym(expr) - # Convert `expr` into something `lensmacro` can parse. - inds = Setfield.lensmacro(identity, replace_basesym(expr, :_)) - - # TODO: Can we do better, i.e. only check if we have `DynamicLens`? - return if concretize - :($(AbstractPPL.concretize)($(AbstractPPL.VarName){$(QuoteNode(sym))}($inds), $(esc(sym)))) + # Split into object/base symbol and lens. + sym_escaped, lens = Setfield.parse_obj_lens(expr) + # Setfield.jl escapes the return symbol, so we need to unescape + # to call `QuoteNode` on it. + sym = drop_escape(sym_escaped) + + return if Setfield.need_dynamic_lens(expr) + :($(AbstractPPL.concretize)($(AbstractPPL.VarName){$(QuoteNode(sym))}($lens), $sym_escaped)) else - :($(AbstractPPL.VarName){$(QuoteNode(sym))}($inds)) + :($(AbstractPPL.VarName){$(QuoteNode(sym))}($lens)) end else error("Malformed variable name $(expr)!") end end +drop_escape(x) = x +function drop_escape(expr::Expr) + Meta.isexpr(expr, :escape) && return drop_escape(expr.args[1]) + return Expr(expr.head, map(x -> drop_escape(x), expr.args)...) +end + +@static if VERSION ≥ v"1.5.0-DEV.666" + function Setfield.need_dynamic_lens(ex) + return Setfield.foldtree(false, ex) do yes, x + yes || x === :end || x === :begin || x === :_ + end + end +end """ replace_basesym(expr, sub::Symbol) From 82993292bb7eb33476f76817218d84330751c73d Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Sat, 31 Jul 2021 10:35:57 +0100 Subject: [PATCH 23/42] added proper fix for begin in indexing --- src/varname.jl | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/varname.jl b/src/varname.jl index 2540de2b..fd0ec282 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -405,9 +405,28 @@ function drop_escape(expr::Expr) end @static if VERSION ≥ v"1.5.0-DEV.666" + function Setfield.lower_index(collection::Symbol, index, dim) + if Setfield.isexpr(index, :call) + return Expr(:call, Setfield.lower_index.(collection, index.args, dim)...) + elseif (index === :end) + if dim === nothing + return :($(Base.lastindex)($collection)) + else + return :($(Base.lastindex)($collection, $dim)) + end + elseif index === :begin + if dim === nothing + return :($(Base.firstindex)($collection)) + else + return :($(Base.firstindex)($collection, $dim)) + end + end + return index + end + function Setfield.need_dynamic_lens(ex) return Setfield.foldtree(false, ex) do yes, x - yes || x === :end || x === :begin || x === :_ + (yes || x === :end || x === :begin || x === :_) end end end From 33d6777b1046bba30661398a6837a37511e2b9af Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Sat, 31 Jul 2021 10:36:27 +0100 Subject: [PATCH 24/42] removed a wild StatsBase that had appeared --- Project.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Project.toml b/Project.toml index 2356e8a5..6be95901 100644 --- a/Project.toml +++ b/Project.toml @@ -8,7 +8,6 @@ version = "0.2.0" [deps] AbstractMCMC = "80f14c24-f653-4e6a-9b94-39d6b0f70001" Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46" -StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" [compat] AbstractMCMC = "2, 3" From 38ac60a85109fdd41c9eaf9abc9ffab09b2d3e37 Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Sat, 31 Jul 2021 11:48:45 +0100 Subject: [PATCH 25/42] removed now redundant replace_basesysm --- src/varname.jl | 32 +------------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/src/varname.jl b/src/varname.jl index fd0ec282..fa4c1905 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -405,6 +405,7 @@ function drop_escape(expr::Expr) end @static if VERSION ≥ v"1.5.0-DEV.666" + # TODO: Replace once https://github.com/jw3126/Setfield.jl/pull/155 has been merged. function Setfield.lower_index(collection::Symbol, index, dim) if Setfield.isexpr(index, :call) return Expr(:call, Setfield.lower_index.(collection, index.args, dim)...) @@ -431,37 +432,6 @@ end end end -""" - replace_basesym(expr, sub::Symbol) - -Return `expr` with the base symbol replaced, e.g. -`:(x[1].a[end])` results in `:(\$(sub).[1].a[end])` - -# Example -```jldoctest -julia> AbstractPPL.replace_basesym(:(x[1].a[end][:]), :_) -:(((_[1]).a[end])[:]) - -julia> AbstractPPL.replace_basesym(:(x), :_) -:x - -julia> AbstractPPL.replace_basesym(:(1), :_) -1 -``` - -""" -replace_basesym(x, sub::Symbol) = x -function replace_basesym(expr::Expr, sub::Symbol) - # Recursively replace_basesym the first argument, until the first - # argument is a `Symbol`, in which case we replace the first - # argument with `:_` and return the expression. - return if length(expr.args) > 0 && !(expr.args[1] isa Symbol) - Expr(expr.head, replace_basesym(expr.args[1], sub), expr.args[2:end]...) - else - Expr(expr.head, sub, expr.args[2:end]...) - end -end - """ @vsym(expr) From e14ce7c349a008d67962f0a7234fe1987c0e9488 Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Sat, 31 Jul 2021 23:12:46 +0100 Subject: [PATCH 26/42] dont export nonexistent methods --- src/AbstractPPL.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/AbstractPPL.jl b/src/AbstractPPL.jl index d85efbb3..1a14c71f 100644 --- a/src/AbstractPPL.jl +++ b/src/AbstractPPL.jl @@ -7,10 +7,8 @@ export VarName, inspace, subsumes, varname, - vinds, vsym, @varname, - @vinds, @vsym From 854bbe16e35607d39f2e313c5aedb35887f88107 Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Sat, 31 Jul 2021 23:22:13 +0100 Subject: [PATCH 27/42] dont allow Tuple in VarName anymore but only allow Lens --- src/varname.jl | 200 +++++++++++++++++++++++-------------------------- 1 file changed, 94 insertions(+), 106 deletions(-) diff --git a/src/varname.jl b/src/varname.jl index fa4c1905..1121daaf 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -29,10 +29,10 @@ julia> @varname x[:, 1][1+1] x[:,1][2] ``` """ -struct VarName{sym, T} +struct VarName{sym, T<:Lens} indexing::T - VarName{sym}(indexing=()) where {sym} = new{sym,typeof(indexing)}(indexing) + VarName{sym}(indexing=IdentityLens()) where {sym} = new{sym,typeof(indexing)}(indexing) end """ @@ -48,7 +48,7 @@ julia> VarName(@varname(x[1][2:3])) x ``` """ -function VarName(vn::VarName, indexing = ()) +function VarName(vn::VarName, indexing=IdentityLens()) return VarName{getsym(vn)}(indexing) end @@ -94,7 +94,7 @@ Base.:(==)(x::VarName, y::VarName) = getsym(x) == getsym(y) && getindexing(x) == # Composition rules similar to the standard one for lenses, but we need a special # one for the "empty" `VarName{..., Tuple{}}`. -Base.:∘(vn::VarName{sym,Tuple{}}, lens::Lens) where {sym} = VarName{sym}(lens) +Base.:∘(vn::VarName{sym,<:IdentityLens}, lens::Lens) where {sym} = VarName{sym}(lens) Base.:∘(vn::VarName{sym,<:Lens}, lens::Lens) where {sym} = VarName{sym}(vn.indexing ∘ lens) function Base.show(io::IO, vn::VarName{<:Any, <:Tuple}) @@ -217,34 +217,14 @@ function subsumes(u::VarName, v::VarName) return getsym(u) == getsym(v) && subsumes(u.indexing, v.indexing) end -subsumes(::Tuple{}, ::Tuple{}) = true # x subsumes x -subsumes(::Tuple{}, ::Tuple) = true # x subsumes x[1] -subsumes(::Tuple, ::Tuple{}) = false # x[1] does not subsume x -function subsumes(t::Tuple, u::Tuple) # does x[i]... subsume x[j]...? - return _issubindex(first(t), first(u)) && subsumes(Base.tail(t), Base.tail(u)) -end - -const AnyIndex = Union{Int, AbstractVector{Int}, Colon} -_issubindex_(::Tuple{Vararg{AnyIndex}}, ::Tuple{Vararg{AnyIndex}}) = false -function _issubindex(t::NTuple{N, AnyIndex}, u::NTuple{N, AnyIndex}) where {N} - return all(_issubrange(j, i) for (i, j) in zip(t, u)) -end - -const ConcreteIndex = Union{Int, AbstractVector{Int}} # this include all kinds of ranges - -"""Determine whether indices `i` are contained in `j`, treating `:` as universal set.""" -_issubrange(i::ConcreteIndex, j::ConcreteIndex) = issubset(i, j) -_issubrange(i::Union{ConcreteIndex, Colon}, j::Colon) = true -_issubrange(i::Colon, j::ConcreteIndex) = true - -# E.g. `x`, `x[1]`, i.e. `u` is always subsumed by `t` -subsumes(t::Tuple{}, u::Lens) = true -subsumes(t::Lens, u::Tuple{}) = false - # Idea behind `subsumes` for `Lens` is that we traverse the two lenses in parallel, # checking `subsumes` for every level. This for example means that if we are comparing # `PropertyLens{:a}` and `PropertyLens{:b}` we immediately know that they do not subsume # each other since at the same level/depth they access different properties. +# E.g. `x`, `x[1]`, i.e. `u` is always subsumed by `t` +subsumes(t::IdentityLens, u::Lens) = true +subsumes(t::Lens, u::IdentityLens) = false + subsumes(t::ComposedLens, u::ComposedLens) = subsumes(t.outer, u.outer) && subsumes(t.inner, u.inner) # If `t` is still a composed lens, then there is no way it can subsume `u` since `u` is a @@ -273,12 +253,50 @@ subsumes(t::ComposedLens{<:IndexLens}, u::IndexLens) = subsumes_index(t, u) # the indexing behavior must be considered jointly. # Therefore we must recurse until we reach something that is NOT # indexing, and then consider the sequence of indices leading up to this. -function subsumes_index(t, u) +""" + subsumes_index(t::Lens, u::Lens) + +Return `true` if the indexing represented by `t` subsumes `u`. + +This is mostly useful for comparing compositions involving `IndexLens` +e.g. `_[1][2].a[2]` and `_[1][2].a`. In such a scenario we do the following: +1. Combine `[1][2]` into a `Tuple` of indices using [`combine_indices`](@ref). +2. Do the same for `[1][2]`. +3. Compare the two tuples from (1) and (2) using `subsumes_index`. +4. Since we're still undecided, we call `subsume(@lens(_.a[2]), @lens(_.a))` + which then returns `false`. + +# Example +```jldoctest; setup=:(using Setfield) +julia> t = @lens(_[1].a); u = @lens(_[1]); + +julia> subsumes_index(t, u) +false + +julia> subsumes_index(u, t) +true + +julia> # `IdentityLens` subsumes all. + subsumes_index(@lens(_), t) +true + +julia> # None subsumes `IdentityLens`. + subsumes_index(t, @lens(_)) +false + +julia> AbstractPPL.subsumes(@lens(_[1][2].a[2]), @lens(_[1][2].a)) +false + +julia> AbstractPPL.subsumes(@lens(_[1][2].a), @lens(_[1][2].a[2])) +true +``` +""" +function subsumes_index(t::Lens, u::Lens) t_indices, t_next = combine_indices(t) u_indices, u_next = combine_indices(u) - # Check if the indices indicate that `t` subsumes `u`. - if !subsumes(t_indices, u_indices) + # If we already know that `u` is not subsumed by `t`, return early. + if !subsumes_index(t_indices, u_indices) return false end @@ -286,19 +304,24 @@ function subsumes_index(t, u) # Means that there's nothing left for `t` and either nothing # or something left for `u`, i.e. `t` indeed `subsumes` `u`. return true - else - # `t` only `subsumes` `u` if `u_next` is also nothing. - if u_next === nothing - return true - else - return false - end + elseif u_next === nothing + # If `t_next` is not `nothing` but `u_ntext` is, then + # `t` does not subsume `u`. + return false end - # If neither is `nothing` we continue iterating. + # If neither is `nothing` we continue. return subsumes(t_next, u_next) end +""" + combine_indices(lens) + +Return sequential indexing into a single `Tuple` of indices, +e.g. `x[:][1][2]` becomes `((Colon(), ), (1, ), (2, ))`. + +The result is compatible with [`subsumes_index`](@ref) for `Tuple` input. +""" combine_indices(lens::Lens) = (), lens combine_indices(lens::IndexLens) = (lens.indices, ), nothing function combine_indices(lens::ComposedLens{<:IndexLens}) @@ -306,6 +329,38 @@ function combine_indices(lens::ComposedLens{<:IndexLens}) return (lens.outer.indices, indices...), next end +""" + subsumes_index(left_index::Tuple, right_index::Tuple) + +Return `true` if `right_index` is subsumed by `left_index`. + +Currently _not_ supported are: +- Boolean indexing, literal `CartesianIndex` (these could be added, though) +- Linear indexing of multidimensional arrays: `x[4]` does not subsume `x[2, 2]` for a matrix `x` +- Trailing ones: `x[2, 1]` does not subsume `x[2]` for a vector `x` +- Dynamic indexing, e.g. `x[1]` does not subsume `x[begin]`. +""" +subsumes_index(::Tuple{}, ::Tuple{}) = true # x subsumes x +subsumes_index(::Tuple{}, ::Tuple) = true # x subsumes x[1] +subsumes_index(::Tuple, ::Tuple{}) = false # x[1] does not subsume x +function subsumes_index(t::Tuple, u::Tuple) # does x[i]... subsume x[j]...? + return _issubindex(first(t), first(u)) && subsumes_index(Base.tail(t), Base.tail(u)) +end + +const AnyIndex = Union{Int, AbstractVector{Int}, Colon} +_issubindex_(::Tuple{Vararg{AnyIndex}}, ::Tuple{Vararg{AnyIndex}}) = false +function _issubindex(t::NTuple{N, AnyIndex}, u::NTuple{N, AnyIndex}) where {N} + return all(_issubrange(j, i) for (i, j) in zip(t, u)) +end + +const ConcreteIndex = Union{Int, AbstractVector{Int}} # this include all kinds of ranges + +"""Determine whether indices `i` are contained in `j`, treating `:` as universal set.""" +_issubrange(i::ConcreteIndex, j::ConcreteIndex) = issubset(i, j) +_issubrange(i::Colon, j::Colon) = true +_issubrange(i::ConcreteIndex, j::Colon) = false +_issubrange(i::Colon, j::ConcreteIndex) = true + """ concretize(l::Lens, x) @@ -471,70 +526,3 @@ function vsym(expr::Expr) end end -""" - @vinds(expr) - -Returns a tuple of tuples of the indices in `expr`. - -## Examples - -```jldoctest -julia> @vinds x -() - -julia> @vinds x[1,1][2,3] -((1, 1), (2, 3)) - -julia> @vinds x[:,1][2,:] -((Colon(), 1), (2, Colon())) - -julia> @vinds x[2:3,1][2,1:2] -((2:3, 1), (2, 1:2)) - -julia> @vinds x[2:3,2:3][[1,2],[1,2]] -((2:3, 2:3), ([1, 2], [1, 2])) -``` - -!!! compat "Julia 1.5" - Using `begin` in an indexing expression to refer to the first index requires at least - Julia 1.5. -""" -macro vinds(expr::Union{Expr, Symbol}) - return vinds(expr) -end - - -""" - vinds(expr) - -Return the indexing part of the [`@varname`](@ref)-compatible expression `expr` as an expression -suitable for input of the [`VarName`](@ref) constructor. - -## Examples - -```jldoctest -julia> vinds(:(x[end])) -:((((lastindex)(x),),)) - -julia> vinds(:(x[1, end])) -:(((1, (lastindex)(x, 2)),)) -``` -""" -function vinds end - -vinds(expr::Symbol) = Expr(:tuple) -function vinds(expr::Expr) - if Meta.isexpr(expr, :ref) - ex = copy(expr) - @static if VERSION < v"1.5.0-DEV.666" - Base.replace_ref_end!(ex) - else - Base.replace_ref_begin_end!(ex) - end - last = Expr(:tuple, ex.args[2:end]...) - init = vinds(ex.args[1]).args - return Expr(:tuple, init..., last) - else - error("Mis-formed variable name $(expr)!") - end -end From f010a1bd5584f0fc6809395c49cfc596486b0116 Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Sat, 31 Jul 2021 23:50:57 +0100 Subject: [PATCH 28/42] sort of fixed _issubrange --- src/varname.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/varname.jl b/src/varname.jl index 1121daaf..a64f7b77 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -358,8 +358,11 @@ const ConcreteIndex = Union{Int, AbstractVector{Int}} # this include all kinds o """Determine whether indices `i` are contained in `j`, treating `:` as universal set.""" _issubrange(i::ConcreteIndex, j::ConcreteIndex) = issubset(i, j) _issubrange(i::Colon, j::Colon) = true +_issubrange(i::Colon, j::ConcreteIndex) = false +# FIXME: [2021-07-31] This is wrong but we have tests in DPPL that tell +# us that it SHOULD be correct. I'll leave it as is for now to ensure that +# we preserve the status quo, but I'm confused. _issubrange(i::ConcreteIndex, j::Colon) = false -_issubrange(i::Colon, j::ConcreteIndex) = true """ concretize(l::Lens, x) From 253dffe85c52a5dbcfcaef31766ff762e9f87f6d Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Sat, 31 Jul 2021 23:51:12 +0100 Subject: [PATCH 29/42] fixed a doctests --- src/varname.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/varname.jl b/src/varname.jl index a64f7b77..cf499ad8 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -18,8 +18,8 @@ indexing expression through the [`@varname`](@ref) convenience macro. # Examples -```jldoctest -julia> vn = VarName{:x}(((Colon(), 1), (2,))) +```jldoctest; setup=:(using Setfield) +julia> vn = VarName{:x}(Setfield.IndexLens((Colon(), 1)) ∘ Setfield.IndexLens((2, ))) x[:,1][2] julia> vn.indexing From d31461e728ce7269389cf311633d8ed49dbab707 Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Sun, 1 Aug 2021 01:28:00 +0100 Subject: [PATCH 30/42] added get and set for VarName --- src/varname.jl | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/varname.jl b/src/varname.jl index cf499ad8..8c717269 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -88,6 +88,24 @@ julia> getindexing(@varname(y)) """ getindexing(vn::VarName) = vn.indexing +""" + get(obj, vn::VarName{sym}) + +Alias for `get(obj, PropertyLens{sym}() ∘ vn.indexing)`. +""" +function Setfield.get(obj, vn::VarName{sym}) where {sym} + return Setfield.get(obj, PropertyLens{sym}() ∘ vn.indexing) +end + +""" + set(obj, vn::VarName{sym}, value) + +Alias for `set(obj, PropertyLens{sym}() ∘ vn.indexing, value)`. +""" +function Setfield.set(obj, vn::VarName{sym}, value) where {sym} + return Setfield.set(obj, PropertyLens{sym}() ∘ vn.indexing, value) +end + Base.hash(vn::VarName, h::UInt) = hash((getsym(vn), getindexing(vn)), h) Base.:(==)(x::VarName, y::VarName) = getsym(x) == getsym(y) && getindexing(x) == getindexing(y) From b596c11e10c89f6725f84bb7fcf8dfbfba1b159f Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Sun, 1 Aug 2021 01:28:20 +0100 Subject: [PATCH 31/42] added backwards compat constructor for VarName --- src/varname.jl | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/varname.jl b/src/varname.jl index 8c717269..237f1701 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -35,12 +35,20 @@ struct VarName{sym, T<:Lens} VarName{sym}(indexing=IdentityLens()) where {sym} = new{sym,typeof(indexing)}(indexing) end +# A bit of backwards compatibility. +# TODO: Should we deprecate this? +VarName{sym}(indexing::Tuple) where {sym} = VarName{sym}(tuple2indexlens(indexing)) + """ - VarName(vn::VarName, indexing=()) + VarName(vn::VarName, indexing::Lens) + VarName(vn::VarName, indexing::Tuple) Return a copy of `vn` with a new index `indexing`. -```jldoctest +```jldoctest; setup=:(using Setfield) +julia> VarName(@varname(x[1][2:3]), Setfield.IndexLens((2,))) +x[2] + julia> VarName(@varname(x[1][2:3]), ((2,),)) x[2] @@ -48,10 +56,15 @@ julia> VarName(@varname(x[1][2:3])) x ``` """ -function VarName(vn::VarName, indexing=IdentityLens()) - return VarName{getsym(vn)}(indexing) +VarName(vn::VarName, indexing::Lens=IdentityLens()) = VarName{getsym(vn)}(indexing) + +function VarName(vn::VarName, indexing::Tuple) + return VarName{getsym(vn)}(tuple2indexlens(indexing)) end +tuple2indexlens(indexing::Tuple{}) = IdentityLens() +tuple2indexlens(indexing::Tuple{<:Tuple}) = IndexLens(first(indexing)) +tuple2indexlens(indexing::Tuple) = IndexLens(first(indexing)) ∘ tuple2indexlens(indexing[2:end]) """ getsym(vn::VarName) From c72faabc153f7fd79773f8487db5fe5c8ff60fd1 Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Sun, 1 Aug 2021 01:28:32 +0100 Subject: [PATCH 32/42] added some weird behavior from existing codebase... --- src/varname.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/varname.jl b/src/varname.jl index 237f1701..7bc19ca4 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -172,7 +172,7 @@ Base.Symbol(vn::VarName) = Symbol(string(vn)) # simplified symbol inspace(vn::Union{VarName, Symbol}, space::Tuple) Check whether `vn`'s variable symbol is in `space`. The empty tuple counts as the "universal space" -containing all variables. Subsumption (see [`subsume`](@ref)) is respected. +containing all variables. Subsumption (see [`subsume`](@ref)) is respected. ## Examples @@ -389,11 +389,11 @@ const ConcreteIndex = Union{Int, AbstractVector{Int}} # this include all kinds o """Determine whether indices `i` are contained in `j`, treating `:` as universal set.""" _issubrange(i::ConcreteIndex, j::ConcreteIndex) = issubset(i, j) _issubrange(i::Colon, j::Colon) = true -_issubrange(i::Colon, j::ConcreteIndex) = false +_issubrange(i::ConcreteIndex, j::Colon) = true # FIXME: [2021-07-31] This is wrong but we have tests in DPPL that tell # us that it SHOULD be correct. I'll leave it as is for now to ensure that # we preserve the status quo, but I'm confused. -_issubrange(i::ConcreteIndex, j::Colon) = false +_issubrange(i::Colon, j::ConcreteIndex) = true """ concretize(l::Lens, x) From fc53f291a893c3d80f2142d4915c81ce8eca4884 Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Sun, 1 Aug 2021 06:05:08 +0100 Subject: [PATCH 33/42] no longer need special handling of begin after Setfield 0.7.1 --- Project.toml | 1 + src/varname.jl | 28 ---------------------------- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/Project.toml b/Project.toml index 6be95901..9139b1c9 100644 --- a/Project.toml +++ b/Project.toml @@ -11,4 +11,5 @@ Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46" [compat] AbstractMCMC = "2, 3" +Setfield = "0.7.1" julia = "1" diff --git a/src/varname.jl b/src/varname.jl index 7bc19ca4..85f6ed76 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -493,34 +493,6 @@ function drop_escape(expr::Expr) return Expr(expr.head, map(x -> drop_escape(x), expr.args)...) end -@static if VERSION ≥ v"1.5.0-DEV.666" - # TODO: Replace once https://github.com/jw3126/Setfield.jl/pull/155 has been merged. - function Setfield.lower_index(collection::Symbol, index, dim) - if Setfield.isexpr(index, :call) - return Expr(:call, Setfield.lower_index.(collection, index.args, dim)...) - elseif (index === :end) - if dim === nothing - return :($(Base.lastindex)($collection)) - else - return :($(Base.lastindex)($collection, $dim)) - end - elseif index === :begin - if dim === nothing - return :($(Base.firstindex)($collection)) - else - return :($(Base.firstindex)($collection, $dim)) - end - end - return index - end - - function Setfield.need_dynamic_lens(ex) - return Setfield.foldtree(false, ex) do yes, x - (yes || x === :end || x === :begin || x === :_) - end - end -end - """ @vsym(expr) From 111f80584bd17e279f28134f8f9991e1ca04bba2 Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Sun, 1 Aug 2021 06:07:13 +0100 Subject: [PATCH 34/42] remove show for old tuple-based VarName --- src/varname.jl | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/varname.jl b/src/varname.jl index 85f6ed76..f5b5e963 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -128,15 +128,6 @@ Base.:(==)(x::VarName, y::VarName) = getsym(x) == getsym(y) && getindexing(x) == Base.:∘(vn::VarName{sym,<:IdentityLens}, lens::Lens) where {sym} = VarName{sym}(lens) Base.:∘(vn::VarName{sym,<:Lens}, lens::Lens) where {sym} = VarName{sym}(vn.indexing ∘ lens) -function Base.show(io::IO, vn::VarName{<:Any, <:Tuple}) - print(io, getsym(vn)) - for indices in getindexing(vn) - print(io, "[") - join(io, map(prettify_index, indices), ",") - print(io, "]") - end -end - function Base.show(io::IO, vn::VarName{<:Any, <:Lens}) print(io, getsym(vn)) _print_application(io, vn.indexing) From 84badceb55140c3e320a6917cbfd1797e13bcc17 Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Sun, 1 Aug 2021 06:17:51 +0100 Subject: [PATCH 35/42] forgot to check if concretize was true in varname --- src/varname.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/varname.jl b/src/varname.jl index f5b5e963..b6b43050 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -455,12 +455,12 @@ julia> @varname(x[1,2][1+5][45][3]).indexing Using `begin` in an indexing expression to refer to the first index requires at least Julia 1.5. """ -macro varname(expr::Union{Expr, Symbol}, concretize=false) +macro varname(expr::Union{Expr, Symbol}, concretize::Bool=false) return varname(expr, concretize) end -varname(sym::Symbol, concretize=false) = :($(AbstractPPL.VarName){$(QuoteNode(sym))}()) -function varname(expr::Expr, concretize=false) +varname(sym::Symbol, concretize::Bool=false) = :($(AbstractPPL.VarName){$(QuoteNode(sym))}()) +function varname(expr::Expr, concretize::Bool=false) if Meta.isexpr(expr, :ref) || Meta.isexpr(expr, :.) # Split into object/base symbol and lens. sym_escaped, lens = Setfield.parse_obj_lens(expr) @@ -468,7 +468,7 @@ function varname(expr::Expr, concretize=false) # to call `QuoteNode` on it. sym = drop_escape(sym_escaped) - return if Setfield.need_dynamic_lens(expr) + return if concretize && Setfield.need_dynamic_lens(expr) :($(AbstractPPL.concretize)($(AbstractPPL.VarName){$(QuoteNode(sym))}($lens), $sym_escaped)) else :($(AbstractPPL.VarName){$(QuoteNode(sym))}($lens)) From 8c1d193e269dc5082c18c64a51dda4b3497c061a Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Sun, 1 Aug 2021 15:42:50 +0100 Subject: [PATCH 36/42] renamed tuple2indexlens --- src/varname.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/varname.jl b/src/varname.jl index b6b43050..b1e9fba8 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -37,7 +37,7 @@ end # A bit of backwards compatibility. # TODO: Should we deprecate this? -VarName{sym}(indexing::Tuple) where {sym} = VarName{sym}(tuple2indexlens(indexing)) +VarName{sym}(indexing::Tuple) where {sym} = VarName{sym}(tupleindex2lens(indexing)) """ VarName(vn::VarName, indexing::Lens) @@ -59,12 +59,12 @@ x VarName(vn::VarName, indexing::Lens=IdentityLens()) = VarName{getsym(vn)}(indexing) function VarName(vn::VarName, indexing::Tuple) - return VarName{getsym(vn)}(tuple2indexlens(indexing)) + return VarName{getsym(vn)}(tupleindex2lens(indexing)) end -tuple2indexlens(indexing::Tuple{}) = IdentityLens() -tuple2indexlens(indexing::Tuple{<:Tuple}) = IndexLens(first(indexing)) -tuple2indexlens(indexing::Tuple) = IndexLens(first(indexing)) ∘ tuple2indexlens(indexing[2:end]) +tupleindex2lens(indexing::Tuple{}) = IdentityLens() +tupleindex2lens(indexing::Tuple{<:Tuple}) = IndexLens(first(indexing)) +tupleindex2lens(indexing::Tuple) = IndexLens(first(indexing)) ∘ tupleindex2lens(indexing[2:end]) """ getsym(vn::VarName) From 3a854e2710a9b976f9fe023ec49618cf1978d7f9 Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Sun, 1 Aug 2021 15:45:15 +0100 Subject: [PATCH 37/42] formatting --- src/AbstractPPL.jl | 15 ++----------- src/varname.jl | 53 ++++++++++++++++++++++++++-------------------- 2 files changed, 32 insertions(+), 36 deletions(-) diff --git a/src/AbstractPPL.jl b/src/AbstractPPL.jl index 1a14c71f..e2015610 100644 --- a/src/AbstractPPL.jl +++ b/src/AbstractPPL.jl @@ -1,22 +1,11 @@ module AbstractPPL # VarName -export VarName, - getsym, - getindexing, - inspace, - subsumes, - varname, - vsym, - @varname, - @vsym +export VarName, getsym, getindexing, inspace, subsumes, varname, vsym, @varname, @vsym # Abstract model functions -export AbstractProbabilisticProgram, - condition, - decondition, - logdensity +export AbstractProbabilisticProgram, condition, decondition, logdensity # Abstract traces diff --git a/src/varname.jl b/src/varname.jl index b1e9fba8..7cb41c8e 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -29,10 +29,11 @@ julia> @varname x[:, 1][1+1] x[:,1][2] ``` """ -struct VarName{sym, T<:Lens} +struct VarName{sym,T<:Lens} indexing::T - VarName{sym}(indexing=IdentityLens()) where {sym} = new{sym,typeof(indexing)}(indexing) + VarName{sym}(indexing = IdentityLens()) where {sym} = + new{sym,typeof(indexing)}(indexing) end # A bit of backwards compatibility. @@ -56,7 +57,7 @@ julia> VarName(@varname(x[1][2:3])) x ``` """ -VarName(vn::VarName, indexing::Lens=IdentityLens()) = VarName{getsym(vn)}(indexing) +VarName(vn::VarName, indexing::Lens = IdentityLens()) = VarName{getsym(vn)}(indexing) function VarName(vn::VarName, indexing::Tuple) return VarName{getsym(vn)}(tupleindex2lens(indexing)) @@ -64,7 +65,8 @@ end tupleindex2lens(indexing::Tuple{}) = IdentityLens() tupleindex2lens(indexing::Tuple{<:Tuple}) = IndexLens(first(indexing)) -tupleindex2lens(indexing::Tuple) = IndexLens(first(indexing)) ∘ tupleindex2lens(indexing[2:end]) +tupleindex2lens(indexing::Tuple) = + IndexLens(first(indexing)) ∘ tupleindex2lens(indexing[2:end]) """ getsym(vn::VarName) @@ -81,7 +83,7 @@ julia> getsym(@varname(y)) :y ``` """ -getsym(vn::VarName{sym}) where sym = sym +getsym(vn::VarName{sym}) where {sym} = sym """ @@ -121,14 +123,14 @@ end Base.hash(vn::VarName, h::UInt) = hash((getsym(vn), getindexing(vn)), h) -Base.:(==)(x::VarName, y::VarName) = getsym(x) == getsym(y) && getindexing(x) == getindexing(y) +Base.:(==)(x::VarName, y::VarName) = + getsym(x) == getsym(y) && getindexing(x) == getindexing(y) -# Composition rules similar to the standard one for lenses, but we need a special -# one for the "empty" `VarName{..., Tuple{}}`. -Base.:∘(vn::VarName{sym,<:IdentityLens}, lens::Lens) where {sym} = VarName{sym}(lens) -Base.:∘(vn::VarName{sym,<:Lens}, lens::Lens) where {sym} = VarName{sym}(vn.indexing ∘ lens) +# Allow compositions with lenses. +Setfield.compose(vn::VarName{sym,<:Lens}, lens::Lens) where {sym} = + VarName{sym}(vn.indexing ∘ lens) -function Base.show(io::IO, vn::VarName{<:Any, <:Lens}) +function Base.show(io::IO, vn::VarName{<:Any,<:Lens}) print(io, getsym(vn)) _print_application(io, vn.indexing) end @@ -138,7 +140,8 @@ function _print_application(io::IO, l::ComposedLens) _print_application(io, l.outer) _print_application(io, l.inner) end -_print_application(io::IO, l::IndexLens) = print(io, "[", join(map(prettify_index, l.indices), ","), "]") +_print_application(io::IO, l::IndexLens) = + print(io, "[", join(map(prettify_index, l.indices), ","), "]") # This is a bit weird but whatever. We're almost always going to # `concretize` anyways. _print_application(io::IO, l::DynamicIndexLens) = print(io, l, "(_)") @@ -247,7 +250,8 @@ end subsumes(t::IdentityLens, u::Lens) = true subsumes(t::Lens, u::IdentityLens) = false -subsumes(t::ComposedLens, u::ComposedLens) = subsumes(t.outer, u.outer) && subsumes(t.inner, u.inner) +subsumes(t::ComposedLens, u::ComposedLens) = + subsumes(t.outer, u.outer) && subsumes(t.inner, u.inner) # If `t` is still a composed lens, then there is no way it can subsume `u` since `u` is a # leaf of the "lens-tree". @@ -345,7 +349,7 @@ e.g. `x[:][1][2]` becomes `((Colon(), ), (1, ), (2, ))`. The result is compatible with [`subsumes_index`](@ref) for `Tuple` input. """ combine_indices(lens::Lens) = (), lens -combine_indices(lens::IndexLens) = (lens.indices, ), nothing +combine_indices(lens::IndexLens) = (lens.indices,), nothing function combine_indices(lens::ComposedLens{<:IndexLens}) indices, next = combine_indices(lens.inner) return (lens.outer.indices, indices...), next @@ -369,13 +373,13 @@ function subsumes_index(t::Tuple, u::Tuple) # does x[i]... subsume x[j]...? return _issubindex(first(t), first(u)) && subsumes_index(Base.tail(t), Base.tail(u)) end -const AnyIndex = Union{Int, AbstractVector{Int}, Colon} +const AnyIndex = Union{Int,AbstractVector{Int},Colon} _issubindex_(::Tuple{Vararg{AnyIndex}}, ::Tuple{Vararg{AnyIndex}}) = false -function _issubindex(t::NTuple{N, AnyIndex}, u::NTuple{N, AnyIndex}) where {N} +function _issubindex(t::NTuple{N,AnyIndex}, u::NTuple{N,AnyIndex}) where {N} return all(_issubrange(j, i) for (i, j) in zip(t, u)) end -const ConcreteIndex = Union{Int, AbstractVector{Int}} # this include all kinds of ranges +const ConcreteIndex = Union{Int,AbstractVector{Int}} # this include all kinds of ranges """Determine whether indices `i` are contained in `j`, treating `:` as universal set.""" _issubrange(i::ConcreteIndex, j::ConcreteIndex) = issubset(i, j) @@ -455,12 +459,13 @@ julia> @varname(x[1,2][1+5][45][3]).indexing Using `begin` in an indexing expression to refer to the first index requires at least Julia 1.5. """ -macro varname(expr::Union{Expr, Symbol}, concretize::Bool=false) +macro varname(expr::Union{Expr,Symbol}, concretize::Bool = false) return varname(expr, concretize) end -varname(sym::Symbol, concretize::Bool=false) = :($(AbstractPPL.VarName){$(QuoteNode(sym))}()) -function varname(expr::Expr, concretize::Bool=false) +varname(sym::Symbol, concretize::Bool = false) = + :($(AbstractPPL.VarName){$(QuoteNode(sym))}()) +function varname(expr::Expr, concretize::Bool = false) if Meta.isexpr(expr, :ref) || Meta.isexpr(expr, :.) # Split into object/base symbol and lens. sym_escaped, lens = Setfield.parse_obj_lens(expr) @@ -469,7 +474,10 @@ function varname(expr::Expr, concretize::Bool=false) sym = drop_escape(sym_escaped) return if concretize && Setfield.need_dynamic_lens(expr) - :($(AbstractPPL.concretize)($(AbstractPPL.VarName){$(QuoteNode(sym))}($lens), $sym_escaped)) + :($(AbstractPPL.concretize)( + $(AbstractPPL.VarName){$(QuoteNode(sym))}($lens), + $sym_escaped, + )) else :($(AbstractPPL.VarName){$(QuoteNode(sym))}($lens)) end @@ -503,7 +511,7 @@ julia> @vsym x[end] :x ``` """ -macro vsym(expr::Union{Expr, Symbol}) +macro vsym(expr::Union{Expr,Symbol}) return QuoteNode(vsym(expr)) end @@ -522,4 +530,3 @@ function vsym(expr::Expr) error("Malformed variable name $(expr)!") end end - From 20191807699811c0af1b1f4d15afc94b04eaa3d6 Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Sun, 1 Aug 2021 17:11:45 +0100 Subject: [PATCH 38/42] disallow usage of dynamic lenses in VarName --- src/varname.jl | 100 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 73 insertions(+), 27 deletions(-) diff --git a/src/varname.jl b/src/varname.jl index 7cb41c8e..bf2ab703 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -32,8 +32,28 @@ x[:,1][2] struct VarName{sym,T<:Lens} indexing::T - VarName{sym}(indexing = IdentityLens()) where {sym} = - new{sym,typeof(indexing)}(indexing) + function VarName{sym}(indexing=IdentityLens()) where {sym} + # TODO: Should we completely disallow or just `@warn`? + # TODO: Does this affect performance? + if !is_static_lens(indexing) + error("attempted to construct `VarName` with dynamic lens of type $(nameof(typeof(indexing)))") + end + return new{sym,typeof(indexing)}(indexing) + end +end + +""" + is_static_lens(l::Lens) + +Return `true` if `l` does not require runtime information to be resolved. + +In particular it returns `false` for `Setfield.DynamicLens` and `Setfield.FunctionLens`. +""" +is_static_lens(l::Lens) = is_static_lens(typeof(l)) +is_static_lens(::Type{<:Lens}) = false +is_static_lens(::Type{<:Union{PropertyLens, IndexLens, IdentityLens}}) = true +function is_static_lens(::Type{ComposedLens{LO, LI}}) where {LO, LI} + return is_static_lens(LO) && is_static_lens(LI) end # A bit of backwards compatibility. @@ -408,39 +428,52 @@ end Return `vn` instantiated on `x`, i.e. any runtime information evaluated using `x`. # Examples -```jldoctest -julia> x = (a = [1.0 2.0;], ); - -julia> vn = @varname(x.a[1, :]) -x.a[1,:] - -julia> AbstractPPL.concretize(vn, x) -x.a[1,:] +```jldoctest; setup=:(using Setfield) -julia> vn = @varname(x.a[1, end][:]); +julia> x = (a = [1.0 2.0;], ); -julia> AbstractPPL.concretize(vn, x) +julia> AbstractPPL.concretize(@lens(_.a[1, end][:]), x) x.a[1,2][:] ``` """ concretize(vn::VarName, x) = VarName(vn, concretize(vn.indexing, x)) """ - @varname(expr[, concretize]) + @varname(expr) A macro that returns an instance of [`VarName`](@ref) given a symbol or indexing expression `expr`. If `concretize` is `true`, the resulting expression will be wrapped in a [`concretize`](@ref) call. -This is useful if you for example want to ensure that no `Setfield.DynamicLens` is used. -The `sym` value is taken from the actual variable name, and the index values are put appropriately -into the constructor (and resolved at runtime). +Note that expressions involving dynamic indexing, i.e. `begin` and/or `end`, will need to be +resolved as `VarName` only supports non-dynamic indexing as determined by +[`is_static_index`](@ref). See examples below. ## Examples +### Dynamic indexing +```jldoctest +julia> # Dynamic indexing is not allowed in `VarName` + @varname(x[end]) +ERROR: UndefVarError: x not defined +[...] + +julia> # To be able to resolve `end` we need `x` to be available. + x = randn(2); @varname(x[end]) +x[2] + +julia> # Note that "dynamic" here refers to usage of `begin` and/or `end`, + # _not_ "information only available at runtime", i.e. the following works. + [@varname(x[i]) for i = 1:length(x)][end] +x[2] +``` + +### General indexing + +Under the hood Setfield.jl's `Lens` are used for the indexing: ```jldoctest julia> @varname(x).indexing -() +(@lens _) julia> @varname(x[1]).indexing (@lens _[1]) @@ -455,17 +488,29 @@ julia> @varname(x[1,2][1+5][45][3]).indexing (@lens _[1, 2][6][45][3]) ``` +This also means that we support property access: + +```jldoctest +julia> @varname(x.a).indexing +(@lens _.a) + +julia> @varname(x.a[1]).indexing +(@lens _.a[1]) + +julia> x = (a = [(b = rand(2), )], ); @varname(x.a[1].b[end]).indexing +(@lens _.a[1].b[2]) +``` + !!! compat "Julia 1.5" Using `begin` in an indexing expression to refer to the first index requires at least Julia 1.5. """ -macro varname(expr::Union{Expr,Symbol}, concretize::Bool = false) - return varname(expr, concretize) +macro varname(expr::Union{Expr,Symbol}) + return varname(expr) end -varname(sym::Symbol, concretize::Bool = false) = - :($(AbstractPPL.VarName){$(QuoteNode(sym))}()) -function varname(expr::Expr, concretize::Bool = false) +varname(sym::Symbol) = :($(AbstractPPL.VarName){$(QuoteNode(sym))}()) +function varname(expr::Expr) if Meta.isexpr(expr, :ref) || Meta.isexpr(expr, :.) # Split into object/base symbol and lens. sym_escaped, lens = Setfield.parse_obj_lens(expr) @@ -473,11 +518,12 @@ function varname(expr::Expr, concretize::Bool = false) # to call `QuoteNode` on it. sym = drop_escape(sym_escaped) - return if concretize && Setfield.need_dynamic_lens(expr) - :($(AbstractPPL.concretize)( - $(AbstractPPL.VarName){$(QuoteNode(sym))}($lens), - $sym_escaped, - )) + return if Setfield.need_dynamic_lens(expr) + :( + $(AbstractPPL.VarName){$(QuoteNode(sym))}( + $(AbstractPPL.concretize)($lens, $sym_escaped) + ) + ) else :($(AbstractPPL.VarName){$(QuoteNode(sym))}($lens)) end From a397bfeb8d25a6f3b1c25eb91f646cbb255439ad Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Sun, 1 Aug 2021 17:13:09 +0100 Subject: [PATCH 39/42] formatting and new show --- src/varname.jl | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/varname.jl b/src/varname.jl index bf2ab703..a1cfbbbb 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -85,8 +85,9 @@ end tupleindex2lens(indexing::Tuple{}) = IdentityLens() tupleindex2lens(indexing::Tuple{<:Tuple}) = IndexLens(first(indexing)) -tupleindex2lens(indexing::Tuple) = - IndexLens(first(indexing)) ∘ tupleindex2lens(indexing[2:end]) +function tupleindex2lens(indexing::Tuple) + return IndexLens(first(indexing)) ∘ tupleindex2lens(indexing[2:end]) +end """ getsym(vn::VarName) @@ -143,18 +144,23 @@ end Base.hash(vn::VarName, h::UInt) = hash((getsym(vn), getindexing(vn)), h) -Base.:(==)(x::VarName, y::VarName) = - getsym(x) == getsym(y) && getindexing(x) == getindexing(y) +function Base.:(==)(x::VarName, y::VarName) + return getsym(x) == getsym(y) && getindexing(x) == getindexing(y) +end # Allow compositions with lenses. -Setfield.compose(vn::VarName{sym,<:Lens}, lens::Lens) where {sym} = - VarName{sym}(vn.indexing ∘ lens) +function Base.:∘(vn::VarName{sym,<:Lens}, lens::Lens) where {sym} + return VarName{sym}(vn.indexing ∘ lens) +end function Base.show(io::IO, vn::VarName{<:Any,<:Lens}) + # No need to check `Setfield.has_atlens_support` since + # `VarName` does not allow dynamic lenses. print(io, getsym(vn)) _print_application(io, vn.indexing) end +# This is all just to allow to convert `Colon()` into `:`. _print_application(io::IO, l::Lens) = Setfield.print_application(io, l) function _print_application(io::IO, l::ComposedLens) _print_application(io, l.outer) @@ -174,9 +180,13 @@ prettify_index(::Colon) = ":" Return a `Symbol` represenation of the variable identifier `VarName`. +# Examples ```jldoctest julia> Symbol(@varname(x[1][2:3])) Symbol("x[1][2:3]") + +julia> Symbol(@varname(x[1][:])) +Symbol("x[1][:]") ``` """ Base.Symbol(vn::VarName) = Symbol(string(vn)) # simplified symbol From 59052068f9718a7592e66c67a4a961061fedfdbe Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Sun, 1 Aug 2021 17:15:34 +0100 Subject: [PATCH 40/42] updated comment --- src/varname.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/varname.jl b/src/varname.jl index a1cfbbbb..c31b5f29 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -33,8 +33,7 @@ struct VarName{sym,T<:Lens} indexing::T function VarName{sym}(indexing=IdentityLens()) where {sym} - # TODO: Should we completely disallow or just `@warn`? - # TODO: Does this affect performance? + # TODO: Should we completely disallow or just `@warn` of limited support? if !is_static_lens(indexing) error("attempted to construct `VarName` with dynamic lens of type $(nameof(typeof(indexing)))") end From 3f287f5ebac2c938737a16fb5531f20cd69ed45a Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Tue, 17 Aug 2021 02:31:02 +0100 Subject: [PATCH 41/42] fixed doctests --- src/varname.jl | 9 ++++----- test/Project.toml | 2 ++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/varname.jl b/src/varname.jl index c31b5f29..3db2c3c3 100644 --- a/src/varname.jl +++ b/src/varname.jl @@ -23,7 +23,7 @@ julia> vn = VarName{:x}(Setfield.IndexLens((Colon(), 1)) ∘ Setfield.IndexLens( x[:,1][2] julia> vn.indexing -((Colon(), 1), (2,)) +(@lens _[Colon(), 1][2]) julia> @varname x[:, 1][1+1] x[:,1][2] @@ -118,7 +118,7 @@ julia> getindexing(@varname(x[1][2:3])) (@lens _[1][2:3]) julia> getindexing(@varname(y)) -() +(@lens _) ``` """ getindexing(vn::VarName) = vn.indexing @@ -322,7 +322,7 @@ e.g. `_[1][2].a[2]` and `_[1][2].a`. In such a scenario we do the following: which then returns `false`. # Example -```jldoctest; setup=:(using Setfield) +```jldoctest; setup=:(using Setfield; using AbstractPPL: subsumes_index) julia> t = @lens(_[1].a); u = @lens(_[1]); julia> subsumes_index(t, u) @@ -438,10 +438,9 @@ Return `vn` instantiated on `x`, i.e. any runtime information evaluated using `x # Examples ```jldoctest; setup=:(using Setfield) - julia> x = (a = [1.0 2.0;], ); -julia> AbstractPPL.concretize(@lens(_.a[1, end][:]), x) +julia> AbstractPPL.concretize(@varname(x.a[1, end][:]), x) x.a[1,2][:] ``` """ diff --git a/test/Project.toml b/test/Project.toml index 4635bf6c..13dcfc86 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,7 +1,9 @@ [deps] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [compat] Documenter = "0.26.3, 0.27" +Setfield = "0.7.1" julia = "1" From b6169027cc447dacedf3875909db31cf37c6bae0 Mon Sep 17 00:00:00 2001 From: Hong Ge Date: Fri, 20 Aug 2021 14:59:49 +0100 Subject: [PATCH 42/42] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 9139b1c9..32502929 100644 --- a/Project.toml +++ b/Project.toml @@ -3,7 +3,7 @@ uuid = "7a57a42e-76ec-4ea3-a279-07e840d6d9cf" keywords = ["probablistic programming"] license = "MIT" desc = "Common interfaces for probabilistic programming" -version = "0.2.0" +version = "0.3.0" [deps] AbstractMCMC = "80f14c24-f653-4e6a-9b94-39d6b0f70001"