Re: [julia-users] Re: We have typed functions, don't we?

2014-08-20 Thread Rafael Fourquet

 'Traditional' Julia: you can pass a function f as an argument to another
 function g.

 Rafael's functors: instead you create new type F whose constructor is f,
 and then you make g a parametric function with a parameter F instead of an
 argument f.


A typo here, the constructor of type F is F, and that's the point: F is
both a type and a callable.


 And the point of this technique is that you can potentially boost
 performance because the compiler can in-line the implicit call to f
 inherent in g{F}(...), whereas the call to f in g(f,...) cannot be inlined.


Yes (but I think it is planned to make the compiler be able to inline
normal functions... maybe by being desugared to functors ;-) ).
The declaration of g must be like g{F}(::Type{F}, ...) to enable inlining,
and simply g(F, ...) or g(F::DataType, ...) otherwise (AFAIU, g would then
have only one specialization for all F::DataType, as typeof(F)==DataType).

Have I got it?  Are there other advantages to Rafael's functors (simplified
 code, better error-checking...) compared to 'traditional' Julia?


One application is using the inheritance machinery (type constraints...),
eg to take Stefan's example from
https://github.com/JuliaLang/julia/issues/1470:

abstract BinaryOperator
function call{op:BinaryOperator}(::Type{op}, v::Vector, w::Vector)
# implement generic vectorized operation in terms of functor op
end
function call{op:BinaryOperator}(::Type{op}, v::Vector, x)
# implement generic op(vector, scalar)
end

type plus : BinaryOperator end
plus(a::Number, b::Number) = a+b
plus(args...) = call(plus, args...) # delegate everything else to super
types

This is less ideal than what is proposed in issue 1470, as one has to write
the delegating code (last line above) for each new sub-type of
BinaryOperator, however only one generic such line is necessary (and can be
macroized :) ).


[julia-users] Re: We have typed functions, don't we?

2014-08-20 Thread vavasis
Rafael,

Thanks very much for taking the time to explain this.  I agree that this is 
a very nice technique that I will probably start using.  I have expressed 
the opinion in other threads that Julia could do more to enforce 
type-correctness, and I see your functor proposal as a big step in the 
right direction.

-- Steve Vavasis


On Thursday, August 14, 2014 2:59:28 PM UTC-4, Rafael Fourquet wrote:

 Hi Julia users,

 As this is my first post, I must say I'm extremely impressed by julia, met 
 2 weeks ago. For many months I've meant to clean-up a C++/python lib of 
 mine, for public consumption. I couldn't help but procrastinate, as the 
 maintenance cost is so high a tax (eg. too clever hacks, big compilation 
 times (mainly due to having to instantiate non-lazily an explosion of 
 templates to make them accessible from within python), the last version of 
 gcc not accepting my code anymore, for probably valid reasons, I guess, 
 etc.). So big thanks to all julia developers for destroying my problem and 
 freeing me from C++. I gave in after checking by myself the hard-to-believe 
 efficiency promise (speed within 120% of C) on the non-toy DES crypto 
 algorithm. I'm very grateful for this awesome beautiful, fast, fun, 
 language, that I didn't dare to dream of.

 The problem of higher-order-functions inlining (via callable types, aka 
 functors) is getting a lot of attention, but I will need fast solutions 
 soon and don't want to give up the right to use cost-free abstractions 
 (offered by C++). So should we hold our breath on built-in functors, are is 
 it still worth investigating library solutions?

 I found NumericFunctors/NumericFuns (and base/reduce.jl) based on 
 `abstract Functor{N}`, are there others built on `Functor{Result, Arg1, 
 ..., ArgN}`?

 I wanted to share a tiny trick that occurred to me today, which doesn't 
 seem to be widely known, unleashing stateless functors (type constructors 
 have a dual nature). Please let me know if it relies on undefined 
 behavior.

 abstract Functor{N}
 typealias UnaryFunctor Functor{1}
 typealias BinaryFunctor Functor{2}

 type double : UnaryFunctor end
 double(x) = 2*x

 type plus : BinaryFunctor end
 plus(x, y) = x+y
 plus(x::String, y::String) = x*y # ;-)

 type compose{Out:Functor, In:Functor} : Functor
 compose(x...) = Out(In(x...))
 # compose(x...) = Out(In(x...)...) # more general but awfully slow
 end

 doubleplus = compose{double, plus}

 # constrained function:
 f{F:BinaryFunctor}(fun::Type{F}, x) = fun(x, x)
 f(plus, 1)

 # etc.

 This can't serve as a drop-in replacement for normal functions, as many 
 API hardcode `Function` in their type signatures.
 I only barely tested the performances. In the benchmark code at [1], 
 statement (1) is 10 times slower than statement (2) on my machine, but if 
 `plus` gets replaced by the definition above, it's only about 20% slower. 
 And then becomes unsurprisigly faster for `doubleplus`. However, for e.g. 
 `reduce(plus, 0, 1:n)` I observed no gain (compared to plus(x,y)=x+y, as 
 reduce(+,...) is special-cased and optimized), any ideas why?

 Note that the native_code of `doubleplus` seems to be more optimized 
 with this finer grained definition of compose (which improves only slightly 
 the benchmark):

 type compose{Out:Functor, In:Functor} : Functor
 if In : UnaryFunctor
 compose(x) = Out(In(x))
 elseif In : BinaryFunctor
