|
|
Subscribe / Log in / New account

From late-bound arguments to deferred computation, part 2

By Jake Edge
August 24, 2022

Discussion on PEP 671 ("Syntax for late-bound function argument defaults") has been going on—in fits and starts—since it was introduced last October. The idea is to provide a way to specify the default for a function argument that is evaluated in the scope of the function call, which will allow more concise, and visible, defaults. But there has been a persistent complaint that what the language needs is a more-general deferred computation feature; late-bound defaults would simply fall out as one specific user of the feature. The arrival of a proposal for deferred computation did not really accomplish that goal, however.

Last week, we looked at the discussion of late-bound argument defaults after it got rekindled in June. There are some developers who are interested in having a way to specify argument defaults that are evaluated at run time in the context of the call. For example:

    def fn(a, max=len(a)):
        ...
That function definition looks sensible at first glance, but len(a) is evaluated at function-definition time, so it does not refer to the argument a at all. PEP 671, authored by Chris Angelico, would add a new way to specify defaults that are evaluated using the context at the beginning of the function call (using "=>"), so the following would do what the example above looks like it is doing:
    def fn(a, max=>len(a)):
        ...

But throughout all of the discussions, deferred computation was repeatedly raised as a better way forward; it was mostly championed by Steven D'Aprano and David Mertz. The details of what that feature might look like were not ever described until fairly late in the game, which was annoying to several of the participants in the threads. Things got more heated than is usual in discussions on the python-ideas mailing list, but that heat finally dissipated after a deferred-computation proposal was made.

A proto-PEP

As Brendan Barnwell and Paul Moore noted, a counter-proposal, such as one for deferred computation, is not required in order to oppose PEP 671. But it is difficult—frustrating—to try to defend a proposal when the "opposition" is so amorphous; that frustration can clearly be seen in the discussions. On June 21, Mertz posted the first version of his proto-PEP for "Generalized deferred computation". It would use the soft keyword later to identify expressions that should only be evaluated when a value is actually required for a non-later expression:

    a = 5
    b = later a + 2        # b is a deferred object
    c = later a * b + b    # c is a deferred object
    print(c)               # c and b are evaluated
The result is 42, as expected. The PEP abstract notes that a deferred object can be thought of as a "thunk" or likened to an existing Python construct: "To a first approximation, a deferred object is a zero-argument lambda function that calls itself when referenced."

The proto-PEP, which has never progressed to the point of getting a non-placeholder number, mentions the delayed() interface from the Dask Python parallel-computation library as something of a model for the feature. However, a Dask delayed object is only evaluated when its compute() method is called, while Mertz's version would automatically evaluate deferred objects when their value is used in a non-deferred context.

The proposal also talked about having a way to determine whether an object was deferred without evaluating it. He suggested that type() would not evaluate a deferred object or, perhaps, an isdeferred() function would be added for that purpose. That puzzled Barnwell who noted that it violates a basic Python principle: arguments to functions are evaluated "before the function gets any say in the matter". Making type() or isdeferred() do something different would be strange.

Mertz agreed that was a problematic piece, but he would like to find a way to query an object without evaluating it. Carl Meyer suggested that PEP 690 ("Lazy Imports"), which he co-authored, might provide some help on that; in general, even though Mertz's proposal was borne out of the PEP 671 discussion, Meyer thought it is more closely aligned with the lazy imports feature. But Angelico pointed out that there is a fundamental question that was not answered in the deferred computation proposal:

What are the scoping rules for deferred objects? Do they access names where they are evaluated, or where they are defined? Consider:
def f(x):
    spam = 1
    print(x)

def g():
    spam = 2
    f(later spam)

g()
Does this print 1 or 2?

This is a fundamental and crucial point, and must be settled early. You cannot defer this. :)

Angelico is demonstrating the two different scopes here. If later spam is evaluated in the scope where it was defined, spam will be 2, but if it is evaluated in the scope where it is evaluated, it will be 1.

Meyer agreed that specifying the scoping was crucial and thought it obvious that deferred objects would use the names from the scope where they were defined. Mertz initially disagreed, but eventually came around to that view as well. What that means, though, is that the feature cannot support late-bound arguments as Angelico had been saying for some time. The example given in the proto-PEP is:

    def func(items=[], n=later len(items)):
	n  # Evaluate the Deferred and re-bind the name n
	items.append("Hello")
	print(n)

    func([1, 2, 3])  # prints: 3
