Creating function with a macro

701 views
Skip to first unread message

Ben Ward

unread,
Jun 17, 2015, 2:13:41 PM6/17/15
to julia...@googlegroups.com
Hi, I want to create a macro with which I can create a function with a custom bit of code:

In the repl I can do a toy example:

name = :hi

vectype = Vector{Int}

quote
  function ($name)(v::$vectype)

    println("hi")

  end

end


However if I try to put this in a macro and use it I get an error:

macro customFun(vectype::DataType, name::Symbol)

 quote
   function ($name)(v::$vectype)

     println("hi World!")

   end

 end

end

@customFun(Vector{Int}, :hi)


What am I doing wrong? I'd like to use macro arguments to provide a function's name, and the datatype of the argument. I haven't used macros to define functions more complex than simple one liners.

Thanks,
Ben.

Peter Brady

unread,
Jun 17, 2015, 2:56:59 PM6/17/15
to julia...@googlegroups.com
Macros take julia expressions as arguments.  A function which calls @eval will do want you want

julia> function customFun(vectype::DataType, name::Symbol)
           @eval function ($name)(v::$vectype)
               println("hello world")
           end
       end
customFun (generic function with 1 method)

julia> customFun(Vector{Int},:hi)
hi (generic function with 1 method)

julia> hi(rand(Int, 5))
hello world

julia> methods(hi)
# 1 method for generic function "hi":
hi(v::Array{Int64,1}) at none:3

Tom Breloff

unread,
Jun 17, 2015, 3:07:11 PM6/17/15
to julia...@googlegroups.com
My gut reaction is that you don't want to use a macro here.  Can you use a parametric definition:
f{T}(vectype:T) = <do something useful with the T>

or can you just use multiple dispatch:
f{T<:FloatingPoint}(v::Vector{T}) = <something for floats>
f
{T<:Integer}(v::Vector{T}) = <something for ints>

What's your use case?

But to answer your question... I think this should work:


julia> macro customFun(vectype::Expr, name::Symbol)
           quote
               function $(esc(name))(v::$(esc(vectype)))
                   println(typeof(v))
               end
           end
       end

julia> @customFun Vector{Int} f
f (generic function with 1 method)

julia> f(Int[])
Array{Int64,1}



Ben Ward

unread,
Jun 17, 2015, 3:23:17 PM6/17/15
to julia...@googlegroups.com
For the toy example you are right, parametric function would be better - what I'm actually trying to do is define several `searchsortfirst` functions, where the lt function is different. 

This is because I have a vector of one immutable composite type of several values I wish to sort and search. I could sort/search them by their first value, second value, or third value, and there is no canonical lt for the type. 

Base.sort can be provided an anonymous function to use as lt - but performance is absolutely critical in my use case and I need to squeeze as much performance as I can. Benchmark's I have done have shown that using anonymous functions slow things down. However, if I copy the searchsorted code and modify it to contain my custom lt condition, then everything is faster.

Therefore I wanted to make a macro that would define several of my custom searchsortedfirst functions: If I provided it a name for the function, the types/arguments is accepts, and then the custom lt expression. It would return a definition of the custom function with the lt condition hard coded in.

Mauro

unread,
Jun 17, 2015, 4:08:59 PM6/17/15
to julia...@googlegroups.com
On Wed, 2015-06-17 at 21:23, Ben Ward <axolotl...@gmail.com> wrote:
> For the toy example you are right, parametric function would be better -
> what I'm actually trying to do is define several `searchsortfirst`
> functions, where the lt function is different.
>
> This is because I have a vector of one immutable composite type of several
> values I wish to sort and search. I could sort/search them by their first
> value, second value, or third value, and there is no canonical lt for the
> type.
>
> Base.sort can be provided an anonymous function to use as lt - but
> performance is absolutely critical in my use case and I need to squeeze as
> much performance as I can. Benchmark's I have done have shown that using
> anonymous functions slow things down. However, if I copy the searchsorted
> code and modify it to contain my custom lt condition, then everything is
> faster.
>
> Therefore I wanted to make a macro that would define several of my custom
> searchsortedfirst functions: If I provided it a name for the function, the
> types/arguments is accepts, and then the custom lt expression. It would
> return a definition of the custom function with the lt condition hard coded
> in.

Sounds like you should use FastAnonymous.jl or NumericFuns.jl or
Functors:
https://github.com/JuliaLang/julia/blob/e97588db65f590d473e7fbbb127f30c01ea94995/base/functors.jl

