Request for comments: timers

352 views
Skip to first unread message

Bram Moolenaar

unread,
Mar 13, 2016, 11:07:07 AM3/13/16
to vim...@googlegroups.com

I have a note about adding a way that a callback can be invoked at a
certain time. I would like to know how this is going to be used, what
the requirements for this feature are.

I imagine there are two kinds of timers:

1. One-time: After N msec the callback is invoked.
2. Repating: invoke the callback every N msec

For both this will happen while waiting for a character, thus the actual
wait time will be longer if the user is typing.

Example:

let timer = timer_add({time}, {callback} [, {repeats} [, {cookie}]])
call timer_stop(timer)

func MyTimerCallback(timer, cookie)

Would that satisfy the need?

I think it would be good that if the callback causes an error the timer
is stopped.

--
Q. What happens to programmers when they die?
A: MS-Windows programmers are reinstalled. C++ programmers become undefined,
anyone who refers to them will die as well. Java programmers reincarnate
after being garbage collected, unless they are in permgen, in which case
they become zombies. Zimbu programmers leave a stack trace that tells us
exactly where they died and how they got there.

/// Bram Moolenaar -- Br...@Moolenaar.net -- http://www.Moolenaar.net \\\
/// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\
\\\ an exciting new programming language -- http://www.Zimbu.org ///
\\\ help me help AIDS victims -- http://ICCF-Holland.org ///

mattn

unread,
Mar 13, 2016, 11:41:24 AM3/13/16
to vim_dev
On Monday, March 14, 2016 at 12:07:07 AM UTC+9, Bram Moolenaar wrote:
> I have a note about adding a way that a callback can be invoked at a
> certain time. I would like to know how this is going to be used, what
> the requirements for this feature are.
>
> I imagine there are two kinds of timers:
>
> 1. One-time: After N msec the callback is invoked.
> 2. Repating: invoke the callback every N msec
>
> For both this will happen while waiting for a character, thus the actual
> wait time will be longer if the user is typing.
>
> Example:
>
> let timer = timer_add({time}, {callback} [, {repeats} [, {cookie}]])
> call timer_stop(timer)
>
> func MyTimerCallback(timer, cookie)
>
> Would that satisfy the need?

function! MyTimerCallback(timer, cookie)
echo getchar()
endfunction


let timer = timer_add({time}, {callback} [, {repeats} [, {cookie}]])

How will this work?

Nikolay Aleksandrovich Pavlov

unread,
Mar 13, 2016, 12:31:50 PM3/13/16
to vim_dev
2016-03-13 18:06 GMT+03:00 Bram Moolenaar <Br...@moolenaar.net>:
>
> I have a note about adding a way that a callback can be invoked at a
> certain time. I would like to know how this is going to be used, what
> the requirements for this feature are.
>
> I imagine there are two kinds of timers:
>
> 1. One-time: After N msec the callback is invoked.
> 2. Repating: invoke the callback every N msec
>
> For both this will happen while waiting for a character, thus the actual
> wait time will be longer if the user is typing.
>
> Example:
>
> let timer = timer_add({time}, {callback} [, {repeats} [, {cookie}]])
> call timer_stop(timer)
>
> func MyTimerCallback(timer, cookie)
>
> Would that satisfy the need?
>
> I think it would be good that if the callback causes an error the timer
> is stopped.

I would say that dictionary is better then four arguments:

1. It enables feature used by Neovim for callbacks: dictionary that
defined timer is passed as a `self` dictionary to a dictionary
function, while in your variant dictionary functions cannot be used at
all.
2. This allows adding more options in the future. E.g. alter error
handling, silence function or add a timeout after which callback is
interrupted.
3. Dictionary may serve both as `timer` and as a `cookie`.

E.g.

let def = {
\ 'interval': Number|Float (seconds),
\ 'callback': String|Funcref,
\ 'repeats': Number (optional),
\ 'Cookie': … (keys matching `^[a-z]\+$` are reserved for Vim)}
call timer_add(def)
call timer_stop(def)

function Callback() dict
endfunction

>
> --
> Q. What happens to programmers when they die?
> A: MS-Windows programmers are reinstalled. C++ programmers become undefined,
> anyone who refers to them will die as well. Java programmers reincarnate
> after being garbage collected, unless they are in permgen, in which case
> they become zombies. Zimbu programmers leave a stack trace that tells us
> exactly where they died and how they got there.
>
> /// Bram Moolenaar -- Br...@Moolenaar.net -- http://www.Moolenaar.net \\\
> /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\
> \\\ an exciting new programming language -- http://www.Zimbu.org ///
> \\\ help me help AIDS victims -- http://ICCF-Holland.org ///
>
> --
> --
> 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.

Bram Moolenaar

