Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Properties #24

Merged
merged 6 commits into from
Jun 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[compat]
Compat = "3.18"
CompositionsBase = "0.1"
ConstructionBase = "0.1, 1.0"
ConstructionBase = "1.2"
MacroTools = "0.4.4, 0.5"
Requires = "0.5, 1.0"
StaticNumbers = "0.3"
Expand Down
28 changes: 16 additions & 12 deletions src/optics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -232,21 +232,25 @@ julia> obj = (a=1, b=2);
julia> Accessors.mapproperties(x -> x+1, obj)
(a = 2, b = 3)
```

# Implementation

This function should not be overloaded directly. Instead both of
* `ConstructionBase.getproperties`
* `ConstructionBase.setproperties`
should be overloaded.
$EXPERIMENTAL
"""
function mapproperties end

function mapproperties(f, nt::NamedTuple)
map(f,nt)
end

function mapproperties(f, obj)
# TODO move this helper elsewhere?
# TODO should we use a generated function based on fieldnames?
pnames = propertynames(obj)
if isempty(pnames)
return obj
else
ctor = constructorof(typeof(obj))
new_props = map(pnames) do p
f(getproperty(obj, p))
end
return ctor(new_props...)
end
nt = getproperties(obj)
patch = mapproperties(f, nt)
return setproperties(obj, patch)
end

"""
Expand Down
3 changes: 2 additions & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import PerformanceTestTools
import Accessors

using Documenter: doctest
if VERSION >= v"1.5" # ⨟ needs to be defined
if VERSION == v"1.6"
# ⨟ needs to be defined
doctest(Accessors)
else
@info "Skipping doctests, on old VERSION = $VERSION"
Expand Down
4 changes: 3 additions & 1 deletion test/test_examples.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ using Test
dir = joinpath("..", "examples")
@testset "example $filename" for filename in readdir(dir)
path = joinpath(dir, filename)
include(path)
@eval module $(Symbol("TestExample_$filename"))
include($path)
end
end
end#module
37 changes: 37 additions & 0 deletions test/test_optics.jl
Original file line number Diff line number Diff line change
@@ -1,11 +1,48 @@
module TestOptics

using Accessors
using Accessors: mapproperties
using Test
import ConstructionBase

@testset "mapproperties" begin
res = @inferred mapproperties(x->2x, (a=1, b=2))
@test res === (a=2, b=4)
@test NamedTuple() === @inferred mapproperties(cos, NamedTuple())
struct S{A,B}
a::A
b::B
end
res = @inferred mapproperties(x->2x, S(1, 2.0))
@test res === S(2, 4.0)

# overloading
struct AB
a::Int
b::Int
_checksum::UInt
AB(a,b) = new(a,b,hash(a,hash(b)))
end

ConstructionBase.getproperties(o::AB) = (a=o.a, b=o.b)
ConstructionBase.setproperties(o::AB, patch::NamedTuple) = AB(patch.a, patch.b)
ab = AB(1,2)
ab2 = @inferred mapproperties(x -> 2x, ab)
@test ab2 === AB(2,4)
end

@testset "Properties" begin
pt = (x=1, y=2, z=3)
@test (x=0, y=1, z=2) === @set pt |> Properties() -= 1
@inferred modify(x->x-1, pt, Properties())

# custom struct
struct Point{X,Y,Z}
x::X; y::Y; z::Z
end
pt = Point(1f0, 2e0, 3)
pt2 = @inferred modify(x->2x, pt, Properties())
@test pt2 === Point(2f0, 4e0, 6)
end

@testset "Elements" begin
Expand Down