>
>
> On Wednesday, June 17, 2015 at 8:07:11 PM UTC+1, Tom Breloff wrote:
>>
>> My gut reaction is that you don't want to use a macro here. Can you use a
>> parametric definition:
>> f{T}(vectype:T) = <do something useful with the T>
>>
>> or can you just use multiple dispatch:
>> f{T<:FloatingPoint}(v::Vector{T}) = <something for floats>
>> f{T<:Integer}(v::Vector{T}) = <something for ints>
>>
>> What's your use case?
>>
>> But to answer your question... I think this should work:
>>
>>
>> julia> macro customFun(vectype::Expr, name::Symbol)
>> quote
>> function $(esc(name))(v::$(esc(vectype)))
>> println(typeof(v))
>> end
>> end
>> end
>>
>> julia> @customFun Vector{Int} f
>> f (generic function with 1 method)
>>
>> julia> f(Int[])
>> Array{Int64,1}
>>
>>
>>
>>
>> On Wednesday, June 17, 2015 at 2:13:41 PM UTC-4, Ben Ward wrote:
>>>
>>> Hi, I want to create a macro with which I can create a function with a
>>> custom bit of code:
>>>
>>> In the repl I can do a toy example:
>>>
>>>
>>> *name = :hi*
>>>
>>>
>>> *vectype = Vector{Int}*
>>>
>>>
>>> *quote** function ($name)(v::$vectype)*
>>>
>>> *println("hi")*
>>>
>>> *end*
>>>
>>> *end*
>>>
>>> However if I try to put this in a macro and use it I get an error:
>>>
>>> *macro customFun(vectype::DataType, name::Symbol)*
>>>
>>>
>>> * quote** function ($name)(v::$vectype)*
>>>
>>> * println("hi World!")*
>>>
>>> * end*
>>>
>>> * end*
>>>
>>>
>>>
>>> *end*
>>>
>>> *@customFun(Vector{Int}, :hi)*

Kristoffer Carlsson

unread,
Jun 17, 2015, 4:11:22 PM6/17/15
to julia...@googlegroups.com
You could also use something called functors which basically are types that overload the call function. When you pass these as argument the compiler can specialize the function on the type of the functor and thus inline the call. See here for example for them being used effectively for performance increase: https://github.com/JuliaLang/julia/pull/11685

As an example I took the code for insertionsort and made it instead accept an argument f which will be the functor. I then create some functors to sort on the different type fields and show an example how sort is called.


function sort!(v::AbstractVector, f, lo::Int=1, hi::Int=length(v))
   
@inbounds for i = lo+1:hi
        j
= i
        x
= v[i]
       
while j > lo
           
if f(x, v[j-1])
                v
[j] = v[j-1]
                j
-= 1
               
continue
           
end
           
break
       
end
        v
[j] = x
   
end
   
return v
end

# Some type
immutable
CompType
    a
::Int
    b
::Int
    c
::Int
end


b
= [CompType(1,2,3), CompType(3,2,1), CompType(2,1,3)]

# Functors
immutable
AFunc end
call
(::AFunc, x, y) = x.a < y.a
immutable
BFunc end
call
(::BFunc, x, y) = x.b < y.b
immutable
CFunc end
call
(::CFunc, x, y) = x.c < y.c

# Can now sort with good performance
sort
!(b, AFunc())
println
(b)
sort
!(b, BFunc())
println
(b)
sort
!(b, CFunc())
println
(b)


Now, this is of course not optimal to rip code out of base. It would be better if we could pass a functor straight to Base.sort!. 

Kristoffer Carlsson

unread,
Jun 17, 2015, 4:13:26 PM6/17/15
to julia...@googlegroups.com
Alos, as Mauro said, instead of manually creating the functors, packages like FastAnonymous.jl makes it easier for you.

Ben Ward

unread,
Jun 17, 2015, 6:20:47 PM6/17/15
to julia...@googlegroups.com
I guess for Base sort! passing a functior overriding call() as lt is possible, if it accepts the three arguments passed to lt in lt(o, x, v[j-1])

Ben Ward

unread,
Jun 17, 2015, 10:03:46 PM6/17/15
to julia...@googlegroups.com
Actually, looking at it again with fresh eyes - I can define a few new classes inhering from Ordering, and then define my own lt(o, x, [j-1]) for each. It looks like these predefined orderings and lt methods in the sorting api - ForwardOrdering, ReverseOrdering and so on are fast as their lt() methods are defined as:

lt(o::ForwardOrdering,       a, b) = isless(a,b)
lt(o::ReverseOrdering,       a, b) = lt(o.fwd,b,a)

Whereas with feeding in custom functions it builds a Lt Operdering type that contains a reference for the custom Lt function - which I believe is the slow version.
Reply all
Reply to author
Forward
0 new messages