diff --git a/.gitignore b/.gitignore index 703c136..0a75f1d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *.jl.mem docs/build/ tmp* +Manifest.toml diff --git a/Project.toml b/Project.toml index 8d49409..12a18be 100644 --- a/Project.toml +++ b/Project.toml @@ -4,6 +4,7 @@ author = ["Mauro Werder "] version = "0.12.0" [deps] +ConstructionBase = "187b0558-2788-49d3-abe0-74a17ed4e7c9" OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" [compat] diff --git a/src/Parameters.jl b/src/Parameters.jl index 0e60af0..2e6ea8f 100644 --- a/src/Parameters.jl +++ b/src/Parameters.jl @@ -1,5 +1,3 @@ -__precompile__() - """ This is a package I use to handle numerical-model parameters, thus the name. However, it should be useful otherwise too. @@ -41,10 +39,14 @@ c = BB.c ``` """ module Parameters +import ConstructionBase +using ConstructionBase: setproperties + import Base: @__doc__ import OrderedCollections: OrderedDict export @with_kw, @with_kw_noshow, type2dict, reconstruct, @unpack, @pack!, @pack +export setproperties ## Parser helpers ################# @@ -199,11 +201,7 @@ end """ Make a new instance of a type with the same values as the input type except for the fields given in the AbstractDict second argument or as -keywords. Works for types, Dicts, and NamedTuples. - -Note: this is not very performant. Check Setfield.jl for a faster & -nicer implementation. - +keywords. Works for types, Dicts, and NamedTuples. ```jldoctest julia> using Parameters @@ -218,25 +216,29 @@ A(3, 4) julia> b = reconstruct(a, b=99) A(3, 99) ``` +See also [`setproperties`](@ref). + +# Overloading +# +If you want to overload `reconstruct` consider overloading `setproperties` instead. +More precisely if you have a dict like type `MyDict <: AbstractDict`, it makes +sense to overload `reconstruct(o::MyDict, di)`. If you have a record like type, +you should probably overload `setproperties(o, patch::NamedTuple)` instead. + +Formulated differently, if the goal of the reconstruct call is to update: +* the result of `getindex`, overload `reconstruct` +* the result of `getproperty` overload `setproperties` instead. """ -function reconstruct(pp::T, di) where T - di = !isa(di, AbstractDict) ? Dict(di) : copy(di) - if pp isa AbstractDict - for (k,v) in di - !haskey(pp, k) && error("Field $k not in type $T") - pp[k] = v - end - return pp - else - ns = fieldnames(T) - args = [] - for (i,n) in enumerate(ns) - push!(args, pop!(di, n, getfield(pp, n))) - end - length(di)!=0 && error("Fields $(keys(di)) not in type $T") - return pp isa NamedTuple ? T(Tuple(args)) : T(args...) +function reconstruct(pp::AbstractDict, di) + # di = !isa(di, AbstractDict) ? Dict(di) : copy(di) + for (k,v) in di + !haskey(pp, k) && throw(KeyError(k)) + pp[k] = v end + return pp end +reconstruct(obj, patch::NamedTuple) = setproperties(obj, patch) +reconstruct(obj, patch) = reconstruct(obj, (;patch...)) reconstruct(pp; kws...) = reconstruct(pp, kws) diff --git a/test/runtests.jl b/test/runtests.jl index 6a2becb..744020a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,11 +4,11 @@ using Parameters, Test, Markdown, REPL a8679 = @eval (a=1, b=2) ra8679 = @eval (a=1, b=44) @test ra8679 == reconstruct(a8679, b=44) -@test_throws ErrorException reconstruct(a8679, c=44) +@test_throws ArgumentError reconstruct(a8679, c=44) a8679 = Dict(:a=>1, :b=>2) @test Dict(:a=>1, :b=>44) == reconstruct(a8679, b=44) -@test_throws ErrorException reconstruct(a8679, c=44) +@test_throws KeyError reconstruct(a8679, c=44) struct A8679 a @@ -16,7 +16,7 @@ struct A8679 end a8679 = A8679(1, 2) @test A8679(1, 44) == reconstruct(a8679, b=44) -@test_throws ErrorException reconstruct(a8679, c=44) +@test_throws ArgumentError reconstruct(a8679, c=44) ########## # @with_kw @@ -36,6 +36,8 @@ end # @test "A field Default: sdaf\n" == Markdown.plain(@doc MT1.c) @test "Field r Default: 4\n" == Markdown.plain(REPL.fielddoc(MT1, :r)) @test "A field Default: sdaf\n" == Markdown.plain(REPL.fielddoc(MT1, :c)) +@test setproperties(MT1(), r=12).r === 12 +@test setproperties(MT1(), r=12, c=nothing).c === nothing abstract type AMT1_2 end "Test documentation with type-parameter" @@ -75,7 +77,8 @@ const TMT1_3 = MT1_3{Int} # Julia bug https://github.com/JuliaLang/julia/issues/ end MT2(r=4, a=5., c=6) MT2(r=4, a=5, c="asdf") -MT2(4, "dsaf", 5) +obj = MT2(4, "dsaf", 5) +@test setproperties(obj, (r=42, c=:c)).r === 42 @test_throws ErrorException MT2(r=4) @test_throws ErrorException MT2() @@ -114,6 +117,12 @@ MT3_2(a=5) MT3_2(4,5) @test_throws ErrorException MT3_2(r=4) @test_throws ErrorException MT3_2() +o = MT3_2(r=4, a=5.0) +o2 = setproperties(o, r=10, a=20) +@test o2.r === 10 +@test o2.a === 20.0 +@test o.r === 4 +@test o.a === 5.0 # with type-parameters @with_kw struct MT4{R,I} @@ -130,6 +139,8 @@ mt4=MT4(5.4, 4) # outer positional @test_throws ErrorException MT4{Float32, Int}() @test_throws InexactError MT4{Float32,Int}(a=5.5) @test_throws InexactError MT4{Float32,Int}(5.5, 5.5) +@test setproperties(MT4(a=1), r=12.0).r === 12.0 +@test setproperties(MT4(a=1), r=12, a=:hello).a === :hello # with type-parameters 2 abstract type AMT{R<:Real} end