[Python-ideas] Jump to function as an an alternative to call function

Steven D'Aprano steve at pearwood.info
Wed Aug 15 21:20:17 EDT 2018


On Wed, Aug 15, 2018 at 01:52:27PM -0500, Jacob Solinsky wrote:

> -Jumping to a function as opposed to calling a function
> 
> When a function is jumped to, it inherits the variables in the 
> caller’s local namespace and is free to modify them or add new local 
> variables, unlike a normal function call, wherein the caller’s 
> namespace is inaccesible.

I think that the standard term for what you want is *dynamic scoping*.

http://wiki.c2.com/?DynamicScoping

http://leafo.net/guides/dynamic-scoping-in-lua.html

The most common language today that uses dynamic scoping is probably 
Emacs Lisp. Although dynamic scoping is very powerful, it is also harder 
to implement correctly (so I believe) and harder to reason about. It is 
generally considered that dynamic scoping is equivalent to the exclusive 
use of global variables (all functions see the same variables, no matter 
where they are defined), so the use of dynamic scoping gives up the 
benefits of local variables.

(See also "Global variables considered harmful".)


> At present, the only way I know of to 
> accomplish this is to bundle all variables in the caller method’s 
> namespace as properties of the method’s instance and have the callee 
> method modify those properties.

This would generally be considered a severe violation of the Law of 
Demeter, one of the most important principles for reducing coupling 
between software components:

https://www2.ccs.neu.edu/research/demeter/demeter-method/LawOfDemeter/LawOfDemeter.htm

http://www.ccs.neu.edu/home/lieber/LoD.html

but also one of the most misunderstood. (Anyone who tells you that the 
LoD is "never use more than one dot" is wrong.)

As far as I am concerned, reducing coupling between software components 
is the single most important design principle there is. But what do we 
do when the problem you are trying to solve inherently has a lot of 
coupling between data components? I think that computer science doesn't 
have a lot to say about such hard problems, apart from "avoid them".


> Though it is easier to read code 
> written this way, it resulted in a great deal of redundancy in the 
> code I was writing. The task I was attempting to accomplish was 
> approximately this:

What you quoted was not a description of the task, but a concrete 
implementation of one possible solution to the task. You are describing 
*how* you want to do it, but you haven't described what *it* is.

This is not the place for it, but generally you should describe the 
functional requirements, not the implementation. You have Nouns, and 
Verbs, but what do you want to do with them? What is the high-level 
behaviour? If you focus on implementation too early, you may blind 
yourself to simpler solutions that accomplish the same end using a 
completely different means.

A light-hearted example:

"How do I turn the temperature of my refridgerator up? I need to boil 
some water, but the temperature control of the fridge only goes up to 
about 10°C (50°F)."

"Why don't you use the stove or microwave oven?"

"Because... that is to say... I... okay."


Since I don't understand your functional requirements, I don't know 
whether you are dealing with a situation which inherently requires high 
coupling, or if you are missing a solution which would avoid the need 
for global variables or dynamic scoping.


[...]
> Each subclass of Morpheme has its own slightly different mutate 
> method. Some subclasses of Morpheme needed to access and manipulate a 
> great deal of information about the verb and their surroundings,

This suggests to me that a solution might be to generate and pass around 
some sort of *context object* for each word you operate on. The obvious 
problems with this include:

- you have to generate that context object for every word, whether
  it will use it or not;

- in principle, the amount of context needed could be unbounded; 
  one word may only need information from the previous word, 
  while another word in a long run-on sentence may need information
  from half a page back.


> while 
> other subclasses’ mutate methods differed little from the default. 
> Most of the variables manipulated by the mutate method are not used by 
> any other methods of class Morpheme and thus should not be made 
> properties of the attached instance. What I wish I were able to do is 
> write many little methods that modify the same set of local variables 
> and compose them together into a complete mutate method. 

Closures are the exact equivalent of classes. If you have a solution 
(however clumsy) that works with classes, you should be able to re-write 
it in terms of closures. Whether that solution is less clumsy remains to 
be seen!

So you can have something like this:

    def environment():
        a = 1
        b = 2
        # define some closures
        def change1():
            nonlocal a
            a += 1
        def change2():
            nonlocal a, b
            a += 2
            b += 3
       def inspect():
            nonlocal a, b
            print(a, b)
       return (change1, change2, inspect)


With those three closures, I can modify and inspect the variables 
defined by environment.

Whether this technique simplifies your problem, I don't know. But if it 
is, it is surely easier than (1) convincing the core devs to add a new 
function call rule to Python, and (2) waiting for it to be implemented 
and released (which cannot be before version 3.8).

(And yes, I agree that both the class-based and closure-based solution 
requires a lot of boilerplate, which is a bad thing.)


-- 
Steve


More information about the Python-ideas mailing list