unread,
Mar 13, 2016, 2:36:54 PM3/13/16
to Nikolay Aleksandrovich Pavlov, vim_dev
That's very different from how existing functions work.

The "cookie" can be a dictionary, thus one can use that. Passing the
function argument back in to the callback looks weird. What we would
actually want is a closure. Then the cookie is not needed.

We could make this generic:
let closure = function('Callback', arg1, arg2)
Would invoke:
func Callback(arg1, arg2, other-args)

We might need more options later, that's true. But the time and
callback are always needed. Thus I would prefer:

let timer = timer_start({time}, {callback}, {options})


--
hundred-and-one symptoms of being an internet addict:
38. You wake up at 3 a.m. to go to the bathroom and stop and check your e-mail
on the way back to bed.

Nikolay Aleksandrovich Pavlov

unread,
Mar 13, 2016, 3:11:15 PM3/13/16
to Bram Moolenaar, vim_dev
Argument being passed is a dictionary containing function, not the
function. And this does not look weird at all, every OOP library
written in VimL I saw does the same thing.

>
> We could make this generic:
> let closure = function('Callback', arg1, arg2)
> Would invoke:
> func Callback(arg1, arg2, other-args)

This is currying (partial application), not closure. Closure is

function Outer()
let l:test = 1
let l:d = {}
function d.inner()
return l:test
endfunction
return d.inner
endfunction

let Closure = Outer()
echo Closure()

>
> We might need more options later, that's true. But the time and
> callback are always needed. Thus I would prefer:
>
> let timer = timer_start({time}, {callback}, {options})

Do you mean that `options` here will be `self`? It is fine as long as
dictionary functions can be used.

Bram Moolenaar

unread,
Mar 13, 2016, 4:08:25 PM3/13/16
to Nikolay Aleksandrovich Pavlov, vim_dev
What is weird is that a function is needed and you pass a dictionary
containing a function. The only reason Vim users have been doing this
is that there was no other way. It's not that this is a good way to do
it.

In this case the "callback" item of the dictionary defines the function.
Why not another? What if there are there several functions? Anyway,
I think it's weird.

Another objection to using the dictionary both for options and for
passing to the callback is that it's not clear what happens if some of
the option values is changed. Does that mean the changed value will be
used?

I think it's better to separate options from any dictionary related to
the callback. And there are many other places callbacks are used where
binding it with arguments or a dictionary is useful.

> > We could make this generic:
> > let closure = function('Callback', arg1, arg2)
> > Would invoke:
> > func Callback(arg1, arg2, other-args)
>
> This is currying (partial application), not closure. Closure is
>
> function Outer()
> let l:test = 1
> let l:d = {}
> function d.inner()
> return l:test
> endfunction
> return d.inner
> endfunction
>
> let Closure = Outer()
> echo Closure()

Yeah, I know it's not a real closure. But it's the best term that I
though of. Never heard of currying. Looking it up finds something
else... Perhaps "partial" is a better fit. Not sure how many people
know what that means.

> > We might need more options later, that's true. But the time and
> > callback are always needed. Thus I would prefer:
> >
> > let timer = timer_start({time}, {callback}, {options})
>
> Do you mean that `options` here will be `self`? It is fine as long as
> dictionary functions can be used.

No, they are options. If a dictionary function is to be used we need a
way that works for any callback. Perhaps:

let callback = function('Callback', dict, arg1, arg2)

Nicer would be if we can do:

let callback = function(dict.Callback, arg1, arg2)

--
Micro$oft: where do you want to go today?
Linux: where do you want to go tomorrow?
FreeBSD: are you guys coming, or what?

Nikolay Aleksandrovich Pavlov

unread,
Mar 13, 2016, 5:45:56 PM3/13/16
to Bram Moolenaar, vim_dev
Why somebody may think that there should be another item? Persons that
do not understand that {callback} should be attached to `'callback'`
key will as well not understand that {repeat} should be attached to
`'repeat'` key.

Also I did not see any problems with developers failing to understand
that ch_sendexpr() or job_start() should have handler attached to
`'callback'` key. This argument is nonsense to hear, I am not the one
that suggested to have {options} with `'callback'`, except that there
it is optional.

Several functions are absolutely fine, one may simply pass a “class
instance” as a callback.

// As a side note: why did you choose callback names so that they
cannot be called using dot-subscript notation (i.e.
`options.out_cb()`)? It is not uncommon when callback function is
reused for purposes other then being a callback, though I guess nobody
currently cares because one cannot use dictionary functions for
callbacks there, so writing `function options.out_cb(...)` is not
possible.

>
> Another objection to using the dictionary both for options and for
> passing to the callback is that it's not clear what happens if some of
> the option values is changed. Does that mean the changed value will be
> used?

