Git Product home page Git Product logo

fastclosures.jl's Introduction

FastClosures

Build Status

A workaround for JuliaLang/julia#15276, for julia-1.x, somewhat in the spirit of FastAnonymous.jl. Provides the @closure macro, which wraps a closure in a let block to make reading variable bindings private to the closure. In certain cases, this make using the closure - and the code surrouding it - much faster. Note that it's not clear that the let block trick implemented in this package helps at all in julia-0.5. However, julia-0.5 compatibility is provided for backward compatibility convenience.

Interface

    @closure closure_expression

Wrap the closure definition closure_expression in a let block to encourage the julia compiler to generate improved type information. For example:

callfunc(f) = f()

function foo(n)
   for i=1:n
       if i >= n
           # Unlikely event - should be fast.  However, capture of `i` inside
           # the closure confuses the julia-0.6 compiler and causes it to box
           # the variable `i`, leading to a 100x performance hit if you remove
           # the `@closure`.
           callfunc(@closure ()->println("Hello \$i"))
       end
   end
end

Here's a further example of where this helps:

using FastClosures

# code_warntype problem
function f1()
    if true
    end
    r = 1
    cb = ()->r
    identity(cb)
end

# code_warntype clean
function f2()
    if true
    end
    r = 1
    cb = @closure ()->r
    identity(cb)
end

@code_warntype f1()
@code_warntype f2()

fastclosures.jl's People

Contributors

abelsiqueira avatar c42f avatar juliatagbot avatar simondanisch avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

fastclosures.jl's Issues

Register FastClosures.jl?

Hi,

Was the plan for this package to get registered?
What needs to be done to get there?

Thanks,

Josh

extra token "Symbol" after end of expression

julia> using FastClosures
INFO: Precompiling module FastClosures.
ERROR: LoadError: syntax: extra token "Symbol" after end of expression
 in include_from_node1(::String) at ./loading.jl:488
 in include_from_node1(::String) at /usr/local/Cellar/julia/0.5.2/lib/julia/sys.dylib:?
 in macro expansion; at ./none:2 [inlined]
 in anonymous at ./<missing>:?
 in eval(::Module, ::Any) at ./boot.jl:234
 in eval(::Module, ::Any) at /usr/local/Cellar/julia/0.5.2/lib/julia/sys.dylib:?
 in process_options(::Base.JLOptions) at ./client.jl:242
 in _start() at ./client.jl:321
 in _start() at /usr/local/Cellar/julia/0.5.2/lib/julia/sys.dylib:?
while loading /Users/dpo/.julia/v0.5/FastClosures/src/FastClosures.jl, in expression starting on line 15

julia> VERSION
v"0.5.2"

Inconsistencies in `@closure` semantics

The semantics of variable capture in @closure were originally intended to be the same as julia, but I got this wrong because at the time I didn't understand the intended semantics of the core language which is that the value associated with a name may be changed either within the closure or within the surrounding function and in both of these cases the change must be dynamically reflected in the other scope.

In particular, the value of x in the following is modified outside the closure, followed by returning closure, and there's nothing @closure can know about this:

using FastClosures

function foo()
    x = 10
    c = ()->x
    x = 20
    c()
end

function bar()
    x = 10
    c = @closure ()->x
    x = 20
    c()
end

julia> foo()
20

julia> bar() # @closure breaks this!
10

On the other hand, @closure does quite a bit of work to allow the following pattern to work the same as in the core language:

function baz()
    x = 10
    c = ()->(x = 20)
    c()
    x
end

function qux()
    x = 10
    c = @closure ()->(x = 20)
    c()
    x
end

julia> baz()

20

julia> qux()
20

(Though x+=10 doesn't appear to work... I guess that's a bug. Ugh!)

In hindsight I think this makes the semantics @closure pretty confusing and it would be better to just freeze all bindings inside the closure to fix them to the values they had at closure creation time. See also some discussion in #5 (CC @bramtayl)

Some possible names for such a macro...

@capturevalues # semantically questionable ?
@freeze_captures
@freeze
@rebind
@constbind # questionable
@newbindings
@let # too technical ?

Alternative anonymous function syntax not working

julia> @closure function (a)
       a
       end                                                                                                                             
ERROR: AssertionError: ex isa Expr && ex.head == Symbol("->")                                                                          

But for me, no need to fix this as

julia> @closure (a) -> begin
       a
       end                                                                                                                             
