Now when we have job_start there are many more cases when nested callbacks may be required
Presently it seems that when there is a chain of partials (one called from another) the scope of all partials (except the last one) gets lost.
Steps to reproduce:
demo.vim
function! Callback_Test() let obj1 = {"test": 'test1', "other_test": "other_test"} function obj1.func1() " next line will use value from wrong scope echomsg "inside obj1.func1: test=".self.test " next line will result in: 'Key not present in Dictionary: other_test' echomsg "inside obj1.func1: other_test=".self.other_test endfunction let obj2 = {"test": 'test2', 'callback': obj1.func1} function obj2.func2(...) echomsg "inside obj2.func2: ".self.test call self.callback() endfunction call obj2.func2() endfunction
vim -u NONE -U NONE -S demo.vim
:call Callback_Test()
Expected:
inside obj2.func2: test2
inside obj1.func1: test=test1
inside obj1.func1: other_test=other_test
Actual:
inside obj2.func2: test2
inside obj1.func1: test=test2 " NOTE the wrong value here
Error detected while processing function 2[2]..1:
line 4:
E716: Key not present in Dictionary: other_test
E15: Invalid expression: "inside obj1.func1: other_test=".self.other_test
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub
Hi, I don't have special knowledge of vimscript, but it appears to me that this is the expected behavior. I don't believe that the function stores the information about the dictionary it lives in; also, if it lives in many dictionaries, I would assume it's still just one function reference and not copies. In Javascript for example it works exactly this way.
These also do what I expect:
function! Callback_Test2()
function! GetColor() dict
return self.color
endfunction
let reddog = { 'color': 'red', 'get_color': function('GetColor') }
let bluedog = { 'color': 'blue', 'get_color': function('GetColor') }
echomsg "First dog is " . reddog.get_color()
" --- prints blue, not red.
echomsg "Second dog is " . bluedog.get_color()
endfunction
function! Callback_Test3()
let reddog = { 'color': 'red' }
function! reddog.get_color()
return self.color
endfunction
let bluedog = { 'color': 'blue', 'get_color': reddog.get_color }
echomsg "First dog is " . reddog.get_color()
" --- and do does this:
echomsg "Second dog is " . bluedog.get_color()
endfunction
Hi, I don't have special knowledge of vimscript, but it appears to me that this is the expected behavior. I don't believe that the function stores the information about the dictionary it lives in; also, if it lives in many dictionaries, I would assume it's still just one function reference and not copies. In Javascript for example it works exactly this way.
These also do what I expect:
function! Callback_Test2() function! GetColor() dict return self.color endfunction let reddog = { 'color': 'red', 'get_color': function('GetColor') } let bluedog = { 'color': 'blue', 'get_color': function('GetColor') } echomsg "First dog is " . reddog.get_color() " --- prints blue, not red. echomsg "Second dog is " . bluedog.get_color() endfunction
function! Callback_Test3() let reddog = { 'color': 'red' } function! reddog.get_color() return self.color endfunction let bluedog = { 'color': 'blue', 'get_color': reddog.get_color } echomsg "First dog is " . reddog.get_color() " --- and do does this: echomsg "Second dog is " . bluedog.get_color() endfunction
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub
--
--
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php
---
You received this message because you are subscribed to the Google Groups "vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to vim_dev+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
To actually do this one needs to use `get(dict, 'func')`
Does not this imply that 'dict' is available at the point when 'func' is about to be invoked?
if I was to extend my original example and instead of calling call obj2.func2()
directly we had something like this:
" obj2.func2 will be called when channel is closed call job_start("ping -c 3 google.com", {"close_cb": obj2.func2})
here is a full version
function! Callback_Test() let obj1 = {"test": 'test1', "other_test": "other_test"} function obj1.func1() " next line will use value from wrong scope echomsg "inside obj1.func1: test=".self.test " next line will result in: 'Key not present in Dictionary: other_test' echomsg "inside obj1.func1: other_test=".self.other_test endfunction let obj2 = {"test": 'test2', 'callback': obj1.func1} function obj2.func2(...) echomsg "inside obj2.func2: ".self.test call self.callback() endfunction "call obj2.func2()
call job_start("ping -c 3 google.com", {"close_cb": obj2.func2}) endfunction
In this instance - is there a way to ensure that when obj1.func1 is finally called we have access to its obj1 'dict' in order to be able to do get(obj1, 'func1')
—
You are receiving this because you commented.
—
You are receiving this because you commented.
Reply to this email directly or view it on GitHub
--
Am I understanding correctly, that we can close this issue?
—
You are receiving this because you commented.
Not sure. Depends on whether dict.my_partial
was intentionally meant to create a new partial with dict
or it is a bug (I personally think this was intentional; was this already discussed in the mailing list?).
—
You are receiving this because you commented.
Am I understanding correctly, that we can close this issue?
Can not say that I am satisfied with the outcome because nested callbacks (preserving scope of partial) appear to be pretty convoluted. I would expect that the more plugin developers take advantage of job_start
the more questions like this will arise.
However, based on the explanations above it would appear that this is expected behaviour. So I am going to close this issue.
Thank you everyone for your input.
—
You are receiving this because you commented.
—
You are receiving this because you commented.
So what would you expect? That assigning a function to a dict creates a
partial, but assigning a partial to a dict stores the partial unchanged?
Or only when it already has an associated dictionary?This gets confusing very quickly. We could drop automatically creating
a partial on assignment, but that increases the difference between a
partial and a funcref.Perhaps the best solution is just to document the current behavior.
handle_subscript
) if value at index is a funcref or a partial. If assignment created partial I would not be able to resolve the issue by suggesting to use get(dict, 'callback')
.
—
You are receiving this because you commented.
So what would you expect?
Thank you for asking.
Let me preface this by confirming that based on the explanations above I now realise that my expectations are not compatible with current vim script behaviour.
I had hopped that self
inside dict
function works same way as this
inside object method in many OO languages.
i.e. every time I pass and then call obj.funct1
that funct1
would have access to the scope of the original obj
(dict) via self
.
What happens now is (as my example demonstrates) self
actually gets bound to the scope of the last dict
in which funct1
was invoked, hence (in my original example)
echomsg "inside obj1.func1: test=".self.test
returns
inside obj1.func1: test=test2
instead of (what I expected)
inside obj1.func1: test=test1
This behaviour (although correct from vim design point of view, as I now understand) makes it quite difficult to program a chain of nested callbacks.
Before I began exploring vim's new (and awesome) job_start
& ch_open/ch_sendraw
functionality this was a non issue, because all calls in my vim scripts were blocking and there was no real need in callbacks.
Now however I am having to resort to a very dirty hack - every time (before I pass obj1.funcX
as a callback to another obj2.funcY
) I copy data which I want to preserve from obj1
dict to obj2
dict.
This way when I get to the bottom of my callback chain final objX
dict contains data belonging to all dict-s in the callback stack.
When callback stack is rolled back up each objX.funcX
can find its data via self
. This is prone to key collisions, generally inconvenient and potentially insecure, but that's the best way I could come up with for now.
I hope this makes sense.
—
You are receiving this because you commented.
—
You are receiving this because you commented.
So, one option is that when one uses:
dict.member = SomeFunctionVar
we do not automatically bind the function that SomeFunctionVar refers to
to "dict", but keep whatever it was bound to (including nothing).
? This is the current behaviour, SomeFunctionVar is kept whatever it was bound to (including nothing) when it is being assigned. It is handle_subscript
that creates binding.
Current variant is backwards compatible: in Vim without partials what @neowit is seeing is exactly what is expected because there no partials are created. If you care only exactly about callbacks, then, assuming that handle_subscript
will handle dict.func()
the old way (partial is created only once all subscripts were handled, so it is different from (dict.func)()
), the solution may be “create new partial if funcref/partial at given index has dict attached, otherwise leave this as-is”, but this will make Vim show strange differences between dict.func()
(call(dict.func, dict.func.args + [], dict)
*) and (dict.func)
(call(dict.func, dict.func.args + [], dict.func.self)
*) if dict.func
is a partial with dictionary attached which is not dict
. In any case any solution that will not break plugins will exhibit the same behaviour as expressed in the first message.
* pseudocode: assumes that partials/funcrefs support indexing and two indexes may be used args
to get a list of arguments or an empty list and self
to get a self dictionary or zero, and also assumes that call(name, args, 0)
is like call(name, args)
. BTW, I think these are good ideas to actually implement.
—
You are receiving this because you commented.
—
You are receiving this because you commented.
From a functionality perspective, there should be a way to take the
function from dict1.f1 (which is bound to dict2) and apply it to dict1
Perhaps an attempt to re-bind a (already bound) function should be explicit.
e.g.
let dict1 = {"test": "dict1"} function dict1.f1() { return self.test " returns 'dict1' } " 1) - explicit binding (scope change) using function(func, dict) let dict2 = {"test": "dict2"} let dict2.f2 = function(dict1.f1, dict2) " explicit binding to dict2 call dict2.f2() " returns 'dict2' because dict2.f2 is explicitly bound to 'dict2' " 2) - desire to change scope is not expressed explicitly let dict2.f2 = function(dict1.f1) call dict2.f2() " returns 'dict1', i.e. scope is preserved
if above does not work from backwards compatibility point of view then perhaps function()
needs another (optional) parameter used to explicitly specify if scope must be preserved or can be changed. If such parameter is not specified then keep current behaviour.
—
You are receiving this because you commented.
—
You are receiving this because you commented.
When specifying a function like this, it's always bound to the
dictionary it's specified for.
Initially it is, but as soon as you assign it as a value in another dictionary the original binding is lost.
function! Callback_Test() let obj1 = {"test": 'test1'
}
function obj1.func1() " next line will use value from wrong scope echomsg "inside obj1.func1: test=".self.
test endfunction let obj2 = {"test": 'test2', 'callback': function(obj1.func1, obj1)}
function obj2.func2(...) echomsg "inside obj2.func2: ".self.test call self.callback() endfunction
call job_start("ping -c 3 google.com", {"close_cb": obj2.func2}) endfunction
results in the same outcome as in my very first message in this thread
inside obj2.func2: test2
inside obj1.func1: test=test2
Last line should have printed ...test=test1
but it prints ...test=test2
—
You are receiving this because you commented.
—
You are receiving this because you commented.
There are only two ways to get a partial with bound dictionary: subscripting a dictionary or explicitly using function().
Excuse my ignorance, but does not this:
let obj2 = {"test": 'test2', 'callback': function(obj1.func1, obj1)}
explicitly use function()
to bind obj1.func1
to obj1
dict ?
If it does then why (when I later call self.callback()
which (in theory) should invoke obj1.func1
bound to obj1
) I get test=test2
instead of test=test1
?
Is there another way to call self.callback()
(from my last example) such as to ensure that whatever I previously saved to obj2.callback
has not been altered, and call self.callback()
would in fact resolve to func1()
with self
linked to obj1
instead of obj2
as it happens now?
—
You are receiving this because you commented.
Is there another way to call self.callback() (from my last example) such as to ensure that whatever I previously saved to obj2.callback has not been altered, and call self.callback() would in fact resolve to func1() with self linked to obj1 instead of obj2 as it happens now?
That was in the very first message: use get()
.
If it does then why (when I later call self.callback() which (in theory) should invoke obj1.func1 bound to obj1) I get test=test2 instead of test=test1 ?
That was explained in my messages. A few times, if I am not mistaking.
—
You are receiving this because you commented.
That was in the very first my message: use get().
You mean get(dict, 'func')
, e.g. get(obj1, 'func1')
?
Not sure how this helps in a situation with real callbacks - where method which invokes a callback has no access/knowledge of the relevant dict
and only has a funcref
of the method to call.
I do realise that in my simplistic example instead of doing call self.callback()
I could just call obj1.func1()
, but in the real world scenario obj2.func2()
may not have access/visibility of obj1
dict, so get(obj1, 'func1')
does not look like a viable option.
I am sure I misunderstood you because get(dict, 'func')
looks like an obvious no go.
Please can you point out where my train of thought went wrong?
—
You are receiving this because you commented.
@neowit In your simplistic example you can use call call(get(self, 'callback'), [])
(using call()
because you can’t write call get(self, 'callback')()
, in e.g. let
that would be fine). get()
does not create new partial, it gets exactly what is there in a dictionary and thus it does help in your situation.
—
You are receiving this because you commented.
But to use this you must know that callback in dictionary is a partial. In less simple example that may need to be
python import vim if pyeval('vim.bindeval("self")["callback"].self is None') return self.callback() else return get(self, 'callback')() endif
(there currently is no way to get self
dictionary presense out of the partial in VimL, except for using string()
which may throw an error; and exactly no way to get self dictionary itself, one of the latest updates to Python bindings may do this).
—
You are receiving this because you commented.
—
Since the normal way to call the function is by using dict1.f1() or
dict1'f1' the function is bound to dict1. Where that is implemented
doesn't really matter for the user.
It only matters when using get(dict1, 'f1'), but that's unusual. I
don't think we want to force using get() to avoid a partial bound to one
dict to be rebound to another dict.
—
Since the normal way to call the function is by using dict1.f1() or
dict1'f1' the function is bound to dict1. Where that is implemented
doesn't really matter for the user.
It does. When you e.g. copy()
/deepcopy()
/extend()
dictionary funcref is copied as funcref and partial is copied as partial, so new dictionary will not receive a bound partial. Also
It only matters when using get(dict1, 'f1'), but that's unusual. I
don't think we want to force using get() to avoid a partial bound to one
dict to be rebound to another dict.
is not the only way to get data from dictionary as-is. In python when using bindeval()
no automatic binding ever happens.
I tend to think that when we have a partial that is bound to a dict,
invoking it from another dict should not rebind it. We'll have to see
if that causes more confusion than it solves though.
I can immediately say that this will break at least https://github.com/Bashka/vim_lib/blob/ca50aed17f51d76ad84c2a8fc2d2112f3477b40e/autoload/vim_lib/base/Object.vim#L27 (it is not the only place which will break in this plugin). I guess this may be the case for other plugins that use objects, just this is the one code of which I most recently viewed. If you want to implement something like this then you need to remove new code from handle_subscript
which creates partials automatically.
Basically your suggestion breaks every time somebody writes something like
let s:Parent = {} function s:Parent.overridden_function() dict let self.num_calls = get(self, 'num_calls', 0) + 1 return self.num_calls endfunction let s:Class = copy(s:Parent) let s:Class._overridden_function = s:Class.overridden_function function! s:Class.overridden_function() dict call self._overridden_function() return self.num_calls * 2 endfunction let s:instance = copy(s:Class) call self.overridden_function()
: s:instance._overridden_function
will be bound to s:Class
, but it is expected and should be bound to s:instance
.
—
Thus it should do the same thing.
I meant vim.bindeval('self')['callback']
, not vim.bindeval('self.callback')
. This has no reason to do the same thing.
Looks like that was written before partials existed, thus it won't
break, since we would only change how partials behave. If you think it
does break, please give an example (I can't read Russian comments :-).
No. After partials were added dict.func
behaviour changed. If you add suggested change (not rebounding partials with still existing bind-on-index) this will break because self.expand
(and others) in that function should be bound to l:child
. Comments are not needed to understand that (in fact, I did not read them).
This doesn't work, there is no "self" here. Did you mean:
call s:instance.overridden_function()
Yes, this is what I meant.
—
This should not break, the behavior must be backwards compatible. We
need to make a difference between using function() to create a partial
and passing on a function reference. This gets a bit tricky internally,
but if it's explainable to the users it can work.
It is explainable to user, but I see it more tricky to explain then to understand. Basically I see it as a same flag “this partial should (not) be rebound” which was already suggested (and rejected) earlier in this issue. Just implicit flag which I think is not the good idea.
If that gets too complicated, we could have a simple way to get a
partial from a dict without having it rebound to that dict. The get()
way still looks a bit ugly.
With current syntax I can suggest one of the following ways:
dict.func
creates a partial, dict['func']
does not.get()
.norebind call self.callback()
.Well, that's after converting to a Python type, so it will work
differently. I don't think anybody expect Python types to work the same
way as Vim types.
That Python type is a mere proxy to a VimL type. So somebody may expect this.
In your simplistic example you can use
call call(get(self, 'callback'), [])
I am happy to report that call call(get(self, 'callback'), [])
suggested by @ZyX-I seems to be doing exactly what I tried to achieve in the first place.
Given this code:
function! Callback_Test() let obj1 = {"test": 'test1'} function obj1.func1
() echomsg "obj1.test=".self.test endfunction call s:step2(obj1.func1) endfunction function! s:step2(callback) let obj2 = {"test": 'test2', 'callback': a:callback} function obj2.internalCallback() echomsg "obj2.test=".self.test call call(get(self, 'callback'), []) endfunction call s:step3(obj2.internalCallback) endfunction function! s:step3(callback) let obj3 = {"test": 'test3', 'callback': a:callback} function obj3.internalCallback(...) echomsg "obj3.test=".self.test call call(get(self, 'callback'), []) endfunction call job_start("ping -c 1 google.com", {"close_cb": obj3.internalCallback}) endfunction
with the above code if I run call Callback_Test()
the end result looks like so
obj3.test=test3
obj2.test=test2
obj1.test=test1
i.e. scope/dict of each function is preserved.
IMHO the Ideal solution would be one where - when we have a partial that is bound to a dict, invoking it from another dict would not rebind it.
But in the absence of that call call(get(self, 'callback'), [])
does the job. I wonder how many people would be able to guess this though. Perhaps we need to add some examples in the documentation? I would be happy to send a pull request.
—
You are receiving this because you commented.
Reply to this email directly or view it on GitHub
—
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub