diff --git a/README.md b/README.md index dba5b94f2..8d88b017c 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,8 @@ Currently, the `@compat` macro supports the following syntaxes: * `@inline` and `@noinline` have been added. On 0.3, these are "no-ops," meaning they don't actually do anything. +* `@functorize` (not present in any Julia version) takes a function (or operator) and turns it into a functor object if one is available in the used Julia version. E.g. something like `mapreduce(Base.AbsFun(), Base.MulFun(), x)` can now be written as `mapreduce(@functorize(abs), @functorize(*), x)` to work accross different Julia versions. + ## Other changes * `Dict(ks, vs)` is now `Dict(zip(ks, vs))` [#8521](https://github.com/JuliaLang/julia/pull/8521) diff --git a/src/Compat.jl b/src/Compat.jl index 289fe87f1..53ed113c2 100644 --- a/src/Compat.jl +++ b/src/Compat.jl @@ -980,4 +980,81 @@ if !isdefined(Base, :istextmime) istextmime(m::@compat(Union{MIME,AbstractString})) = istext(m) end +export @functorize +macro functorize(f) + if VERSION >= v"0.5.0-dev+3701" + f === :scalarmax ? :(Base.scalarmax) : + f === :scalarmin ? :(Base.scalarmin) : + f === :centralizedabs2fun ? :(typeof(Base.centralizedabs2fun(0)).name.primary) : + f + else + f = f === :identity ? :(Base.IdFun()) : + f === :abs ? :(Base.AbsFun()) : + f === :abs2 ? :(Base.Abs2Fun()) : + f === :exp ? :(Base.ExpFun()) : + f === :log ? :(Base.LogFun()) : + f === :& ? :(Base.AndFun()) : + f === :| ? :(Base.OrFun()) : + f === :+ ? :(Base.AddFun()) : + f === :* ? :(Base.MulFun()) : + f === :scalarmax ? :(Base.MaxFun()) : + f === :scalarmin ? :(Base.MinFun()) : + f === :centralizedabs2fun ? :(Base.CentralizedAbs2Fun) : + f + if VERSION >= v"0.4.0-dev+4902" + f = f === :< ? :(Base.LessFun()) : + f === :> ? :(Base.MoreFun()) : + f + end + if VERSION >= v"0.4.0-dev+4902" + f = f === :conj ? :(Base.ConjFun()) : + f + end + if VERSION >= v"0.4.0-dev+6254" + f = f === :- ? :(Base.SubFun()) : + f === :^ ? :(Base.PowFun()) : + f + end + if VERSION >= v"0.4.0-dev+6256" + f = f === :/ ? :(Base.RDivFun()) : + f === :\ ? :(Base.LDivFun()) : + f === :div ? :(Base.IDivFun()) : + f + end + if VERSION >= v"0.4.0-dev+6353" + f = f === :$ ? :(Base.XorFun()) : + f === :.+ ? :(Base.DotAddFun()) : + f === :.- ? :(Base.DotSubFun()) : + f === :.* ? :(Base.DotMulFun()) : + f === :mod ? :(Base.ModFun()) : + f === :rem ? :(Base.RemFun()) : + # DotRemFun is defined, but ::call(::DotRemFun, ...) is not until later + #f === :.% ? :(Base.DotRemFun()) : + f === :.<< ? :(Base.DotLSFun()) : + f === :.>> ? :(Base.DotRSFun()) : + f + end + if VERSION >= v"0.4.0-dev+6359" + f = f === :./ ? :(Base.DotRDivFun()) : + f + end + if VERSION >= v"0.4.0-rc1+59" + f = f === :max ? :(Base.ElementwiseMaxFun()) : + f === :min ? :(Base.ElementwiseMinFun()) : + f + end + if VERSION >= v"0.5.0-dev+741" + f = f === :complex ? :(Base.SparseArrays.ComplexFun()) : + f === :dot ? :(Base.SparseArrays.DotFun()) : + f + end + if VERSION >= v"0.5.0-dev+1472" + f = f === symbol(".÷") ? :(Base.DotIDivFun()) : + f === :.% ? :(Base.DotRemFun()) : + f + end + f + end +end + end # module diff --git a/test/runtests.jl b/test/runtests.jl index 7664e2049..b390f17d0 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1004,3 +1004,90 @@ if VERSION < v"0.4.0" else @compat rc = RemoteChannel{Channel{Any}}(1,2,3) end + +# @functorize +function checkfunc(Fun, func) + if VERSION >= v"0.5.0-dev+3701" + @eval @test @functorize($(func)) === Base.$(func) + else + if isdefined(Base, Fun) + @eval @test isa(@functorize($(func)), Base.$(Fun)) + else + @eval @test isa(@functorize($(func)), Function) + @eval @test @functorize($(func)) === Base.$(func) + end + end +end + +for (Fun, func) in [(:IdFun, :identity), + (:AbsFun, :abs), + (:Abs2Fun, :abs2), + (:ExpFun, :exp), + (:LogFun, :log), + (:ConjFun, :conj)] + begin + if isdefined(Base, func) + checkfunc(Fun, func) + a = rand(1:10, 10) + @eval @test mapreduce($(func), +, $(a)) == mapreduce(@functorize($(func)), +, $(a)) + end + end +end + +for (Fun, func) in [(:AndFun, :&), + (:OrFun, :|), + (:XorFun, :$), + (:AddFun, :+), + (:DotAddFun, :.+), + (:SubFun, :-), + (:DotSubFun, :.-), + (:MulFun, :*), + (:DotMulFun, :.*), + (:RDivFun, :/), + (:DotRDivFun, :./), + (:LDivFun, :\), + (:IDivFun, :div), + (:DotIDivFun, symbol(".÷")), + (:ModFun, :mod), + (:RemFun, :rem), + (:DotRemFun, :.%), + (:PowFun, :^), + (:MaxFun, :scalarmax), + (:MinFun, :scalarmin), + (:LessFun, :<), + (:MoreFun, :>), + (:DotLSFun, :.<<), + (:DotRSFun, :.>>), + (:ElementwiseMaxFun, :max), + (:ElementwiseMinFun, :min)] + begin + if isdefined(Base, func) && (func !== :.>> || VERSION >= v"0.4.0-dev+553") && (func !== :.% || VERSION >= v"0.5.0-dev+1472") + checkfunc(Fun, func) + a = rand(1:10, 10) + @eval @test mapreduce(identity, Base.$(func), $(a)) == mapreduce(identity, @functorize($(func)), $(a)) + end + end +end + +if VERSION >= v"0.5.0-dev+3701" + @test @functorize(complex) === complex + @test @functorize(dot) === dot +else + if isdefined(Base, :SparseArrays) && isdefined(Base.SparseArrays, :ComplexFun) + @test isa(@functorize(complex), Base.SparseArrays.ComplexFun) + @test isa(@functorize(dot), Base.SparseArrays.DotFun) + else + @test isa(@functorize(complex), Function) + @test isa(@functorize(dot), Function) + @test @functorize(complex) === complex + @test @functorize(dot) === dot + end +end +let a = rand(1:10, 10) + @test mapreduce(identity, dot, a) == mapreduce(identity, @functorize(dot), a) +end +@test isa(@functorize(centralizedabs2fun)(1), @functorize(centralizedabs2fun)) +@test isa(@functorize(centralizedabs2fun)(1.0), @functorize(centralizedabs2fun)) +let a = rand(1:10, 10) + @eval @test mapreduce(x -> abs2(x - 1), +, $(a)) == mapreduce(@functorize(centralizedabs2fun)(1), +, $(a)) +end