Signature binary function (e.g. comparison) design #63
Replies: 7 comments
-
|
About the question: "Are there things we cannot do with key functions that we can with the usual binary operator way (specifying comparison function via a two-element boolean function such as compare(x, y))?", the answer should be "No" because of the existence of |
Beta Was this translation helpful? Give feedback.
-
|
The code of cmp_to_key is nothing more than: def cmp_to_key(mycmp):
"""Convert a cmp= function into a key= function"""
class K(object):
__slots__ = ['obj']
def __init__(self, obj):
self.obj = obj
def __lt__(self, other):
return mycmp(self.obj, other.obj) < 0
def __gt__(self, other):
return mycmp(self.obj, other.obj) > 0
def __eq__(self, other):
return mycmp(self.obj, other.obj) == 0
def __le__(self, other):
return mycmp(self.obj, other.obj) <= 0
def __ge__(self, other):
return mycmp(self.obj, other.obj) >= 0
__hash__ = None
return K |
Beta Was this translation helpful? Give feedback.
-
|
Excellent! That's going to be a valuable thing for simplifying the design! |
Beta Was this translation helpful? Give feedback.
-
|
I'm not managing to come up with a design that I'm satisfied with. I'll do some more work on this, but am thinking that, for now, I won't solve the issue as a mini-framework like I was hoping, but rather collect a few useful tools around the issue. (Perhaps, this is the right design I've been looking for.) Here are my thoughts about these. At the very top level, some contexts will call for a signature binary function (SBF). That is, a Task: extract what ever logic is hardcoded in our code and percolate it to the surface (as a function's argument). By doing this, we're keeping things as they are, while making them open-closed for this issue. A user can always change the behavior by making a different SBF. From there, there's a temptation to go further, because we'll quickly realize that there's a lot of reuse potential when making these SBFs. I propose we don't go further (until the need calls for it), except perhaps documenting the problem patterns we see, and possibly making functions for these (but without trying to fit these in a framework). Here are some problem patterns I see: matching paramsEssentially, adding a layer of information (or structuring, annotating) about the params of each of the signatures (and possibly the
param binary functionsSeveral times in the above, we forwarded the problem down to the "pairs of params" level.
param attribute binary functionsMost of the time, our param binary function will really be just an aggregate (e.g.
The following code (meant to be used as a factory that makes param binary functions through from operator import eq
def param_binary_func(
param1, param2, *, name=eq, kind=eq, default=eq, annotation=eq, aggreg=all
):
return aggreg((
name(param1.name, param2.name),
kind(param1.kind, param2.kind),
default(param1.default, param2.default),
annotation(param1.annotation, param2.annotation),
))Note that the same |
Beta Was this translation helpful? Give feedback.
-
|
Though "binary function" is more precisely indicates the generality we need here, I'm wondering if we should sinfully just call it "comparator" since:
Perhaps we can call it "collator" and "comparison->collation"? I really insist on finding a non-composite word because things get out of hand when adding other words to it. Any opinions? |
Beta Was this translation helpful? Give feedback.
-
|
Regarding using the parameter comparators to make signature comparators, one way is to do: graph
a(sig1) --> matcher
b(sig2) --> matcher
matcher --> matches(matches)
matcher --> remainder(remainder)
matches -- for each match --> param_comparator
param_comparator -- collect --> comparison(comparison)
comparison --> aggreg
remainder --> aggreg
aggreg --> output(final output or error)
Or something like that. But, it could also be: graph
sig1 --> cartesian_product
sig2 --> cartesian_product
cartesian_product -- for each pair --> param_comparator
param_comparator --> comparisons_matrix(comparisons_matrix)
comparisons_matrix --> reduction
reduction --> result(result)
The latter is cleaner, but quite wasteful, and maybe harder to express some simple cases (our most common cases). |
Beta Was this translation helpful? Give feedback.
-
Notes on Aug 2024 work on signatures
Use of postprocessIn the current version of return (
validate_variadics()
and validate_param_counts()
and validate_extra_params()
and validate_param_positions()
and validate_param_compatibility()
)The problem I had with that today is that I'm getting It would be better to have an aggregator of the validators that defaults to So I'm changing @postprocess(all)
def is_call_compatible_with(
...
return (
f()
for f in [
validate_variadics,
validate_param_counts,
validate_extra_params,
validate_param_positions,
validate_param_compatibility,
]
)Is this better? Many would argue that it's not, because it's not as straightforward as the previous code. And they have a point. Straightforward is always nicer when needing to maintain, especially debug, especially when it's juniors that do so. But the new code is better in the sense that it has better separation of concerns and is more reusable, is set up for better "open-close"ness etc. Now, if you want to see what function produced a from i2.signatures import *
sig1 = Sig('(a, /, b, *, c)')
sig2 = Sig('(a=0, b=0, c=0)')
list(is_call_compatible_with.__wrapped__(sig1, sig2))
# [True, True, True, True, False]and now you know it's the |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
There's a recurring need for more control over how we compare and/or merge signatures.
See also:
These often involve some logic on parameter objects (with their name, kind, and optionally default and annotation), order, etc.
We'd like to have a clean framework that enables the user to easily and correctly define these functions, reusing much of the common logic.
Examples:
sig1 == sig2: equality (more precisely "equivalence relation") of two signaturessig1 <= sig2: comparison (more precisely "total order relation" asissubset,issubclassetc.)sig1 + sig2)sig1to an input value of function with signaturesig2Don't miss this comment that proposes a design.
Notes
Return type of a comparison
Must cases might just need a Boolean, like with
x == yorx <= y(orissubset,issubclass, etc.).But in some cases, such as with self-organizing code (or less crazily: validation scores), we'd like to have a numerical (or more generally, vectorial) score, or more generally, some "comparable" type that can be used to do things like "best match".
Use a key function or a binary comparison function as the specifier?
Might want to express comparisons based on key functions like builtin
sorted,maxetc. do. What are the pros/cons? It seems like a cleaner design enabling easier UX (and obviously, a big pro is the interface-consistency with familiar builtins). But, for example, is it complete? Are there things we cannot do with key functions that we can with the usual binary operator way (specifying comparison function via a two-element boolean function such ascompare(x, y)).For example, how would comparison scores be derived from key functions? Even if it's possible to "fit" any comparison function with vectorial key functions, would it be the most intuitive way of expressing it?
References
name,kind,defaultandannotation). Returns first parameter object of iterable if validation passes.signatures.pycodeRelated issues
code_to_dag. But that's only forcode_to_dag. Still, the long issue has some reflections about signature comparison.Further notes
The definition of signature equality
Sigdoesn't redefine it -- the builtin is used, which boils down to using the following (acts basically as a "key function"):Beta Was this translation helpful? Give feedback.
All reactions