diff --git a/Project.toml b/Project.toml index cd9cc86d..e90d3ac1 100644 --- a/Project.toml +++ b/Project.toml @@ -15,16 +15,22 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [weakdeps] AxisKeys = "94b1ba4f-4ee9-5380-92f1-94cde586c3c5" +Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Requires = "ae029012-a4dd-5104-9daa-d747884805df" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" StructArrays = "09ab397b-f2b6-538f-b94a-2f83cf4a842a" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [extensions] AccessorsAxisKeysExt = "AxisKeys" +AccessorsDatesExt = "Dates" AccessorsIntervalSetsExt = "IntervalSets" +AccessorsLinearAlgebraExt = "LinearAlgebra" AccessorsStaticArraysExt = "StaticArrays" AccessorsStructArraysExt = "StructArrays" +AccessorsTestExt = "Test" [compat] AxisKeys = "0.2" diff --git a/ext/AccessorsDatesExt.jl b/ext/AccessorsDatesExt.jl new file mode 100644 index 00000000..392f5276 --- /dev/null +++ b/ext/AccessorsDatesExt.jl @@ -0,0 +1,37 @@ +module AccessorsDatesExt + +using Accessors +import Accessors: set +using Dates + +set(x::DateTime, ::Type{Date}, y) = DateTime(y, Time(x)) +set(x::DateTime, ::Type{Time}, y) = DateTime(Date(x), y) +set(x::T, ::Type{T}, y) where {T <: Union{Date, Time}} = y + +# directly mirrors Dates.value implementation in stdlib +set(x::Date, ::typeof(Dates.value), y) = @set x.instant.periods.value = y +set(x::DateTime, ::typeof(Dates.value), y) = @set x.instant.periods.value = y +set(x::Time, ::typeof(Dates.value), y) = @set x.instant.value = y + +set(x::Date, ::typeof(year), y) = Date(y, month(x), day(x)) +set(x::Date, ::typeof(month), y) = Date(year(x), y, day(x)) +set(x::Date, ::typeof(day), y) = Date(year(x), month(x), y) +set(x::Date, ::typeof(yearmonth), y::NTuple{2}) = Date(y..., day(x)) +set(x::Date, ::typeof(monthday), y::NTuple{2}) = Date(year(x), y...) +set(x::Date, ::typeof(yearmonthday), y::NTuple{3}) = Date(y...) +set(x::Date, ::typeof(dayofweek), y) = firstdayofweek(x) + Day(y - 1) + +set(x::Time, ::typeof(hour), y) = Time(y, minute(x), second(x), millisecond(x), microsecond(x), nanosecond(x)) +set(x::Time, ::typeof(minute), y) = Time(hour(x), y, second(x), millisecond(x), microsecond(x), nanosecond(x)) +set(x::Time, ::typeof(second), y) = Time(hour(x), minute(x), y, millisecond(x), microsecond(x), nanosecond(x)) +set(x::Time, ::typeof(millisecond), y) = Time(hour(x), minute(x), second(x), y, microsecond(x), nanosecond(x)) +set(x::Time, ::typeof(microsecond), y) = Time(hour(x), minute(x), second(x), millisecond(x), y, nanosecond(x)) +set(x::Time, ::typeof(nanosecond), y) = Time(hour(x), minute(x), second(x), millisecond(x), microsecond(x), y) + +set(x::DateTime, optic::Union{typeof.((year, month, day, yearmonth, monthday, yearmonthday, dayofweek))...}, y) = modify(d -> set(d, optic, y), x, Date) +set(x::DateTime, optic::Union{typeof.((hour, minute, second, millisecond))...}, y) = modify(d -> set(d, optic, y), x, Time) + + +set(x::AbstractString, optic::Base.Fix2{Type{T}}, dt::T) where {T <: Union{Date, Time, DateTime}} = Dates.format(dt, optic.x) + +end diff --git a/ext/AccessorsLinearAlgebraExt.jl b/ext/AccessorsLinearAlgebraExt.jl new file mode 100644 index 00000000..9904475a --- /dev/null +++ b/ext/AccessorsLinearAlgebraExt.jl @@ -0,0 +1,9 @@ +module AccessorsLinearAlgebraExt + +import Accessors: set +using LinearAlgebra: norm, normalize + +set(arr, ::typeof(normalize), val) = norm(arr) * val +set(arr, ::typeof(norm), val) = map(Base.Fix2(*, val / norm(arr)), arr) # should we check val is positive? + +end diff --git a/ext/AccessorsTestExt.jl b/ext/AccessorsTestExt.jl new file mode 100644 index 00000000..f69bc41c --- /dev/null +++ b/ext/AccessorsTestExt.jl @@ -0,0 +1,44 @@ +module AccessorsTestExt + +using Accessors +using Test: @test + +function Accessors.test_getset_laws(lens, obj, val1, val2; cmp=(==)) + # set ⨟ get + val = lens(obj) + @test cmp(set(obj, lens, val), obj) + + # get ⨟ set + obj1 = set(obj, lens, val1) + @test cmp(lens(obj1), val1) + + # set idempotent + obj12 = set(obj1, lens, val2) + obj2 = set(obj12, lens, val2) + @test cmp(obj12, obj2) +end + +function Accessors.test_modify_law(f, lens, obj) + obj_modify = modify(f, obj, lens) + old_val = lens(obj) + val = f(old_val) + obj_setfget = set(obj, lens, val) + @test obj_modify == obj_setfget +end + +function Accessors.test_getsetall_laws(optic, obj, vals1, vals2; cmp=(==)) + # setall ⨟ getall + vals = getall(obj, optic) + @test cmp(setall(obj, optic, vals), obj) + + # getall ⨟ setall + obj1 = setall(obj, optic, vals1) + @test cmp(collect(getall(obj1, optic)), collect(vals1)) + + # setall idempotent + obj12 = setall(obj1, optic, vals2) + obj2 = setall(obj12, optic, vals2) + @test obj12 == obj2 +end + +end diff --git a/src/Accessors.jl b/src/Accessors.jl index 0a1cc2a7..d23f319e 100644 --- a/src/Accessors.jl +++ b/src/Accessors.jl @@ -20,4 +20,10 @@ include("sugar.jl") include("functionlenses.jl") include("testing.jl") +@static if !isdefined(Base, :get_extension) + include("../ext/AccessorsDatesExt.jl") + include("../ext/AccessorsLinearAlgebraExt.jl") + include("../ext/AccessorsTestExt.jl") +end + end diff --git a/src/functionlenses.jl b/src/functionlenses.jl index 1f5911b7..a3eb8888 100644 --- a/src/functionlenses.jl +++ b/src/functionlenses.jl @@ -1,6 +1,3 @@ -using LinearAlgebra: norm, normalize -using Dates - # first and last on general indexable collections set(obj, ::typeof(first), val) = @set obj[firstindex(obj)] = val set(obj, ::typeof(last), val) = @set obj[lastindex(obj)] = val @@ -132,46 +129,10 @@ set(x, f::Base.Fix2{typeof(rem)}, y) = set(x, @optic(last(divrem(_, f.x))), y) set(x::AbstractString, f::Base.Fix1{typeof(parse), Type{T}}, y::T) where {T} = string(y) -set(arr, ::typeof(normalize), val) = norm(arr) * val -set(arr, ::typeof(norm), val) = map(Base.Fix2(*, val / norm(arr)), arr) # should we check val is positive? - set(f, ::typeof(inverse), invf) = setinverse(f, invf) -set(obj, ::typeof(Base.splat(atan)), val) = @set Tuple(obj) = norm(obj) .* sincos(val) -set(obj, ::typeof(Base.splat(hypot)), val) = @set norm(obj) = val - -################################################################################ -##### dates -################################################################################ -set(x::DateTime, ::Type{Date}, y) = DateTime(y, Time(x)) -set(x::DateTime, ::Type{Time}, y) = DateTime(Date(x), y) -set(x::T, ::Type{T}, y) where {T <: Union{Date, Time}} = y - -# directly mirrors Dates.value implementation in stdlib -set(x::Date, ::typeof(Dates.value), y) = @set x.instant.periods.value = y -set(x::DateTime, ::typeof(Dates.value), y) = @set x.instant.periods.value = y -set(x::Time, ::typeof(Dates.value), y) = @set x.instant.value = y - -set(x::Date, ::typeof(year), y) = Date(y, month(x), day(x)) -set(x::Date, ::typeof(month), y) = Date(year(x), y, day(x)) -set(x::Date, ::typeof(day), y) = Date(year(x), month(x), y) -set(x::Date, ::typeof(yearmonth), y::NTuple{2}) = Date(y..., day(x)) -set(x::Date, ::typeof(monthday), y::NTuple{2}) = Date(year(x), y...) -set(x::Date, ::typeof(yearmonthday), y::NTuple{3}) = Date(y...) -set(x::Date, ::typeof(dayofweek), y) = firstdayofweek(x) + Day(y - 1) - -set(x::Time, ::typeof(hour), y) = Time(y, minute(x), second(x), millisecond(x), microsecond(x), nanosecond(x)) -set(x::Time, ::typeof(minute), y) = Time(hour(x), y, second(x), millisecond(x), microsecond(x), nanosecond(x)) -set(x::Time, ::typeof(second), y) = Time(hour(x), minute(x), y, millisecond(x), microsecond(x), nanosecond(x)) -set(x::Time, ::typeof(millisecond), y) = Time(hour(x), minute(x), second(x), y, microsecond(x), nanosecond(x)) -set(x::Time, ::typeof(microsecond), y) = Time(hour(x), minute(x), second(x), millisecond(x), y, nanosecond(x)) -set(x::Time, ::typeof(nanosecond), y) = Time(hour(x), minute(x), second(x), millisecond(x), microsecond(x), y) - -set(x::DateTime, optic::Union{typeof.((year, month, day, yearmonth, monthday, yearmonthday, dayofweek))...}, y) = modify(d -> set(d, optic, y), x, Date) -set(x::DateTime, optic::Union{typeof.((hour, minute, second, millisecond))...}, y) = modify(d -> set(d, optic, y), x, Time) - - -set(x::AbstractString, optic::Base.Fix2{Type{T}}, dt::T) where {T <: Union{Date, Time, DateTime}} = Dates.format(dt, optic.x) +set(obj, ::typeof(Base.splat(atan)), val) = @set Tuple(obj) = hypot(obj...) .* sincos(val) +set(obj, ::typeof(Base.splat(hypot)), val) = map(Base.Fix2(*, val / hypot(obj...)), obj) ################################################################################ ##### strings diff --git a/src/testing.jl b/src/testing.jl index e5f356a9..2687a6ae 100644 --- a/src/testing.jl +++ b/src/testing.jl @@ -1,40 +1,5 @@ -using Test: @test -function test_getset_laws(lens, obj, val1, val2; cmp=(==)) - - # set ⨟ get - val = lens(obj) - @test cmp(set(obj, lens, val), obj) - - # get ⨟ set - obj1 = set(obj, lens, val1) - @test cmp(lens(obj1), val1) - - # set idempotent - obj12 = set(obj1, lens, val2) - obj2 = set(obj12, lens, val2) - @test cmp(obj12, obj2) -end - -function test_modify_law(f, lens, obj) - obj_modify = modify(f, obj, lens) - old_val = lens(obj) - val = f(old_val) - obj_setfget = set(obj, lens, val) - @test obj_modify == obj_setfget -end - -function test_getsetall_laws(optic, obj, vals1, vals2; cmp=(==)) - - # setall ⨟ getall - vals = getall(obj, optic) - @test cmp(setall(obj, optic, vals), obj) - - # getall ⨟ setall - obj1 = setall(obj, optic, vals1) - @test cmp(collect(getall(obj1, optic)), collect(vals1)) - - # setall idempotent - obj12 = setall(obj1, optic, vals2) - obj2 = setall(obj12, optic, vals2) - @test obj12 == obj2 -end +# placeholders only +# actual definitions in ext/AccessorsTestExt.jl +function test_getset_laws end +function test_modify_law end +function test_getsetall_laws end