From 535ac985eb6d8f72bc274190b4702dc71966fae3 Mon Sep 17 00:00:00 2001 From: Martin Holters Date: Mon, 18 Apr 2016 10:48:35 +0200 Subject: [PATCH 1/2] Add @functorize The new macro @functorize(f) turns function f into a Functor object if there is one in Base corresponding to f. E.g. @functorize(+) yields Base.AddFun(); @functorize(<) yields < in Julia 0.3, but Base.LessFun() in 0.4. --- README.md | 2 ++ src/Compat.jl | 75 +++++++++++++++++++++++++++++++++++++++++++ test/runtests.jl | 82 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+) 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..3bb26dd13 100644 --- a/src/Compat.jl +++ b/src/Compat.jl @@ -980,4 +980,79 @@ 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 + 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 + 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..b705d99e4 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1004,3 +1004,85 @@ 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 From 2aacc82f29a16da4ea23fe2ff80652d1ce313d99 Mon Sep 17 00:00:00 2001 From: Martin Holters Date: Wed, 20 Apr 2016 10:03:36 +0200 Subject: [PATCH 2/2] Add @functorize(centralizedabs2fun) Note that `@functorize(centralizedabs2fun)` returns the type which can then be used to construct the actual functor as e.g. `@functorize(centralizedabs2fun)(23)`. --- src/Compat.jl | 2 ++ test/runtests.jl | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/src/Compat.jl b/src/Compat.jl index 3bb26dd13..53ed113c2 100644 --- a/src/Compat.jl +++ b/src/Compat.jl @@ -985,6 +985,7 @@ 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()) : @@ -998,6 +999,7 @@ macro functorize(f) 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()) : diff --git a/test/runtests.jl b/test/runtests.jl index b705d99e4..b390f17d0 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1086,3 +1086,8 @@ 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