diff --git a/Project.toml b/Project.toml index 1ae7d47..a1809be 100644 --- a/Project.toml +++ b/Project.toml @@ -1,10 +1,18 @@ name = "AbsentTypes" -uuid = "806a83c1-d09f-449d-ab1d-4e49ad4cc0e9" +uuid = "0d71be07-595a-4f89-9529-4065a4ab43a6" authors = ["CliMA Contributors "] version = "0.1.0" +[compat] +Aqua = "0.8" +Test = "1" +LazyBroadcast = "1" +julia = "1.8" + [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +LazyBroadcast = "9dccce8e-a116-406d-9fcc-a88ed4f510c8" [targets] -test = ["Test"] +test = ["Test", "Aqua", "LazyBroadcast"] diff --git a/docs/Project.toml b/docs/Project.toml index c577252..e0ab8f8 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,4 +1,4 @@ [deps] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244" -AbsentTypes = "806a83c1-d09f-449d-ab1d-4e49ad4cc0e9" +AbsentTypes = "0d71be07-595a-4f89-9529-4065a4ab43a6" diff --git a/docs/make.jl b/docs/make.jl index b1537ee..8b9a839 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -4,12 +4,7 @@ import AbsentTypes bib = DocumenterCitations.CitationBibliography(joinpath(@__DIR__, "refs.bib")) mathengine = Documenter.MathJax( - Dict( - :TeX => Dict( - :equationNumbers => Dict(:autoNumber => "AMS"), - :Macros => Dict(), - ), - ), + Dict(:TeX => Dict(:equationNumbers => Dict(:autoNumber => "AMS"), :Macros => Dict())), ) format = Documenter.HTML( @@ -26,11 +21,7 @@ Documenter.makedocs(; clean = true, doctest = true, modules = [AbsentTypes], - pages = Any[ - "Home" => "index.md", - "API" => "api.md", - "References" => "references.md", - ], + pages = Any["Home"=>"index.md", "API"=>"api.md", "References"=>"references.md"], ) Documenter.deploydocs( diff --git a/docs/src/references.md b/docs/src/references.md new file mode 100644 index 0000000..4b47677 --- /dev/null +++ b/docs/src/references.md @@ -0,0 +1,4 @@ +# References + +```@bibliography +``` \ No newline at end of file diff --git a/src/AbsentTypes.jl b/src/AbsentTypes.jl index 5a079d3..c0e302e 100644 --- a/src/AbsentTypes.jl +++ b/src/AbsentTypes.jl @@ -1,5 +1,76 @@ module AbsentTypes -greet() = print("Hello World!") +""" + Absent() + +A `Base.AbstractBroadcasted` that represents arithmetic object. + +An `Absent()` can be added to, subtracted from, or multiplied by any value in a +broadcast expression without incurring a runtime performance penalty. + +For example, the following rules hold when broadcasting instances of `Absent`: +``` +1 + Absent() == 1 +Absent() + 1 == 1 +1 - Absent() == 1 +1 * Absent() == Absent() +1 / Absent() == Absent() + ``` +""" +struct Absent <: Base.AbstractBroadcasted end +Base.broadcastable(x::Absent) = x + +struct AbsentStyle <: Base.BroadcastStyle end +Base.BroadcastStyle(::Type{<:Absent}) = Absent() + +# Specialize on AbstractArrayStyle to avoid ambiguities with AbstractBroadcasted. +Base.BroadcastStyle(::Absent, ::Base.Broadcast.AbstractArrayStyle) = Absent() +Base.BroadcastStyle(::Base.Broadcast.AbstractArrayStyle, ::Absent) = Absent() + +# Add another method to avoid ambiguity between the previous two. +Base.BroadcastStyle(::Absent, ::Absent) = Absent() + +broadcasted_sum(args) = + if isempty(args) + Absent() + elseif length(args) == 1 + args[1] + else + Base.broadcasted(+, args...) + end +Base.broadcasted(::Absent, ::typeof(+), args...) = + broadcasted_sum(filter(arg -> !(arg isa Absent), args)) + +Base.broadcasted(op::typeof(-), ::Absent, arg) = Base.broadcasted(op, arg) +Base.broadcasted(op::typeof(-), arg, ::Absent) = Base.broadcasted(Base.identity, arg) +Base.broadcasted(op::typeof(-), a::Absent) = Absent() +Base.broadcasted(op::typeof(-), a::Absent, ::Absent) = Base.broadcasted(op, a) + +Base.broadcasted(op::typeof(+), ::Absent, args...) = Base.broadcasted(op, args...) +Base.broadcasted(op::typeof(+), arg, ::Absent) = Base.broadcasted(op, arg) +Base.broadcasted(op::typeof(+), a::Absent, ::Absent) = Base.broadcasted(op, a) + +Base.broadcasted(op::typeof(*), ::Absent, args...) = Absent() +Base.broadcasted(op::typeof(*), arg, ::Absent) = Absent() +Base.broadcasted(op::typeof(*), ::Absent, ::Absent) = Absent() +Base.broadcasted(op::typeof(/), ::Absent, args...) = Absent() +Base.broadcasted(op::typeof(/), arg, ::Absent) = Absent() +Base.broadcasted(op::typeof(/), ::Absent, ::Absent) = Absent() + +function skip_materialize(dest, bc::Base.Broadcast.Broadcasted) + if typeof(bc.f) <: typeof(+) || typeof(bc.f) <: typeof(-) + if length(bc.args) == 2 && + bc.args[1] === dest && + bc.args[2] === Base.Broadcast.Broadcasted(Absent, ()) + return true + else + return false + end + else + return false + end +end + +Base.Broadcast.instantiate(bc::Base.Broadcast.Broadcasted{AbsentStyle}) = x end # module AbsentTypes diff --git a/test/runtests.jl b/test/runtests.jl index 2d969cf..2fa98de 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,11 +1,67 @@ +#= +julia --project +using Revise; using TestEnv; TestEnv.activat(); include("test/runtests.jl") +=# using Test using AbsentTypes +using AbsentTypes: Absent using Aqua +using LazyBroadcast: lazy +import Base.Broadcast: instantiate, materialize, Broadcasted, DefaultArrayStyle -@testset "AbsentTypes" begin - @test 1 == 1 +@testset "Absent" begin + x = [1] + a = Absent() + @test typeof(lazy.(x .+ a)) <: Broadcasted{ + DefaultArrayStyle{1}, + Tuple{Base.OneTo{Int64}}, + typeof(+), + Tuple{Vector{Int64}}, + } + @test typeof(lazy.(a .+ x)) <: Broadcasted{ + DefaultArrayStyle{1}, + Tuple{Base.OneTo{Int64}}, + typeof(+), + Tuple{Vector{Int64}}, + } + @test lazy.(a .* x) isa Absent + @test lazy.(a ./ x) isa Absent + + # + + @test materialize(lazy.(a .+ x .+ 1)) == [2] + @test materialize(lazy.(a .+ 1 .+ x)) == [2] + @test materialize(lazy.(1 .+ a .+ x)) == [2] + @test materialize(lazy.(1 .+ x .+ a)) == [2] + + # - + @test materialize(lazy.(a .- x .- 1)) == [-2] + @test materialize(lazy.(a .- 1 .- x)) == [-2] + @test materialize(lazy.(1 .- a .- x)) == [0] + @test materialize(lazy.(1 .- x .- a)) == [0] + @test materialize(lazy.(a .- a)) == Absent() + @test materialize(lazy.(1 .- 1 .+ a .- a)) == 0 + @test materialize(lazy.(x .- x .+ a .- a)) == [0] + + # * + @test materialize(lazy.(a .* x .* 1)) == Absent() + @test materialize(lazy.(a .* 1 .* x)) == Absent() + @test materialize(lazy.(1 .* a .* x)) == Absent() + @test materialize(lazy.(1 .* x .* a)) == Absent() + + # / + @test materialize(lazy.(a ./ x ./ 1)) == Absent() + @test materialize(lazy.(a ./ 1 ./ x)) == Absent() + @test materialize(lazy.(1 ./ a ./ x)) == Absent() + @test materialize(lazy.(1 ./ x ./ a)) == Absent() + + @test_throws MethodError Absent() + 1 + @test_throws MethodError Absent() - 1 + @test_throws MethodError Absent() * 1 + @test_throws MethodError Absent() / 1 + + @test materialize(Absent()) isa Absent end @testset "Aqua" begin - @test Aqua.test_all(AbsentTypes) + Aqua.test_all(AbsentTypes) end