Angelico pointed out that the lookup of items in that example would be done in the surrounding scope:
That's why PEP 671 has the distinction that the late-bound default is scoped within the function body, not its definition. Otherwise, this doesn't work.

No one has replied to Angelico, so it would seem that using the generalized mechanism for late-bound arguments is a non-starter. That does not necessarily mean that the deferred computation feature should be abandoned, but it would seem that whatever barrier it imposed for PEP 671 has disappeared.

More problems

Meanwhile, there are other problem spots for deferred computation, including further difficulties with the late-bound-arguments use case. Martin Di Paola noted that Dask, PySpark, the Django object-relational mapping (ORM) layer for databases, and other similar tools do much more under the covers than simply evaluate the deferred object. In each case, the framework can manipulate the abstract syntax tree (AST) to optimize the evaluation in some fashion. Moore was concerned that only having the automatic-evaluation mechanism would preclude these other use cases (and those that may arise) from using the proposed mechanism:

My concern is that we're unlikely to be able to justify *two* forms of "deferred expression" construct in Python, and your proposal, by requiring transparent evaluation on reference, would preclude any processing (such as optimisation, name injection, or other forms of AST manipulation) of the expression before evaluation.

I suspect that you consider evaluation-on-reference as an important feature of your proposal, but could you consider explicit evaluation as an alternative? Or at the very least address in the PEP the fact that this would close the door on future explicit evaluation models?

Another problem arose from a familiar source; Angelico pointed out another late-bound-argument use case that fails. Stephen J. Turnbull reframed the problem and suggested a possible way forward, though things got ever more tangled.

    def foo(cookiejar=defer []):
foo() produces a late bound empty list that will be used again the next time foo() is invoked.

Now, we could modify the defer syntax in function parameter default values to produce a deferred deferred object [...]

He guessed that might lead Angelico to reply with something akin to the "puke emoji", but instead Angelico poked holes in that idea as well:

def foo(cookiejar=defer []):
    cookiejar.append(1) # new list here
    cookiejar.append(2) # another new list???
So the only way around it would be to make the defer keyword somehow magical when used in a function signature, which kinda defeats the whole point about being able to reuse another mechanic to achieve this.

It was around that point where Mertz seemed to perhaps throw in the towel on the idea, at least in terms of supporting late-bound defaults using it. Overall, it would seem that a lot more thought and work will need to go into a deferred-computation proposal before it can really even get off the ground. It is a popular feature from other languages, especially functional languages, but there may not be a real fit for it in the Python language. As mentioned, the technique is already used in a variety of ways in the ecosystem—without any change to the language syntax.

In the two months since that flurry of discussion, things have been quiet. The barriers to pushing PEP 671 forward would seem to have lessened some, at minimum, though that may still not be enough to get it across the finish line. As noted, there are no huge wins for adding this syntax; there are already only slightly more verbose ways to manually create late-bound defaults.

But back in October, Guido van Rossum was pleased to see work happening on fixing that particular language wart; he is no longer the final arbiter, of course, but he still has plenty of influence within the core Python development community. In order to proceed, if Angelico is still wanting to do so, he will need a core developer to sponsor the PEP, since, surprisingly, he is not one. Even if the PEP were to fail, it might be worth pushing it a bit further if a sponsor can be found; that way, the next time the idea gets raised, much of the discussion can be short-circuited by referring to the PEP—in theory anyway.


Index entries for this article
PythonArguments
PythonDeferred computation
PythonPython Enhancement Proposals (PEP)/PEP 671


to post comments

From late-bound arguments to deferred computation, part 2

