On Sunday, August 3, 2014 1:29:19 PM UTC-5, Edward K. Ream wrote:

>> ...keep revising code until it *obviously* is the simplest code possible.

>To make this scheme work, all key handlers must end in an acceptance 
method: vc.accept, vc.delegate or vc.ignore.

> As a result, [the new] key handlers [will] know nothing about state 
handlers and vice versa!

As discussed in another thread, rev ebacdc1... puts this scheme into 
practice.

Here, I'll discuss these changes in detail, including complications that I 
discovered yesterday and further simplifications that I'll make today.

1. In general, ending each command handler with an acceptance method has 
been a great success.  Acceptance methods clearly indicate what should 
happen when a command handler ends.

Acceptance handlers hide the existence of the vc.next_func and 
vc.return_value ivars from all command handlers.  This makes each command 
handler much cleaner in appearance. Furthermore, it is now possible to 
tweak  acceptance methods without changing command handlers in any way.

In other words, even though the acceptance methods follow the principle, 
"explicit is better than implicit", they *also* hide implementation details 
in a most useful way.  It's the best of both "implicit" and "explicit".

2. Besides the **direct** acceptance methods, vc.accept, vc.delegate or 
vc.ignore, vc.done & vc.quit, command handlers can also end with 
**indirect** acceptance methods: vc.begin_insert_mode, vc.begin_motion, 
vc.end_insert_mode, and vc.vim_digits.  Indirect acceptance methods 
(eventually!) call direct acceptance methods.

**Important**: command handlers can use different acceptance methods 
(direct or indirect) in different branches of their code, provided that all 
branches end in a call to exactly one acceptance method.  In practice, 
checking this requirement is easy.  Furthermore, the checking code in 
vc.do_state will warn if this requirement has not been met.

3. I did get one surprise: vc.do_inner_motion can't just call 
vc.do_state(motion_dispatch_d,'motion') because vc.do_inner_motion must 
call the after-motion callback, vc.motion_func.  No big deal, except...

4. At present, vc.done calls vc.reinit_ivars, and that caused problems for 
vc.do_inner_motion because vc.reinit_ivars wiped out the ivars needed when 
calling the motion callback, including vc.motion_func itself!

The temporary hack was simply to save/restore the needed ivars in 
vc.do_inner_motion.  Happily, this morning I saw a cleaner, more explicit 
way to init ivars if and when needed. This will simplify the init code, and 
more importantly, clarify exactly what is happening and why.

To recap: the problem is that vc.reinit_ivars clears too many ivars.  
Furthermore, the dot ivars are handled as special cases.  The solution is 
to define the following methods:

- vc.init_dot_vars()
- vc.init_motion_vars()
- vc.init_state_vars()
- vc.init_persistent_vars()

Doh!  We have now clearly grouped *all* ivars into *disjoint* classes.  We 
can now define the following:

def init_all_ivars(vc):
    '''Init all vim-mode ivars.  Called only from the ctor.'''
    vc.init_dot_vars()
    vc.init_motion_vars()
    vc.init_state_vars()
    vc.init_persistent_vars()

def init_ivars_after_quit(vc):
    '''Init vim-mode ivars after the keyboard-quit command.'''
    vc.init_motion_vars()
    vc.init_state_vars()

def init_ivars_after_done(vc):
    '''Init vim-mode ivars when a command completes.'''
    if not vc.in_motion:
        vc.init_motion_ivars()
        vc.init_state_ivars()
        
**Important**: it would be very bad design to call vc.init_ivars_after_quit 
inside vc.init_ivars_after_done just because (for now!) the effect would be 
the same.  Glen Meyers calls reusing common code "code-level" binding.  It 
can cause all kinds of problems.

Instead, we want what Meyers calls "functional" binding.  The (proper) code 
shown above makes vc.init_after_quit and vc.init_after_done independent of 
each other, regardless of what happens in future.

So this is good.  We have now created a higher-level grouping of ivars that 
makes clear what is intended.  This scheme is simple and flexible: new 
(disjoint!) groups of ivars can be created at any time. The new scheme is 
yet another example of using abstraction as a design and coding tool.

===== Conclusions

Leo's vim code is now spectacularly different, both visually and 
functionally, from the real vim's code. Wherever possible, Leo uses methods 
to hide the blah, blah, blah of implementation details.  Imo, the result is 
*far* better design and code than vim's.

Imo, if vim were recreated today from scratch, it would be reasonable to 
use Leo's vim-mode code as a starting point.

Edward

P.S.  To emphasize what I said before in another post: Python encourages 
simplifications that are, in practice, denied to C programmers.  Leo's 
dispatch dicts for vim mode are an example.  Creating such dicts is 
difficult in plain C, and non-trivial in C++.  So C programmers don't 
create those dicts and miss all the simplifying possibilities afforded by 
them.  There are many other ways that Python aids simplification while C 
inhibits it.

EKR

-- 
You received this message because you are subscribed to the Google Groups 
"leo-editor" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.

Reply via email to