(::#100) (generic function with 1 method)                                                                                              

works.

Argument to @closure must be a closure!

In Julia 0.6.3, I get

julia> using FastClosures

julia> A = rand(5,5);

julia> h(f1, f2, x) = f1(x) + f2(x)
h (generic function with 1 method)

julia> h(@closure x -> A * x, @closure x -> transpose(A) * x, rand(5))
ERROR: ArgumentError: Argument to @closure must be a closure!  (Got ((x->begin  # REPL[8], line 1:
            transpose(A) * x
        end), rand(5)))

Everything works if I first define

julia> f1 = @closure x -> A * x
(::#7) (generic function with 1 method)

julia> f2 = @closure x -> transpose(A) * x
(::#9) (generic function with 1 method)

julia> h(f1, f2, rand(5))  # all ok!

Nested do blocks do not work

using FastClosures
outer = [[1]]
@closure map(outer) do inner
    map(inner) do x
        x + 1
    end
end
UndefVarError: x not defined

The reason is, that @closure thinks x is a variable from outer scope:

quote
    let x = x
        map(outer) do inner
            map(inner) do x
                x + 1
            end
        end
    end
end

Example is not _bad_ anymore

The last example seems to work now without @closure

julia> versioninfo()
Julia Version 1.7.3
Commit 742b9abb4d (2022-05-06 12:58 UTC)
Platform Info:
  OS: Linux (x86_64-pc-linux-gnu)
  CPU: 11th Gen Intel(R) Core(TM) i7-11700KF @ 3.60GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-12.0.1 (ORCJIT, icelake-client)
Environment:
  JULIA_NUM_THREADS = 16

julia> using FastClosures

julia> # code_warntype problem
       function f1()
           if true
           end
           r = 1
           cb = ()->r
           identity(cb)
       end
f1 (generic function with 1 method)

julia> # code_warntype clean
       function f2()
           if true
           end
           r = 1
           cb = @closure ()->r
           identity(cb)
       end
f2 (generic function with 1 method)

julia> @code_warntype f1()
MethodInstance for f1()
  from f1() in Main at REPL[42]:2
Arguments
  #self#::Core.Const(f1)
Locals
  #16::var"#16#17"{Int64}
  cb::var"#16#17"{Int64}
  r::Int64
Body::var"#16#17"{Int64}
1 ─       Core.NewvarNode(:(#16))
│         Core.NewvarNode(:(cb))
│         Core.NewvarNode(:(r))
└──       goto #2 if not true
2 ─       (r = 1)
│   %6  = Main.:(var"#16#17")::Core.Const(var"#16#17")
│   %7  = Core.typeof(r::Core.Const(1))::Core.Const(Int64)
│   %8  = Core.apply_type(%6, %7)::Core.Const(var"#16#17"{Int64})
│         (#16 = %new(%8, r::Core.Const(1)))
│         (cb = #16::Core.Const(var"#16#17"{Int64}(1)))%11 = Main.identity(cb::Core.Const(var"#16#17"{Int64}(1)))::Core.Const(var"#16#17"{Int64}(1))
└──       return %11


julia> @code_warntype f2()
MethodInstance for f2()
  from f2() in Main at REPL[43]:2
Arguments
  #self#::Core.Const(f2)
Locals
  cb::var"#18#19"{Int64}
  r@_3::Int64
  #18::var"#18#19"{Int64}
  r@_5::Int64
Body::var"#18#19"{Int64}
1 ─       Core.NewvarNode(:(cb))
│         Core.NewvarNode(:(r@_3))
└──       goto #2 if not true
2 ─       (r@_3 = 1)
│   %5  = r@_3::Core.Const(1)
│         (r@_5 = %5)
│   %7  = Main.:(var"#18#19")::Core.Const(var"#18#19")
│   %8  = Core.typeof(r@_5::Core.Const(1))::Core.Const(Int64)
│   %9  = Core.apply_type(%7, %8)::Core.Const(var"#18#19"{Int64})
│         (#18 = %new(%9, r@_5::Core.Const(1)))
│         (cb = #18::Core.Const(var"#18#19"{Int64}(1)))%12 = Main.identity(cb::Core.Const(var"#18#19"{Int64}(1)))::Core.Const(var"#18#19"{Int64}(1))
└──       return %12

@inbounds not supported?

julia> f(x) = @closure i -> @inbounds x[i]
f (generic function with 1 method)

julia> f([])
ERROR: UndefVarError: pop not defined
Stacktrace:
 [1] macro expansion at /home/takafumi/.julia/packages/FastClosures/mMjMm/src/FastClosures.jl:51 [inlined]
 [2] f(::Array{Any,1}) at ./REPL[37]:1
 [3] top-level scope at REPL[38]:1

Maybe @closure needs to exclude :inbounds node when accumulating the variables?

julia> @macroexpand @closure i -> @inbounds x[i]
quote
    #= /home/takafumi/.julia/packages/FastClosures/mMjMm/src/FastClosures.jl:51 =#
    let x = x, pop = pop
        #= /home/takafumi/.julia/packages/FastClosures/mMjMm/src/FastClosures.jl:52 =#
        i->begin
                #= REPL[40]:1 =#
                begin
                    $(Expr(:inbounds, true))
                    local #215#val = x[i]
                    $(Expr(:inbounds, :pop))
                    #215#val
                end
            end
    end
end

Syntax error type declared in inner scope

@closure doesn't like this:

@closure x -> begin
    y::Float64 = 0.0
    y += x
    return y
end

results in

ERROR: syntax: type of "y" declared in inner scope

@macroexpand shows the above get's turned into

let y = y, Float64 = Float64
    x->begin
        # ...
    end
end

I guess there's in principle no reason to disallow things like y::Float64 in a closure, though, right?

do block support?

"""
    @closure1 e::Expr

Applies closure to the first argument of a function. This is useful for do-block
syntax.
"""
macro closure1(e::Expr)
    if e.head != :call
        error("Must be a function call")
    end
    e.args[2] = :($FastClosures.@closure $(e.args[2]))
    esc(e)
end

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.