Skip to content

Commit 233569c

Browse files
authored
Merge pull request #24 from JuliaObjects/properties
Implement mapproperties in terms of setproperties and getproperties
2 parents 5757e6a + 2cdbf8c commit 233569c

File tree

5 files changed

+59
-15
lines changed

5 files changed

+59
-15
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
1515
[compat]
1616
Compat = "3.18"
1717
CompositionsBase = "0.1"
18-
ConstructionBase = "0.1, 1.0"
18+
ConstructionBase = "1.2"
1919
MacroTools = "0.4.4, 0.5"
2020
Requires = "0.5, 1.0"
2121
StaticNumbers = "0.3"

src/optics.jl

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -232,21 +232,25 @@ julia> obj = (a=1, b=2);
232232
julia> Accessors.mapproperties(x -> x+1, obj)
233233
(a = 2, b = 3)
234234
```
235+
236+
# Implementation
237+
238+
This function should not be overloaded directly. Instead both of
239+
* `ConstructionBase.getproperties`
240+
* `ConstructionBase.setproperties`
241+
should be overloaded.
235242
$EXPERIMENTAL
236243
"""
244+
function mapproperties end
245+
246+
function mapproperties(f, nt::NamedTuple)
247+
map(f,nt)
248+
end
249+
237250
function mapproperties(f, obj)
238-
# TODO move this helper elsewhere?
239-
# TODO should we use a generated function based on fieldnames?
240-
pnames = propertynames(obj)
241-
if isempty(pnames)
242-
return obj
243-
else
244-
ctor = constructorof(typeof(obj))
245-
new_props = map(pnames) do p
246-
f(getproperty(obj, p))
247-
end
248-
return ctor(new_props...)
249-
end
251+
nt = getproperties(obj)
252+
patch = mapproperties(f, nt)
253+
return setproperties(obj, patch)
250254
end
251255

252256
"""

test/runtests.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import PerformanceTestTools
33
import Accessors
44

55
using Documenter: doctest
6-
if VERSION >= v"1.5" # ⨟ needs to be defined
6+
if VERSION == v"1.6"
7+
# ⨟ needs to be defined
78
doctest(Accessors)
89
else
910
@info "Skipping doctests, on old VERSION = $VERSION"

test/test_examples.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ using Test
33
dir = joinpath("..", "examples")
44
@testset "example $filename" for filename in readdir(dir)
55
path = joinpath(dir, filename)
6-
include(path)
6+
@eval module $(Symbol("TestExample_$filename"))
7+
include($path)
8+
end
79
end
810
end#module

test/test_optics.jl

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,48 @@
11
module TestOptics
22

33
using Accessors
4+
using Accessors: mapproperties
45
using Test
6+
import ConstructionBase
7+
8+
@testset "mapproperties" begin
9+
res = @inferred mapproperties(x->2x, (a=1, b=2))
10+
@test res === (a=2, b=4)
11+
@test NamedTuple() === @inferred mapproperties(cos, NamedTuple())
12+
struct S{A,B}
13+
a::A
14+
b::B
15+
end
16+
res = @inferred mapproperties(x->2x, S(1, 2.0))
17+
@test res === S(2, 4.0)
18+
19+
# overloading
20+
struct AB
21+
a::Int
22+
b::Int
23+
_checksum::UInt
24+
AB(a,b) = new(a,b,hash(a,hash(b)))
25+
end
26+
27+
ConstructionBase.getproperties(o::AB) = (a=o.a, b=o.b)
28+
ConstructionBase.setproperties(o::AB, patch::NamedTuple) = AB(patch.a, patch.b)
29+
ab = AB(1,2)
30+
ab2 = @inferred mapproperties(x -> 2x, ab)
31+
@test ab2 === AB(2,4)
32+
end
533

634
@testset "Properties" begin
735
pt = (x=1, y=2, z=3)
836
@test (x=0, y=1, z=2) === @set pt |> Properties() -= 1
37+
@inferred modify(x->x-1, pt, Properties())
38+
39+
# custom struct
40+
struct Point{X,Y,Z}
41+
x::X; y::Y; z::Z
42+
end
43+
pt = Point(1f0, 2e0, 3)
44+
pt2 = @inferred modify(x->2x, pt, Properties())
45+
@test pt2 === Point(2f0, 4e0, 6)
946
end
1047

1148
@testset "Elements" begin

0 commit comments

Comments
 (0)