Posted Aug 25, 2022 1:53 UTC (Thu) by milesrout (subscriber, #126894) [Link] (14 responses)

This is so frustrating to watch unfold. The average Python programmer, in my opinion, wants one simple little feature: the ability to write something like "def foo(x=[])" or "def foo(x, l=len(x))" and have it work properly, the way it was intended, preferably indicated by a small but visually apparent syntactic change like an extra character in there somewhere (=>, or whatever). It is unnecessary to spend months and years discussing amorphous "generalised deferred computation".

This is exactly the same as people distracting from (x := 1) assignment expressions by making a never-ending stream of "oh but why can't we just generalise it to allow statements inside expressions?" suggestions. After all, it would be more elegant to allow such a thing. But it would not actually solve the original problem (because the main problem was allowing assignment in an expression without making (x = 1) into an assignment---getting an error when you mistype (x == 1) is a valuable feature of Python) and is a huge distraction. Same thing here: the proposals don't even solve the original problem and they're a huge distraction.

So now we get to watch the same thing unfold again, I guess? Every time a simple and valuable little feature is suggested to be added to Python, is this going to happen? Somehow the proposals for adding huge syntactic complexity to the language in the form of async/await and the constant type annotation changes are fine, but adding `=>` to clean up one of the BIGGEST hurdles in teaching the language is not? Like come on. Python is huge as a language for teaching programming. This point in particular is one of the most unintuitive aspects of one of the most intuitive programming languages. It sticks out like a sore thumb and the "solution" requires creating ugly sentinel objects and writing several extra lines of code. Just fix it already...

From late-bound arguments to deferred computation, part 2

Posted Aug 25, 2022 3:32 UTC (Thu) by k8to (guest, #15413) [Link] (6 responses)

Python used to be simple, and pretty good at maintaining comprehensibility. That's gone in practice and gone culturally.

From late-bound arguments to deferred computation, part 2

Posted Aug 25, 2022 6:57 UTC (Thu) by pwfxq (subscriber, #84695) [Link] (3 responses)

I think that could be said about many languages. They start simple and slowly add features until they become a Gordian Knot. We must be due a new language soon...

From late-bound arguments to deferred computation, part 2

Posted Aug 25, 2022 10:27 UTC (Thu) by k8to (guest, #15413) [Link] (2 responses)

Yes, certainly it's the overall trend, but python navigated like its first 10 years or so in a pretty healthy manner, which is relatively unusual.

From late-bound arguments to deferred computation, part 2

Posted Aug 26, 2022 20:52 UTC (Fri) by NYKevin (subscriber, #129325) [Link] (1 responses)

Eh, Python spent its first 10 years or so building out descriptors and modern classes (remember "classic" classes?), which were rather substantial overhauls of the language's syntax and semantics (especially when you consider metaclasses in their full generality). But nobody cared, because these things were relatively abstruse and could be quietly ignored if you just wanted to write a 10-line script, and if you were writing a larger program, the old way of doing things was obviously inferior in a number of ways.

From late-bound arguments to deferred computation, part 2

Posted Aug 28, 2022 0:47 UTC (Sun) by k8to (guest, #15413) [Link]

Yes, I remember this fully. While there were significant changes going on, the surface area to the usual programmer changed extremely little, and pretty much all my deployed python from 1.4 and 1.5 continued blissfully on without trouble. Some complexity existed in that engineering work, but it wasn't pushed onto most users.

That old way of doing things, just making a language that worked, is pretty superior.

From late-bound arguments to deferred computation, part 2

Posted Aug 26, 2022 23:10 UTC (Fri) by rjones (subscriber, #159862) [Link]

As a guy who used python extensively as a sort of utility language to "do things"...

I am very happy Golang exists.

From late-bound arguments to deferred computation, part 2

Posted Aug 28, 2022 19:50 UTC (Sun) by tnoo (subscriber, #20427) [Link]

Exactly my feelings. The language becomes overloaded with stuff that is hard to understand, and worse, unintuitive.

From late-bound arguments to deferred computation, part 2

Posted Aug 25, 2022 6:10 UTC (Thu) by clay.sweetser@gmail.com (guest, #155278) [Link]

At this point I consider Python the language to be fairly complete. The improvements I want mainly center around packaging and distribution. Oh well.

From late-bound arguments to deferred computation, part 2

Posted Aug 25, 2022 7:11 UTC (Thu) by NYKevin (subscriber, #129325) [Link]

This is completely standard bikeshedding. You see it in every large software project (FOSS or corporate). It is not peculiar to Python.

From late-bound arguments to deferred computation, part 2

Posted Aug 25, 2022 11:34 UTC (Thu) by anton (subscriber, #25547) [Link]

Every time a simple and valuable little feature is suggested to be added to Python, is this going to happen? Somehow the proposals for adding huge syntactic complexity to the language in the form of async/await and the constant type annotation changes are fine, but adding `=>` to clean up one of the BIGGEST hurdles in teaching the language is not?
Welcome to the law of triviality. It not only points out that the committee discusses the material of the bike shed at undue length, but also that it approves the plans for a reactor without much discussion. Unfortunately, I don't know any way to fix this state of affairs.

From late-bound arguments to deferred computation, part 2

Posted Aug 30, 2022 6:27 UTC (Tue) by edeloget (subscriber, #88392) [Link] (1 responses)

In some contexts I would agree with you. But then we deal with the evolutions of a language, and that must be done very carefully. Or, to be blunt, the average developper/language user is wrong and don't really know what he wants.

Suppose you add the => notation because it's OK for the feature you want - that is, the expression is evaluated when the function is called. Then two years later you want to add a small variation - the argument is evaluated when it's used within the function. You'll then add another keyword or another operator (say, =:> because this one looks fun). Continue this way and soon, you'll get twenty four operators for doing things that are semantically similar but not functionnaly identical. Developpers are lost, nobody remembers the difference between |=?> and ?==> (yeah, I don't know either and I just invented them), programs are full of bugs and users no longer use these programs.

That's pretty much the reason why this so-called bike shedding exists (to be fair, I don't believe this is bike shedding): to avoid doing changes that would make future evolutions more difficult. The python developpers have an obligation to see farther than just "this would be useful, let's do it quickly".

(I cannot believe I just somewhat defended the python developpers ; I still consider that a language that let you multiply arrays of strings is dangerous for mental health).

From late-bound arguments to deferred computation, part 2

Posted Aug 30, 2022 7:44 UTC (Tue) by milesrout (subscriber, #126894) [Link]

>Suppose you add the => notation because it's OK for the feature you want - that is, the expression is evaluated when the function is called. Then two years later you want to add a small variation - the argument is evaluated when it's used within the function.

Slippery slope is not always a fallacy, but it is here. We're talking about adding a feature that people have requested for many years, and you're basically saying "oh but what if people then want to add a feature nobody has ever asked for". Then say no at that point to any change? And what is the result? Well, the useful feature is included and not the useless feature.

From late-bound arguments to deferred computation, part 2

Posted Aug 30, 2022 8:39 UTC (Tue) by jem (subscriber, #24231) [Link] (1 responses)

Python is huge as a language for teaching programming. This point in particular is one of the most unintuitive aspects of one of the most intuitive programming languages.

The root of this problem is the existence of argument defaults in the first place. Without them Python would be an even more intuitive programming language for beginners. In the Zen of Python the second principle is "Explicit is better than implicit.". Also: "Simple is better than complex.", "Readability counts.", and "There should be one – and preferably only one – obvious way to do it.".

From late-bound arguments to deferred computation, part 2

Posted Aug 30, 2022 9:36 UTC (Tue) by milesrout (subscriber, #126894) [Link]

Default arguments do not make things less explicit.

Frankly I find that the Zen of Python is cited more by non-Python programmers as an attempted "gotcha!" based on Python violating their interpretations of it. I rarely see it referred to by Python programmers these days. The whole point of it is meant to be that most of the points are followed by a counterpoint that says 'but don't take it too far'.

'Simple is better than complex' is immediately followed by 'complex is better than complicated' and personally I think it would be overcomplicated to need to write range(0, n) everywhere instead of range(n), even if it's a little "complex" that range has a default argument that comes *before* its normal arguments. No Python beginner that I've ever taught has found that particular point difficult or confusing.

From late-bound arguments to deferred computation, part 2

Posted Aug 25, 2022 21:25 UTC (Thu) by roc (subscriber, #30627) [Link]

When you derail a discussion by proposing an alternative that ends up not even being usable for the original use case, I think you owe a lot of people an apology.

Also, just gotta say, I'm prejudiced against anyone who writes ", PhD" after their name. (And I have one.)

From late-bound arguments to deferred computation, part 2

Posted Aug 26, 2022 2:48 UTC (Fri) by smitty_one_each (subscriber, #28989) [Link] (3 responses)

IIUC, the point here is to make python lazier, i.e. more Haskell-like?

I thought that lambda expressions could do some of this, at the expense of looking a bit ugly.

Maybe there are some specific use-cases for this feature, but it gets at the general concern that python may succumb to "featuritis", and start adding too many niche features whose additional complexity juice just isn't worth the squeeze.

From late-bound arguments to deferred computation, part 2

Posted Aug 26, 2022 20:58 UTC (Fri) by ballombe (subscriber, #9523) [Link] (2 responses)

Python is a good language for beginners and some of them become advanced programmers, but instead of moving to more powerful languages, some of them attempt to extend python to cover their use case, while python is lacking the necessary foundation for that. Duck typing will only bring you so far. Theoretical computer science exists for a reason.

From late-bound arguments to deferred computation, part 2

Posted Aug 30, 2022 7:46 UTC (Tue) by milesrout (subscriber, #126894) [Link] (1 responses)

Arrogant and offensive comment from someone that clearly thinks he is smarter than he really is.

You are right that theoretical computer science exists for a reason. It exists the same reason as pure mathematics: intellectual stimulation. That is its sole purpose.

From late-bound arguments to deferred computation, part 2

Posted Aug 30, 2022 10:15 UTC (Tue) by Wol (subscriber, #4433) [Link]

> You are right that theoretical computer science exists for a reason. It exists the same reason as pure mathematics: intellectual stimulation. That is its sole purpose.

No. A major purpose is research and "blue-sky thinking". Theoretical Physics is pretty much all pure mathematics.

The problem arises when people start thinking that maths PREscribes the way the world behaves. It doesn't. Different maths defines different universes, and we need to do experiments to find out which maths actually applies. That's the step most people don't bother with ...

Cheers,
Wol

From late-bound arguments to deferred computation, part 2

Posted Aug 26, 2022 17:20 UTC (Fri) by lonely_bear (subscriber, #2726) [Link]

I don't think the feature of "late-bound function argument default" has value. Just to save a few key stoke? When the default value could changed according to input, then it is not default, they should be expressed fully.

From late-bound arguments to deferred computation, part 2

Posted Aug 27, 2022 0:04 UTC (Sat) by marcH (subscriber, #57642) [Link] (4 responses)

> Overall, it would seem that a lot more thought and work will need to go into a deferred-computation proposal before it can really even get off the ground. It is a popular feature from other languages, especially functional languages, but there may not be a real fit for it in the Python language.

I always thought "core" features of a language like lazy evaluation must be _all_ designed from the start, as a consistent whole. I'm very far from a language expert but my gut feeling tells me that bolting on something that fundamental that late in the game is doomed to fail in various ways.

From late-bound arguments to deferred computation, part 2

Posted Aug 27, 2022 0:52 UTC (Sat) by NYKevin (subscriber, #129325) [Link] (3 responses)

To my mind, there are three basic ways of going about it:

1. Everything is implicitly lazy all the time, and order of evaluation is totally up to the implementation. For this to work, the entire language has to revolve around it, like Haskell does. Everything has to be referentially transparent and pure, you have to have some sort of "escape hatch" for I/O (which may or may not end up looking like a monad, but it had better exist, unless your language is really domain-specific like CSS), and so on. If you don't do those things up front, then you're going to end up back-solving for them after the fact, and your language is going to be really painful to use.
2. Explicitly constructed and implicitly evaluated thunks. For this to work, you probably need some kind of mandatory static typing, so that the compiler can disambiguate between "I want to invoke the thunk now" and "I want to pass the thunk along to somebody else." You can probably do this sort of thing with C++'s implicit single-argument constructors (and, I would imagine, with Rust's From<T> trait), but that doesn't necessarily mean that it's a Good Idea. Python supports a limited subset of this functionality using the descriptor protocol (see for example some of the Django code [1]), but implicit evaluation is restricted to foo.bar expressions, where foo has already been eagerly evaluated at least to some extent. The lack of static typing means that there are all sorts of weird things that can happen if you try to introspect foo or take it apart when bar has not yet been evaluated.
3. Completely explicit thunks. In other words, lambdas (as well as related stuff like functools.partial). Python already has these, and they work reasonably well. There's not much to improve here (people complain about the lack of statement-lambdas, but that's just minor syntactic salt, and you can use a regular def to get around it).

[1]: https://docs.djangoproject.com/en/4.1/_modules/django/uti...

From late-bound arguments to deferred computation, part 2

Posted Aug 29, 2022 11:08 UTC (Mon) by tialaramex (subscriber, #21167) [Link] (2 responses)

> You can probably do this sort of thing with C++'s implicit single-argument constructors (and, I would imagine, with Rust's From<T> trait)

I don't really see how?

The C++ implicit constructor behaviour means this can be invisible (and hence surprising, and hence a footgun) but it is still a constructor.

Suppose I have types FinalScore (a simple named pair of integers) and SoccerMatch (potentially sophisticated modelling parameters for a game of football). In C++ we can write a function which takes the names of two teams as string_views, and then a FinalScore, but pass it parameters for two strings, and a SoccerMatch. The compiler will implicitly construct the string_views, and the FinalScore, calling a constructor on FinalScore which takes a SoccerMatch (if there isn't one this code doesn't type check).

It does this when the function is called, even if the function implementation actually checks the team names and discards any matches not involving Liverpool without using their FinalScore.

Rust's From<T> (and its cousins Into, TryFrom, and TryInto) are explicit, so you'd need to call the from() function (or the into() function etc.) to actually invoke the thunk. You could once again do this for function arguments, but now it's explicit what's happening (we might just as well call SoccerMatch::simulate here) and you still don't get lazy evaluation.

Rust does have some silent conversions as traits, but they're explicitly not intended to be expensive. Deref and DerefMut are intended solely to make smart pointers work as you'd want them to, and you're cautioned not to do something silly (they're safe Traits, so this can't make your program unsound, but e.g. misfortunate::Double deliberately has a silly implementation of these Traits and it hurts your head) AsRef strongly cautions you that it's intended to be cheap. For example AsRef allows us to take a String (which remember is just a Vec of bytes but we've promised it's UTF-8 text) and get a read only view into those bytes. Cheap.

From late-bound arguments to deferred computation, part 2

Posted Aug 29, 2022 17:55 UTC (Mon) by NYKevin (subscriber, #129325) [Link] (1 responses)

> The C++ implicit constructor behaviour means this can be invisible (and hence surprising, and hence a footgun) but it is still a constructor.

You say "constructor," I say "function that happens to initialize some memory as a side effect." Either way, it's an implicit function call.

> It does this when the function is called, even if the function implementation actually checks the team names and discards any matches not involving Liverpool without using their FinalScore.

That's not really what I'm getting at. Your function could take SoccarMatch instead of FinalScore, and then call a private helper function which takes FinalScore if and when it actually wants to evaluate it. You *choose* to put FinalScore at the API boundary, which may make sense for your particular application, but C++ doesn't force you to do it that way. You could even do it both ways using templating... which is exactly what Rust's From<T> facilitates (i.e. you could accept a From<T> as an argument, then pass it as a T to some other function, and never worry about the conversion at all).

> Rust does have some silent conversions as traits, but they're explicitly not intended to be expensive. Deref and DerefMut are intended solely to make smart pointers work as you'd want them to, and you're cautioned not to do something silly (they're safe Traits, so this can't make your program unsound, but e.g. misfortunate::Double deliberately has a silly implementation of these Traits and it hurts your head) AsRef strongly cautions you that it's intended to be cheap. For example AsRef allows us to take a String (which remember is just a Vec of bytes but we've promised it's UTF-8 text) and get a read only view into those bytes. Cheap.

I never said this was a Good Idea, and in fact specifically called out that it might not be. Nevertheless, it is a thing that you can do if you really want to.

From late-bound arguments to deferred computation, part 2

Posted Aug 31, 2022 1:58 UTC (Wed) by tialaramex (subscriber, #21167) [Link]

I think you've got muddled about From and Into, your description suggests we can take From<T> but I'm pretty sure we would actually write Into<FinalScore> instead

This is one of those mirror image situations, you should implement From<SoccerMatch> on your FinalScore type, but you don't write From<T> or From<SoccerMatch> in the function signature which eventually might need the score, you write what you wanted, Into<FinalScore> instead. We're going to have a line like:

score: FinalScore = Into::into(some_parameter);

... so you can see some_parameter's type needs to be Into<FinalScore> to match.

This is idiomatic for the iterators, hence IntoIterator but it would be unusual to ask for Into<FinalScore> rather than just FinalScore unless you'd specifically designed all of the API this way. As I understand it, what the deferred computation people want for Python is a type which does not require you to be explicit up front in this way.

And that's why I don't think either C++ or Rust fit the bill. They can explicitly do this, but they aren't really designed to be able to implicitly defer computation, everybody involved needs to agree up front to this arrangement.


Copyright © 2022, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds