Skip to content

Commit 54bbad3

Browse files
authored
Split up varname.jl into more manageable bits (#140)
* Split up varname.jl into more manageable bits * [skip ci] No coveralls
1 parent 4c77999 commit 54bbad3

File tree

9 files changed

+533
-538
lines changed

9 files changed

+533
-538
lines changed

.github/workflows/CI.yml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
os: macos-latest
2929

3030
steps:
31-
- uses: actions/checkout@v4
31+
- uses: actions/checkout@v5
3232

3333
- uses: julia-actions/setup-julia@v2
3434
with:
@@ -49,8 +49,3 @@ jobs:
4949
files: lcov.info
5050
token: ${{ secrets.CODECOV_TOKEN }}
5151
fail_ci_if_error: true
52-
53-
- uses: coverallsapp/github-action@v2
54-
with:
55-
github-token: ${{ secrets.GITHUB_TOKEN }}
56-
path-to-lcov: lcov.info

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
[![CI](https://github.com/TuringLang/AbstractPPL.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/TuringLang/AbstractPPL.jl/actions/workflows/CI.yml?query=branch%3Amain)
44
[![IntegrationTest](https://github.com/TuringLang/AbstractPPL.jl/actions/workflows/IntegrationTest.yml/badge.svg?branch=main)](https://github.com/TuringLang/AbstractPPL.jl/actions/workflows/IntegrationTest.yml?query=branch%3Amain)
5-
[![Coverage Status](https://coveralls.io/repos/github/TuringLang/AbstractPPL.jl/badge.svg?branch=main)](https://coveralls.io/github/TuringLang/AbstractPPL.jl?branch=main)
65
[![Codecov](https://codecov.io/gh/TuringLang/AbstractPPL.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/TuringLang/AbstractPPL.jl)
76

87
A light-weight package to factor out interfaces and associated APIs for modelling languages for

src/AbstractPPL.jl

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,14 @@ export AbstractProbabilisticProgram,
2929
# Abstract traces
3030
export AbstractModelTrace
3131

32-
include("varname.jl")
3332
include("abstractmodeltrace.jl")
3433
include("abstractprobprog.jl")
3534
include("evaluate.jl")
36-
include("varname_leaves.jl")
37-
include("hasvalue.jl")
35+
include("varname/varname.jl")
36+
include("varname/subsumes.jl")
37+
include("varname/hasvalue.jl")
38+
include("varname/leaves.jl")
39+
include("varname/prefix.jl")
40+
include("varname/serialize.jl")
3841

3942
end # module
File renamed without changes.
File renamed without changes.

src/varname/prefix.jl

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
### Functionality for prefixing and unprefixing VarNames.
2+
3+
"""
4+
optic_to_vn(optic)
5+
6+
Convert an Accessors optic to a VarName. This is best explained through
7+
examples.
8+
9+
```jldoctest; setup=:(using Accessors)
10+
julia> AbstractPPL.optic_to_vn(Accessors.@o _.a)
11+
a
12+
13+
julia> AbstractPPL.optic_to_vn(Accessors.@o _.a.b)
14+
a.b
15+
16+
julia> AbstractPPL.optic_to_vn(Accessors.@o _.a[1])
17+
a[1]
18+
```
19+
20+
The outermost layer of the optic (technically, what Accessors.jl calls the
21+
'innermost') must be a `PropertyLens`, or else it will fail. This is because a
22+
VarName needs to have a symbol.
23+
24+
```jldoctest; setup=:(using Accessors)
25+
julia> AbstractPPL.optic_to_vn(Accessors.@o _[1])
26+
ERROR: ArgumentError: optic_to_vn: could not convert optic `(@o _[1])` to a VarName
27+
[...]
28+
```
29+
"""
30+
function optic_to_vn(::Accessors.PropertyLens{sym}) where {sym}
31+
return VarName{sym}()
32+
end
33+
function optic_to_vn(
34+
o::ComposedFunction{Outer,Accessors.PropertyLens{sym}}
35+
) where {Outer,sym}
36+
return VarName{sym}(o.outer)
37+
end
38+
optic_to_vn(o::ComposedFunction) = optic_to_vn(normalise(o))
39+
function optic_to_vn(@nospecialize(o))
40+
msg = "optic_to_vn: could not convert optic `$o` to a VarName"
41+
throw(ArgumentError(msg))
42+
end
43+
44+
unprefix_optic(o, ::typeof(identity)) = o # Base case
45+
function unprefix_optic(optic, optic_prefix)
46+
# Technically `unprefix_optic` only receives optics that were part of
47+
# VarNames, so the optics should already be normalised (in the inner
48+
# constructor of the VarName). However I guess it doesn't hurt to do it
49+
# again to be safe.
50+
optic = normalise(optic)
51+
optic_prefix = normalise(optic_prefix)
52+
# strip one layer of the optic and check for equality
53+
head = _head(optic)
54+
head_prefix = _head(optic_prefix)
55+
if head != head_prefix
56+
msg = "could not remove prefix $(optic_prefix) from optic $(optic)"
57+
throw(ArgumentError(msg))
58+
end
59+
# recurse
60+
return unprefix_optic(_tail(optic), _tail(optic_prefix))
61+
end
62+
63+
"""
64+
unprefix(vn::VarName, prefix::VarName)
65+
66+
Remove a prefix from a VarName.
67+
68+
```jldoctest
69+
julia> AbstractPPL.unprefix(@varname(y.x), @varname(y))
70+
x
71+
72+
julia> AbstractPPL.unprefix(@varname(y.x.a), @varname(y))
73+
x.a
74+
75+
julia> AbstractPPL.unprefix(@varname(y[1].x), @varname(y[1]))
76+
x
77+
78+
julia> AbstractPPL.unprefix(@varname(y), @varname(n))
79+
ERROR: ArgumentError: could not remove prefix n from VarName y
80+
[...]
81+
```
82+
"""
83+
function unprefix(
84+
vn::VarName{sym_vn}, prefix::VarName{sym_prefix}
85+
) where {sym_vn,sym_prefix}
86+
if sym_vn != sym_prefix
87+
msg = "could not remove prefix $(prefix) from VarName $(vn)"
88+
throw(ArgumentError(msg))
89+
end
90+
optic_vn = getoptic(vn)
91+
optic_prefix = getoptic(prefix)
92+
return optic_to_vn(unprefix_optic(optic_vn, optic_prefix))
93+
end
94+
95+
"""
96+
prefix(vn::VarName, prefix::VarName)
97+
98+
Add a prefix to a VarName.
99+
100+
```jldoctest
101+
julia> AbstractPPL.prefix(@varname(x), @varname(y))
102+
y.x
103+
104+
julia> AbstractPPL.prefix(@varname(x.a), @varname(y))
105+
y.x.a
106+
107+
julia> AbstractPPL.prefix(@varname(x.a), @varname(y[1]))
108+
y[1].x.a
109+
```
110+
"""
111+
function prefix(vn::VarName{sym_vn}, prefix::VarName{sym_prefix}) where {sym_vn,sym_prefix}
112+
optic_vn = getoptic(vn)
113+
optic_prefix = getoptic(prefix)
114+
new_optic_vn = optic_vn PropertyLens{sym_vn}() optic_prefix
115+
return VarName{sym_prefix}(new_optic_vn)
116+
end

src/varname/serialize.jl

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
### Serialisation to JSON / string
2+
3+
using JSON: JSON
4+
5+
# String constants for each index type that we support serialisation /
6+
# deserialisation of
7+
const _BASE_INTEGER_TYPE = "Base.Integer"
8+
const _BASE_VECTOR_TYPE = "Base.Vector"
9+
const _BASE_UNITRANGE_TYPE = "Base.UnitRange"
10+
const _BASE_STEPRANGE_TYPE = "Base.StepRange"
11+
const _BASE_ONETO_TYPE = "Base.OneTo"
12+
const _BASE_COLON_TYPE = "Base.Colon"
13+
const _CONCRETIZED_SLICE_TYPE = "AbstractPPL.ConcretizedSlice"
14+
const _BASE_TUPLE_TYPE = "Base.Tuple"
15+
16+
"""
17+
index_to_dict(::Integer)
18+
index_to_dict(::AbstractVector{Int})
19+
index_to_dict(::UnitRange)
20+
index_to_dict(::StepRange)
21+
index_to_dict(::Colon)
22+
index_to_dict(::ConcretizedSlice{T, Base.OneTo{I}}) where {T, I}
23+
index_to_dict(::Tuple)
24+
25+
Convert an index `i` to a dictionary representation.
26+
"""
27+
index_to_dict(i::Integer) = Dict("type" => _BASE_INTEGER_TYPE, "value" => i)
28+
index_to_dict(v::Vector{Int}) = Dict("type" => _BASE_VECTOR_TYPE, "values" => v)
29+
function index_to_dict(r::UnitRange)
30+
return Dict("type" => _BASE_UNITRANGE_TYPE, "start" => r.start, "stop" => r.stop)
31+
end
32+
function index_to_dict(r::StepRange)
33+
return Dict(
34+
"type" => _BASE_STEPRANGE_TYPE,
35+
"start" => r.start,
36+
"stop" => r.stop,
37+
"step" => r.step,
38+
)
39+
end
40+
function index_to_dict(r::Base.OneTo{I}) where {I}
41+
return Dict("type" => _BASE_ONETO_TYPE, "stop" => r.stop)
42+
end
43+
index_to_dict(::Colon) = Dict("type" => _BASE_COLON_TYPE)
44+
function index_to_dict(s::ConcretizedSlice{T,R}) where {T,R}
45+
return Dict("type" => _CONCRETIZED_SLICE_TYPE, "range" => index_to_dict(s.range))
46+
end
47+
function index_to_dict(t::Tuple)
48+
return Dict("type" => _BASE_TUPLE_TYPE, "values" => map(index_to_dict, t))
49+
end
50+
51+
"""
52+
dict_to_index(dict)
53+
dict_to_index(symbol_val, dict)
54+
55+
Convert a dictionary representation of an index `dict` to an index.
56+
57+
Users can extend the functionality of `dict_to_index` (and hence `VarName`
58+
de/serialisation) by extending this method along with [`index_to_dict`](@ref).
59+
Specifically, suppose you have a custom index type `MyIndexType` and you want
60+
to be able to de/serialise a `VarName` containing this index type. You should
61+
then implement the following two methods:
62+
63+
1. `AbstractPPL.index_to_dict(i::MyModule.MyIndexType)` should return a
64+
dictionary representation of the index `i`. This dictionary must contain the
65+
key `"type"`, and the corresponding value must be a string that uniquely
66+
identifies the index type. Generally, it makes sense to use the name of the
67+
type (perhaps prefixed with module qualifiers) as this value to avoid
68+
clashes. The remainder of the dictionary can have any structure you like.
69+
70+
2. Suppose the value of `index_to_dict(i)["type"]` is `"MyModule.MyIndexType"`.
71+
You should then implement the corresponding method
72+
`AbstractPPL.dict_to_index(::Val{Symbol("MyModule.MyIndexType")}, dict)`,
73+
which should take the dictionary representation as the second argument and
74+
return the original `MyIndexType` object.
75+
76+
To see an example of this in action, you can look in the the AbstractPPL test
77+
suite, which contains a test for serialising OffsetArrays.
78+
"""
79+
function dict_to_index(dict)
80+
t = dict["type"]
81+
if t == _BASE_INTEGER_TYPE
82+
return dict["value"]
83+
elseif t == _BASE_VECTOR_TYPE
84+
return collect(Int, dict["values"])
85+
elseif t == _BASE_UNITRANGE_TYPE
86+
return dict["start"]:dict["stop"]
87+
elseif t == _BASE_STEPRANGE_TYPE
88+
return dict["start"]:dict["step"]:dict["stop"]
89+
elseif t == _BASE_ONETO_TYPE
90+
return Base.OneTo(dict["stop"])
91+
elseif t == _BASE_COLON_TYPE
92+
return Colon()
93+
elseif t == _CONCRETIZED_SLICE_TYPE
94+
return ConcretizedSlice(Base.Slice(dict_to_index(dict["range"])))
95+
elseif t == _BASE_TUPLE_TYPE
96+
return tuple(map(dict_to_index, dict["values"])...)
97+
else
98+
# Will error if the method is not defined, but this hook allows users
99+
# to extend this function
100+
return dict_to_index(Val(Symbol(t)), dict)
101+
end
102+
end
103+
104+
optic_to_dict(::typeof(identity)) = Dict("type" => "identity")
105+
function optic_to_dict(::PropertyLens{sym}) where {sym}
106+
return Dict("type" => "property", "field" => String(sym))
107+
end
108+
optic_to_dict(i::IndexLens) = Dict("type" => "index", "indices" => index_to_dict(i.indices))
109+
function optic_to_dict(c::ComposedFunction)
110+
return Dict(
111+
"type" => "composed",
112+
"outer" => optic_to_dict(c.outer),
113+
"inner" => optic_to_dict(c.inner),
114+
)
115+
end
116+
117+
function dict_to_optic(dict)
118+
if dict["type"] == "identity"
119+
return identity
120+
elseif dict["type"] == "index"
121+
return IndexLens(dict_to_index(dict["indices"]))
122+
elseif dict["type"] == "property"
123+
return PropertyLens{Symbol(dict["field"])}()
124+
elseif dict["type"] == "composed"
125+
return dict_to_optic(dict["outer"]) dict_to_optic(dict["inner"])
126+
else
127+
error("Unknown optic type: $(dict["type"])")
128+
end
129+
end
130+
131+
function varname_to_dict(vn::VarName)
132+
return Dict("sym" => getsym(vn), "optic" => optic_to_dict(getoptic(vn)))
133+
end
134+
135+
function dict_to_varname(dict::Dict{<:AbstractString,Any})
136+
return VarName{Symbol(dict["sym"])}(dict_to_optic(dict["optic"]))
137+
end
138+
139+
"""
140+
varname_to_string(vn::VarName)
141+
142+
Convert a `VarName` as a string, via an intermediate dictionary. This differs
143+
from `string(vn)` in that concretised slices are faithfully represented (rather
144+
than being pretty-printed as colons).
145+
146+
For `VarName`s which index into an array, this function will only work if the
147+
indices can be serialised. This is true for all standard Julia index types, but
148+
if you are using custom index types, you will need to implement the
149+
`index_to_dict` and `dict_to_index` methods for those types. See the
150+
documentation of [`dict_to_index`](@ref) for instructions on how to do this.
151+
152+
```jldoctest
153+
julia> varname_to_string(@varname(x))
154+
"{\\"optic\\":{\\"type\\":\\"identity\\"},\\"sym\\":\\"x\\"}"
155+
156+
julia> varname_to_string(@varname(x.a))
157+
"{\\"optic\\":{\\"field\\":\\"a\\",\\"type\\":\\"property\\"},\\"sym\\":\\"x\\"}"
158+
159+
julia> y = ones(2); varname_to_string(@varname(y[:]))
160+
"{\\"optic\\":{\\"indices\\":{\\"values\\":[{\\"type\\":\\"Base.Colon\\"}],\\"type\\":\\"Base.Tuple\\"},\\"type\\":\\"index\\"},\\"sym\\":\\"y\\"}"
161+
162+
julia> y = ones(2); varname_to_string(@varname(y[:], true))
163+
"{\\"optic\\":{\\"indices\\":{\\"values\\":[{\\"range\\":{\\"stop\\":2,\\"type\\":\\"Base.OneTo\\"},\\"type\\":\\"AbstractPPL.ConcretizedSlice\\"}],\\"type\\":\\"Base.Tuple\\"},\\"type\\":\\"index\\"},\\"sym\\":\\"y\\"}"
164+
```
165+
"""
166+
varname_to_string(vn::VarName) = JSON.json(varname_to_dict(vn))
167+
168+
"""
169+
string_to_varname(str::AbstractString)
170+
171+
Convert a string representation of a `VarName` back to a `VarName`. The string
172+
should have been generated by `varname_to_string`.
173+
"""
174+
string_to_varname(str::AbstractString) = dict_to_varname(JSON.parse(str))

0 commit comments

Comments
 (0)