This should be documented. In case of a repeating execution in some
cases it may actually be convenient to adjust interval by assigning
new value to `'interval'` key. And restarting is in any case easier if
everything is kept in one container:

```
let timer = {'interval': 1, 'Cookie': []}

function timer.callback() dict
call add(self.Cookie, 'test')
endfunction

function timer.enable() dict
call timer_add(self) " Should do nothing if timer was enabled
because dictionary is an identifier
endfunction

function timer.disable() dict
call timer_del(self)
endfunction
```

vs

```
function Callback(cookie)
call add(a:cookie, 'test')
endfunction

let g:timer = v:null
let g:cookie = []

function EnableTimer()
if g:timer is v:null " Needed because new timer_add will simply
make function run twice, callback is not an identifier
let g:timer = timer_add(1, function('Callback', [g:cookie]))
endif
endfunction

function DisableTimer()
call timer_del(g:timer)
let g:timer = v:null
endfunction
```

Using dictionary functions does not make much difference in the second case.

>
> I think it's better to separate options from any dictionary related to
> the callback. And there are many other places callbacks are used where
> binding it with arguments or a dictionary is useful.

Partial application is orthogonal to timer_* API. It is useful even
without any callbacks involved.
I would suggest function(name[, args[, self]]) instead where `args` is
a list. Otherwise you need to change `dict.Callback` expression to
produce “bound function”. It is not uncommon thing in OO languages,
but I do not remember this anywhere else.

E.g. in Python

s = 'test'
rep = s.replace
print(rep('e', ''))

will show `tst` because `s.replace` is a bound method (`print(rep)`
produces `<built-in method replace of str object at 0x7f0ad2ebac70>`
in Python 3 where 0x7f0ad2ebac70 is address of object to which `rep`
is bound).

Do not forget that bound functions need to hold references of the
object they are bound to.

Bram Moolenaar

unread,
Mar 13, 2016, 6:39:18 PM3/13/16
to Nikolay Aleksandrovich Pavlov, vim_dev
That is besides the point. The "callback" is just an argument to the
method, an optional argument. For that we use an options dict. We can
check if any of the items are not supported and give an error.

It is totally different when the dict is handled like an object, in this
case a timer object. That dict can then contain lots of items that are
not options. We can't check if some items are invalid.

To do it properly would require defining a timer object, returned by
timer_new(). Would then have timer_set_callback() or whatever to set
options before calling timer_start(). Adding other members to the dict
should probably not be allowed. But all this doesn't fit into how Vim
works.

> Also I did not see any problems with developers failing to understand
> that ch_sendexpr() or job_start() should have handler attached to
> `'callback'` key. This argument is nonsense to hear, I am not the one
> that suggested to have {options} with `'callback'`, except that there
> it is optional.
>
> Several functions are absolutely fine, one may simply pass a “class
> instance” as a callback.
>
> // As a side note: why did you choose callback names so that they
> cannot be called using dot-subscript notation (i.e.
> `options.out_cb()`)? It is not uncommon when callback function is
> reused for purposes other then being a callback, though I guess nobody
> currently cares because one cannot use dictionary functions for
> callbacks there, so writing `function options.out_cb(...)` is not
> possible.

Well, nobody said anything about that so far. I did notice when writing
tests, but since nobody made a remark I left the dash-names.

So we should probably rename them, change dashes to underscores.
It's a good idea to pass the arguments in a list to function(). And use
the third argument for the dict. Could even detect the second argument
to be a dict:
function(name)
function(name, dict)
function(name, arglist)
function(name, arglist, dict)
Can't really do this wrong.

The "bound function" won't add functionality, it's just shorter.
In a cryptic way, it's not directly clear what happens. I prefer
keeping it explicit, using function().

--
Everyone has a photographic memory. Some don't have film.

Nikolay Aleksandrovich Pavlov

unread,
Mar 14, 2016, 11:03:47 AM3/14/16
to Bram Moolenaar, vim_dev
Underscore names are not much useful until dictionary functions can be
used as callbacks (or it will be possible to create non-dictionary
anonymous functions using `function options.out_cb()` syntax). So
people did not complain.
Not more cryptic as current solution is:

let d = {}
function d.f() dict
endfunction
echo d.f() " Echoes 0
echo (d.f)() " E725: Calling dict function without Dictionary

This is surprising to any person coming from OO language, lua (which
also does not have “bound functions”) in the second case case (though
`(d:f)()` and not `(d.f)()` as it is not different from `d.f()`)
raises syntax error. But still I do not think bound functions are good
idea, this raises loads of questions like

let d = {}
function d.f() dict
return self is d
endfunction
let d2 = {'f': d.f}
echo d2.f()

: what should it print? If 1 then this is likely to break existing
plugins. And 0 is not what is expected from “bound function” call.
Reply all
Reply to author
Forward
0 new messages