0

Is there a way to determine if the return value of a function is needed, from inside that function?

Example:

drawPoint(1,2)

vs.

newPointId=drawPoint(1,2)

drawPoint makes an http request, so, the response from that request might be received from the server 'right away' or 'soon' or 'after a while' or never.

If the return value is not needed (the first case), the command could be 'fire-and-forget': just an immediate return with irrelevant value after the request is sent with zero timeout.

If the return value is needed (the second case), then the command should be 'blocking', at least for some timeout period. Various retry schemes and timeout values could be employed. (If no response is received in the desired timeframe, it could return False, and the rest of the code would need to be able to deal with that case; but there's an expectation that the variable 'newPointId' should be usable on the next line of code.)

def drawPoint(x,y):
  if [returnValueNeeded]:
    # do a blocking request, with retry scheme etc.
    # return its response or whatever part of its response is needed
  else:
    # do a non-blocking 'fire-and-forget' request with zero timeout
    # return with any value or no value
3
  • 1
    I don't understand the question, you need the return value for next code then you wait for it. Or else you don't. Whats the question? Commented Jul 11 at 4:33
  • 3
    This is absolutely an XY problem. You are asking the called code to know the intent of the calling code. Either the function needs to be evaluated or not. Why should the function be responsible for divining this decision? Or, more directly, what is the actual problem you are trying to solve? Commented Jul 11 at 5:39
  • 1
    Don't ever make the callee guess the intentions of the caller. Either pass in parameters that explicitly alter the behaviour of the callee, or write the callee in such a way that the caller can decide what to do with it; e.g. async functions, which the caller can either await or chuck into a fire-and-forget background task. Commented Jul 11 at 7:15

2 Answers 2

1

Preamble

Well, that is just for the challenge. Since, I agree, that looks like a XYProblem.

Plus, if it is just for convenience, to avoid having 2 functions, or, as in Torben's solution, an explicit parameter (like, if I understand correctly the intended usage async=True/False), you will probably understand that, even if you pushed the method I am about to show so far that it becomes reliable, that would be a way too heavy cost (both in development time, and in CPU time) to worth it.

All that negative preamble done, to do this kind of thing, you need the ability to inspect your own code. Which is theoretically possible in interpreted language. But very implementation dependent (implementation of the interpreter. CPython isn't the only python interpreter).

inspect module, for example, can inspect code. Can go upward in the call chain, and know what was the code that called a function.

Extremely wicked, shockingly evil and vile demo

import inspect

frames=[]

def drawPoint(x,y):
    context = inspect.stack(context=1)[1].code_context[0]
    if context.strip()[:9]=='drawPoint':
        print("return value unused for", x, y)
    else:
        print("returning", x+y)
        return x+y

drawPoint(1,0)

pt=drawPoint(10,20)

First call prints "return value unused" and returns nothing. Second returns x+y.

insect.stack gives me the current call stack. inspect.stack()[0] is the call to inspect.stack()itself.inspect.stack()[1]is the previous one in the call tree. That is the call todrawPoint. And inspect.stack()[1].code_context[0]is the line of code of that caller. That line is eitherdrawPoint(...)in the first call, andpt=drawPoint(...)` in the second.

So, what I do, is just inspecting a string containing some code, either drawPoint(1,0), pt=drawPoint(10,20), to try to deduce if return value is used. Which I do in a very naive way.

Static check limitations (among zillions)

Counter-point (way to make my naive way fail)

print("wait for it",
      drawPoint(15,25)
      )

displays "return value unused for 15 25". Because line of code is drawPoint(15,25).

And the opposite

print('hello'); drawPoint(15,25)

For those two, of course, I could inspect 3 lines instead of 1 (that is the context=1 parameters. And the reason why I need that [0] after code_context: because there can be more than 1 string in what it returns), and parse ; (easier said than done). And then we can build some other counter-example with those... It would be pretty much writing a python parser to try to understand what is done with result.

In reality, we should either access an abstract tree representation of the code, or the virtual machine opcode, rather than the text version of the code. I am lazy (since I know that the whole point of this "answer" is to prove that it is a bad idea). But either way, to do this seriously, it would take way more complicated hacks than what I do here.

What is "used"?

And then next question would be: what of

pt = drawPoint(10,20)
# then do absolutely nothing with pt. Even garbage collect it 
pt = None # just to ensure that after this point nobody use it

in such case, would you say that we use the result of the call or not? Up to what point would you want the optimization to go?

Point is, it is a fuzzy task anyway, since it is not well defined what is "result is used"; and it can be a very hard task to know it, whatever the definition of "used"

Dynamic analysis is needed

What of

def dosomething(callback):
    callback(5,6)

dosomething(drawPoint)

And even more convulted way?

The only reliable way to know for sure if the value is used, is to simulate the rest of the code (so run it, basically, to predict future) to see if the result is used.

So, for your goal, you may be satisfied with some heuristic-conservative static criteria (when in doubt, let's be synchronized and return a value. But when I can statically prove that the call isn't using the result, no need to wait). But that static proof would probably cost you more CPU time than just wait. And the effort to write something serious would cost you way more than adding some returnValueNeeded=False

tl;dr

There are tools to inspect stack and call tree, and, generally speaking the code and the interpreter state. They are implementation dependent. And even the simplest reliable static inspection would be hard to implement. Theoretically, as long as you accept a ternary answer ("yes, return value is used", "no it is not used", "can't be sure"), those tools could be a starting point. But the only way to do it would be, at each call, to simulate future code execution. Clearly, that kind of code analysis could be doing for some invariant proving, or, for extreme debug mode logging, or for compiler optimization, ... It is certainly the kind of thing you do just to have some comfy "no need to pass a returnValueNeeded" for your coders.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for the clear detailed explanation of why it's a bad idea. (you are definitely not lazy in that regard.) I wasn't familiar with what an XY question is, but, yeah I guess this is one! It's also a standalone question "can you tell if the return value is 'used'" but you spell out why there's no reliable way to do so and is vague. The core motivation for asking was to make it easier on the downstream coder, who might not know that they need to specify an argument to say whether the call should be blocking or not - especially for migrating code from a previous version that was always blo
1

Not as you wrote it down. However you can add an optional argument:

def drawPoint(x, y, returnValueNeeded=True):
    ...

Then the call side can pass this flag easily, while existing code keeps working.

I.e. drawPoint(x,y) would become drawPoint(x, y, False);

newPointId = drawPoint(x, y) would work as it is.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.