compose(x, y) = Out(In(x, y))
 else
 compose(x...) = Out(In(x...))
 end
 end

 On a related note, my last question: is there a variation of the following 
 definition that would compile?

 arity{N, F:Functor{N}}(::Type{F}) = N

 Thanks,
 Rafael

 [1] https://github.com/timholy/NumericFunctors.jl, benchmark code below:
 plus(x, y) = x + y
 map_plus(x, y) = map(plus, x, y)

 a = rand(1000, 1000)
 b = rand(1000, 1000)

  # warming up and get map_plus compiled
 a + b
 map_plus(a, b)

  # benchmark
 @time for i in 1 : 10 map_plus(a, b) end  # -- statement (1)
 @time for i in 1 : 10 a + b end   # -- statement (2)



[julia-users] Re: We have typed functions, don't we?

2014-08-19 Thread vavasis
As a newcomer to Julia, I'm having a bit of trouble following this 
discussion: is it OK if I try to summarize it in my own words and you tell 
me whether I've understood?

'Traditional' Julia: you can pass a function f as an argument to another 
function g.

Rafael's functors: instead you create new type F whose constructor is f, 
and then you make g a parametric function with a parameter F instead of an 
argument f.

And the point of this technique is that you can potentially boost 
performance because the compiler can in-line the implicit call to f 
inherent in g{F}(...), whereas the call to f in g(f,...) cannot be inlined.

Have I got it?  Are there other advantages to Rafael's functors (simplified 
code, better error-checking...) compared to 'traditional' Julia?




On Thursday, August 14, 2014 2:59:28 PM UTC-4, Rafael Fourquet wrote:

 Hi Julia users,

 As this is my first post, I must say I'm extremely impressed by julia, met 
 2 weeks ago. For many months I've meant to clean-up a C++/python lib of 
 mine, for public consumption. I couldn't help but procrastinate, as the 
 maintenance cost is so high a tax (eg. too clever hacks, big compilation 
 times (mainly due to having to instantiate non-lazily an explosion of 
 templates to make them accessible from within python), the last version of 
 gcc not accepting my code anymore, for probably valid reasons, I guess, 
 etc.). So big thanks to all julia developers for destroying my problem and 
 freeing me from C++. I gave in after checking by myself the hard-to-believe 
 efficiency promise (speed within 120% of C) on the non-toy DES crypto 
 algorithm. I'm very grateful for this awesome beautiful, fast, fun, 
 language, that I didn't dare to dream of.

 The problem of higher-order-functions inlining (via callable types, aka 
 functors) is getting a lot of attention, but I will need fast solutions 
 soon and don't want to give up the right to use cost-free abstractions 
 (offered by C++). So should we hold our breath on built-in functors, are is 
 it still worth investigating library solutions?

 I found NumericFunctors/NumericFuns (and base/reduce.jl) based on 
 `abstract Functor{N}`, are there others built on `Functor{Result, Arg1, 
 ..., ArgN}`?

 I wanted to share a tiny trick that occurred to me today, which doesn't 
 seem to be widely known, unleashing stateless functors (type constructors 
 have a dual nature). Please let me know if it relies on undefined 
 behavior.

 abstract Functor{N}
 typealias UnaryFunctor Functor{1}
 typealias BinaryFunctor Functor{2}

 type double : UnaryFunctor end
 double(x) = 2*x

 type plus : BinaryFunctor end
 plus(x, y) = x+y
 plus(x::String, y::String) = x*y # ;-)

 type compose{Out:Functor, In:Functor} : Functor
 compose(x...) = Out(In(x...))
 # compose(x...) = Out(In(x...)...) # more general but awfully slow
 end

 doubleplus = compose{double, plus}

 # constrained function:
 f{F:BinaryFunctor}(fun::Type{F}, x) = fun(x, x)
 f(plus, 1)

 # etc.

 This can't serve as a drop-in replacement for normal functions, as many 
 API hardcode `Function` in their type signatures.
 I only barely tested the performances. In the benchmark code at [1], 
 statement (1) is 10 times slower than statement (2) on my machine, but if 
 `plus` gets replaced by the definition above, it's only about 20% slower. 
 And then becomes unsurprisigly faster for `doubleplus`. However, for e.g. 
 `reduce(plus, 0, 1:n)` I observed no gain (compared to plus(x,y)=x+y, as 
 reduce(+,...) is special-cased and optimized), any ideas why?

 Note that the native_code of `doubleplus` seems to be more optimized 
 with this finer grained definition of compose (which improves only slightly 
 the benchmark):

 type compose{Out:Functor, In:Functor} : Functor
 if In : UnaryFunctor
 compose(x) = Out(In(x))
 elseif In : BinaryFunctor
compose(x, y) = Out(In(x, y))
 else
 compose(x...) = Out(In(x...))
 end
 end

 On a related note, my last question: is there a variation of the following 
 definition that would compile?

 arity{N, F:Functor{N}}(::Type{F}) = N

 Thanks,
 Rafael

 [1] https://github.com/timholy/NumericFunctors.jl, benchmark code below:
 plus(x, y) = x + y
 map_plus(x, y) = map(plus, x, y)

 a = rand(1000, 1000)
 b = rand(1000, 1000)

  # warming up and get map_plus compiled
 a + b
 map_plus(a, b)

  # benchmark
 @time for i in 1 : 10 map_plus(a, b) end  # -- statement (1)
 @time for i in 1 : 10 a + b end   # -- statement (2)