"unintern" symbol

proposal

#1

This is a common error for interactive development:

julia> struct MyType a end

julia> isapprox(a::MyType, b::MyType) = abs(a.a-a.b) ≤ 10 # FORGOT TO IMPORT
isapprox (generic function with 1 method)

julia> import Base.isapprox # SO LET'S MAKE UP FOR IT
WARNING: import of Base.isapprox into Main conflicts with an existing identifier; ignored.

In Common Lisp, this is handled by cl:unintern.

I wonder if it would be possible to do something similar for Julia. Strictly for interactive development. The only remedy I know of is restarting (and of course not doing development in Main, but that just happens).


#2

There might be a starting point here https://github.com/timholy/Revise.jl/pull/57


#3

I may have missed something, but AFAICT that (awesome) package has code for deleting methods from the method table, not binding from a module.


#4

Another approach, complementary, would be [as illustration] to inquire “do you want to import isapprox?” As it is, it is much much rarer to parallel operationalize an importable namthan it is to omit the module qualification. Others will have better ways to get here. imo this is an incremental way forward, allowing Julia’s collaborative spirit to just work more so while we do.


#5

I don’t think I agree, I tend to reuse names with different bindings a lot. I would prefer a “fix it later” approach to Julia trying to guess what I am doing.


#6

You can always do this:

julia> isapprox(args...) = Base.isapprox(args...) # SO LET'S MAKE UP FOR IT
isapprox (generic function with 2 methods)

It is not exactly the same thing as importing first. (Your method will be called even if a more specific one exists in base.) But it’s probably good enough in most cases.


#7

I understand that – and, as you like is best.

I have chosen to avoid doing so much of the time, simply to allow the code to be clearer to me after doing other things. I used to do it your way with great glee.


#8

unintern seems like a weird name for this since the symbol is not the issue, it’s the resolved binding that’s the issue. The current situation is pretty annoying and it would be great to figure out a way that trying the wrong thing doesn’t force you to restart your REPL session.


#9

Yes, it is weird in Julia since Julia does not intern symbols. That’s why I put it in quotes.

What I want is basically undo

f_not_yet_defined(...) = ...
const v_not_yet_defined = ...

in an interactive session.


#10

Julia does intern symbols, but doing so is unrelated to resolving bindings.


#11

How about an undo buffer at the REPL? Hitting Cmd-Z would strike out the last command(s) and restore the global state. This might require a lot of memory shuffling, but other memory-intensive software like Photoshop still do it.
(Of course the size of the buffer should be user-definable, and setting it to zero would disable the feature.)


#12

I wonder if this can be achieved via forking in linux.

Separate the “actual” julia process that runs work from the REPL.

Like before each REPL command is run it forks the “actual” julia process and let that process sleep.

Then when you undo, it sleeps the current julia process (so you can redo),
and wakes up the fork.

The important part of the trick is that linux fork’s share references to the same memory,
until one of them writes to it, then it creates a copy (i.e. Copy On Write)

This would avoid dublicating large unchanged memory.
But it would make every operation that did change memory slooooow.
Since changing 1 element of an array would copy that whole array.


#13

Not that slow. I think only one page (maybe 4 kb) would be copied if one element of a large array is changed. And the delay only happens when the user hits “Enter”. I think the key-press itself will take more time than the memory copy most of the time.


#14

Another place to do this would be in Jypyter notebooks. If the Julia/Python/whatever process is forked before executing each code cell, then one could at any time modify a cell and re-evaluate the rest of the document without having to re-run any previous cells. (And then one could undo the entire change with Cmd-Z.)


#15

I don’t think one needs to resort to (potentially very expensive) hacks, as I hope that a solution will be provided by Julia in the medium run.


#16

A general undo feature would also be helpful if you accidentally modify the wrong array at the REPL, or even if you get a core dump. These are things that could never be handled by the language itself.


#17

Helpful, yes, but also very difficult to implement and I imagine mostly orthogonal to the original question.


#18

I don’t think one needs to resort to (potentially very expensive) hacks, as I hope that a solution will be provided by Julia in the medium run.

You mean “surprisingly cheap” hacks. :stuck_out_tongue:

I don’t know that julia can do anything meaningfully better than this; at least performance-wise.
I think an undo requires storing the global state, in a turing complete language.
Or at least storing the parts of the state that can possibly change.
(Working out which parts can definitely (rather than possibly) change I think runs into the halting problem)
There is no better mechanism for that than copy-on-write.

Of course, if it is just unbinding, rather than an univeral undo, that statement does not apply,
at least not as much.
Feels a lot like #265


#19

Or being able to undo arbitrary changes to the state of the entire world…


#20

Just to clarify: my question is about removing or at least overwriting bindings in the namespace of a module, not about undoing arbitrary changes to the state of the world.

This is purely a workflow/convenience question, and yes, very much like the famous #265. Fixing that issue, combined with the amazing Revise.jl, cut my restarts by at least 80%.

Is there an open issue for this?