{"action":"created","issue":{"url":"https://api.github.com/repos/JuliaLang/julia/issues/14910","repository_url":"https://api.github.com/repos/JuliaLang/julia","labels_url":"https://api.github.com/repos/JuliaLang/julia/issues/14910/labels{/name}","comments_url":"https://api.github.com/repos/JuliaLang/julia/issues/14910/comments","events_url":"https://api.github.com/repos/JuliaLang/julia/issues/14910/events","html_url":"https://github.com/JuliaLang/julia/issues/14910","id":130719040,"number":14910,"title":"Programmatically overloading methods","user":{"login":"omus","id":1675958,"avatar_url":"https://avatars.githubusercontent.com/u/1675958?v=3","gravatar_id":"","url":"https://api.github.com/users/omus","html_url":"https://github.com/omus","followers_url":"https://api.github.com/users/omus/followers","following_url":"https://api.github.com/users/omus/following{/other_user}","gists_url":"https://api.github.com/users/omus/gists{/gist_id}","starred_url":"https://api.github.com/users/omus/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/omus/subscriptions","organizations_url":"https://api.github.com/users/omus/orgs","repos_url":"https://api.github.com/users/omus/repos","events_url":"https://api.github.com/users/omus/events{/privacy}","received_events_url":"https://api.github.com/users/omus/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":2,"created_at":"2016-02-02T15:36:30Z","updated_at":"2016-02-02T16:17:57Z","closed_at":null,"body":"I've written a package called [Mocking.jl](https://github.com/invenia/Mocking.jl) which allows developers to temporarily overwrite a method for testing purposes. Due to some changes in Julia 0.5 it appears that a method is not overwritten until the executing function is complete.\r\n\r\nI've distilled Mocking.jl into a single method that should work in both 0.4 and 0.5:\r\n```julia\r\nfunction overwrite(body::Function, o::Function, n::Function, sig::Array{DataType}, restore::Bool=true)\r\n    old_methods = collect(methods(o, Tuple{sig...}))\r\n    new_methods = collect(methods(n, Tuple{sig...}))\r\n\r\n    length(old_methods) != 1 && error(\"old function has too many methods\")\r\n    length(new_methods) != 1 && error(\"new function has too many methods\")\r\n\r\n    old_method = old_methods[1]\r\n    new_method = new_methods[1]\r\n\r\n    # Save the original implementation\r\n    org_impl = old_method.func\r\n\r\n    # Generate a method expression to overwrite the existing function\r\n\r\n    if isa(old_method.func, Function)\r\n        mod = old_method.func.code.module\r\n        name = old_method.func.code.name\r\n    else\r\n        mod = old_method.func.module\r\n        name = old_method.func.name\r\n    end\r\n\r\n    types = [:(::$t) for t in sig]\r\n    expr = :($name($(types...)) = nothing)\r\n\r\n    # Overwrite a method such that Julia uses updated implementation on future calls\r\n    Core.eval(mod, expr)\r\n\r\n    # Replace implementation\r\n    old_method.func = new_method.func\r\n\r\n    try\r\n        return body()\r\n    finally\r\n        # Restore the overwritten function body with the original implementation\r\n        if restore\r\n            Core.eval(mod, expr)\r\n            old_method.func = org_impl\r\n        end\r\n    end\r\nend\r\n\r\nrep(x::AbstractString) = x\r\n```\r\n\r\nHere is the expected behaviour as demonstrated in Julia 0.4.3:\r\n```julia\r\njulia> overwrite(open, rep, [AbstractString], true) do\r\n          open(\"foo\")\r\n       end\r\nWARNING: Method definition open(AbstractString) in module Main at none:1 overwritten in module Base at none:25.\r\n\"foo\"\r\n\r\njulia> open(\"foo\")\r\nERROR: SystemError: opening file foo: No such file or directory\r\n in open at ./iostream.jl:90\r\n in open at iostream.jl:99\r\n```\r\n\r\nIn Julia 0.5.0-dev+2444 the behaviour has changed:\r\n```julia\r\njulia> overwrite(open, rep, [AbstractString], true) do\r\n          open(\"foo\")\r\n       end\r\nWARNING: Method definition open(AbstractString) in module Base at iostream.jl:99 overwritten at none:25.\r\nWARNING: Method definition open(AbstractString) in module Main at none:1 overwritten in module Base at none:25.\r\nERROR: SystemError: opening file foo: No such file or directory\r\n in systemerror(Base.#systemerror, ASCIIString, Bool) at /Users/omus/Development/Julia/latest/usr/lib/julia/sys.dylib:-1\r\n [inlined code] from ./c.jl:91\r\n in open(Base.#open, ASCIIString, Bool, Bool, Bool, Bool, Bool) at ./iostream.jl:90\r\n in overwrite(#overwrite, ##1#2, Any, Any, Array{DataType,1}, Bool) at ./none:34\r\n in eval(Core.#eval, Module, Any) at ./boot.jl:267\r\n\r\njulia> open(\"foo\")\r\nERROR: SystemError: opening file foo: No such file or directory\r\n in systemerror(Base.#systemerror, ASCIIString, Bool) at /Users/omus/Development/Julia/latest/usr/lib/julia/sys.dylib:-1\r\n [inlined code] from ./c.jl:91\r\n in open(Base.#open, ASCIIString, Bool, Bool, Bool, Bool, Bool) at ./iostream.jl:90\r\n in open(Base.#open, ASCIIString) at ./iostream.jl:99\r\n in eval(Core.#eval, Module, Any) at ./boot.jl:267\r\n```\r\n\r\nIf we don't restore the old function you can see that the behaviour does change but only once we get back to the REPL (0.5.0-dev+2444):\r\n\r\n```julia\r\njulia> overwrite(open, rep, [AbstractString], false) do\r\n           open(\"foo\")\r\n       end\r\nWARNING: Method definition open(AbstractString) in module Base at iostream.jl:99 overwritten at none:25.\r\nERROR: SystemError: opening file foo: No such file or directory\r\n in systemerror(Base.#systemerror, ASCIIString, Bool) at /Users/omus/Development/Julia/latest/usr/lib/julia/sys.dylib:-1\r\n [inlined code] from ./c.jl:91\r\n in open(Base.#open, ASCIIString, Bool, Bool, Bool, Bool, Bool) at ./iostream.jl:90\r\n in overwrite(#overwrite, ##1#2, Any, Any, Array{DataType,1}, Bool) at ./none:34\r\n in eval(Core.#eval, Module, Any) at ./boot.jl:267\r\n\r\njulia> open(\"foo\")\r\n\"foo\"\r\n```\r\n\r\nIs this new behaviour intentional? Should I be implementing this in a different way? Should method overriding be integrated into Base to ensure its continued availability?\r\n\r\n\r\n"},"comment":{"url":"https://api.github.com/repos/JuliaLang/julia/issues/comments/178664470","html_url":"https://github.com/JuliaLang/julia/issues/14910#issuecomment-178664470","issue_url":"https://api.github.com/repos/JuliaLang/julia/issues/14910","id":178664470,"user":{"login":"omus","id":1675958,"avatar_url":"https://avatars.githubusercontent.com/u/1675958?v=3","gravatar_id":"","url":"https://api.github.com/users/omus","html_url":"https://github.com/omus","followers_url":"https://api.github.com/users/omus/followers","following_url":"https://api.github.com/users/omus/following{/other_user}","gists_url":"https://api.github.com/users/omus/gists{/gist_id}","starred_url":"https://api.github.com/users/omus/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/omus/subscriptions","organizations_url":"https://api.github.com/users/omus/orgs","repos_url":"https://api.github.com/users/omus/repos","events_url":"https://api.github.com/users/omus/events{/privacy}","received_events_url":"https://api.github.com/users/omus/received_events","type":"User","site_admin":false},"created_at":"2016-02-02T16:17:57Z","updated_at":"2016-02-02T16:17:57Z","body":"Lol, thanks @JeffBezanson. I figured that integrating function overriding into Base wouldn't be popular."}}