Problem with Lexical Scope

Bengt Richter bokr at oz.net
Sat Dec 17 02:33:05 EST 2005


On 16 Dec 2005 16:55:52 -0800, "jslowery at gmail.com" <jslowery at gmail.com> wrote:
[...]

>When I first wrote this thing it was 3 stage construction, not 2. I
>wanted to abstract out specific tests and then JUST apply fields to
>them in the actual rules for the business code. I then figured out if I
>standardize having the fields as the last arguments on the check
>routines, then I could use a simple curry technique to build the
>abstraction.
>
>from functional import curry
I'm not familiar with that module, but I wrote a byte-code-munging curry
as a decorator that actually modified the decorated function to eliminate
parameter(s) from the signature and preset the parameter values inside the code.
I mention this because pure-python currying that I've seen typically creates a wrapper
function that calls the original function, and slows things down with the extra calling
instead of speeding things up, at least until some future version of python.
Looks nice on the surface though.

>
>comp_rule = curry(check)(do_complicated_test)
>
>Then in the actual business code, can use it like this:
>
>comp_rule(('a', 'b'))
>
>Or:
>
>ssn_rule = curry(match)(is_ssn)
>
>ssn_rule('my-social-security-field')
>
>For this to work properly and easily for the business logic. I do need
>to get down some more solid rule invocation standards.
>
>
>>>(have('length'), have('width')),
>>>                   check(['length', 'width'], lambda x, y: x == y))
>>>assert rule({'length' : '2', 'width' : '2'}) == True
>>>assert rule({'length' : '2', 'width' : '1'}) == False
>>>assert rule({'length' : '1', 'width' : '2'}) == False
>>
>>But what about when the "when" clause says the rule does not apply?
>>Maybe return NotImplemented, (which passes as True in an if test) e.g.,
>
>I don't really see why a special exception needs to be here because
>"when" easily falls into the logic. When is basically the truth
>function, "if A therefore B" and when(A, B) => NOT A OR B
>
I was suggesting a distinction between testing and production failure detection.
The latter of course can treat not-applicable as logically the same as
applicable-and-did-not-fail.

Note that NotImplemented is not the same as NotImplementedError -- and I wasn't suggesting
raising an exception, just returning a distinguishable "True" value, so that
a test suite (which I think you said the above was from) can test that the "when"
guard logic is working vs just passing back a True which can't distinguish whether
the overall test passed or was inapplicable.

Of course, to assert a did-not-fail condition, bool(NotImplemented) tests as True,
so the assertion (if that's what you want to do) would be written

    assert rule(...),          'You only see this if rule was applicable and did not pass'
                               # since assert NotImplented succeeds and assert True succeeds
                               # w/o raising AssertionError and False means rule was applicable and failed.

for testing purposes you can distinguish results:

    assert rule(...) is True,  'You only see this if rule was not applicable or test failed'
    assert rule(...) is False, 'You only see this if rule was not applicable or test succeeded'
    assert rule(...) is NotImplemented, 'You see this if rule WAS applicable, and either passed or failed

OTOH, I can see returning strictly True or False. It's a matter of defining the semantics.

>If A is false, then the expression is always true.
>
>def when(predicate, expr):
>    """ when(rule predicate, rule expr) -> rule
>    """
>    def rule(record):
>        if predicate(record):
>            return expr(record)
>        else:
>            return True
>    return rule
>
>So if the "test" fails, then the expression is True OR X, which is
>always True.
Sure.

>
>Or, form another POV, the entire system is based on conjunction.
>Returning "true" means that it doesn't force the entire validation
>False. When the predicate fails on the When expression, then the
>"therefore" expression does not matter because the predicate failed.
>Therefore, the expresison is true no matter what.
>
>Trying not to be condesending, I just think that the typical
>propositional logic works well in this case.
>Because this fits well into the system, can you give me an example why
>this should make an exception to the boolean logic and raise an
>exception?
First, I didn't suggest raising an exception, I suggested returning an alternate
value (NotImplemented) that python also considers logically true in an "assert value"
or "if value" context, so it could be distinguised, if desired, in a test suite (as I believe
you said your snippets were from). 

Of course if you want to write "assert expr == True" as opposed to
just "assert expr", you are writing a narrower assertion. Note:

 >>> assert NotImplemented, 'assertion failed'
 >>> assert True,           'assertion failed'
 >>> assert False,          'assertion failed'
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
 AssertionError: assertion failed

If your rule was returning NotImplemented as a "true" value, you wouldn't use
the narrowed assertion in a production context, you'd just use the normal
bare python-truth assertion. I.e., (using the dummy rule to illustrate)

 >>> def rulereturning(x): return x
 ...

you would not write

 >>> assert rulereturning(NotImplemented)==True, 'assertion failed'
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
 AssertionError: assertion failed

You would write
 >>> assert rulereturning(NotImplemented),       'assertion failed'

(That passed)

BTW, note that unless you use 'is', expr==True is not as narrow as you might think:
 >>> assert 1.0==True, 'assertion failed'
or
 >>> assert rulereturning(1.0)==True,            'assertion failed'

(both passed sans exception)


>The inline code generation is an interesting concept. It would
>definately run faster. Hmm, I did this little test.
>
[...]
BTW, is this data coming from an actual DBMS system? If so, wouldn't
there be native facilities to do all this validation? Or is Python
just too much more fun than SQL? ;-)
[...]
>
>The thing I'm wrestling with now is the identity of rules and fields.
>I'd like to fully support localization. Making a localization library
>that will provide string tables for "error messages".  Could use
>positions to do this.
IWT dict keys would be more robust. Maybe just use the english string as the
key for looking up alternates? BTW strings could also name data to be
formatted, as in '%(this)s and %(that_num)d' % mappingobject,
where mappingobject can be anything that looks up values per
mappingobject.__getitem__(name) where name would be 'this' or 'that' here.

>
>rules = [
>     have('first_name'),
>     have('last_name'),
>     all(have('ssn'), match(is_ssn, 'ssn')),
>     when(all(have('birth_date'), have('hire_date')), lt('birth_date',
>'hire_date'))
>]
>
>msgs = [
>    locale('MISSING_VALUE', 'FIRST_NAME'),
>    locale('MISSING_VALUE', 'LAST_NAME'),
>    locale('INVALID_SSN'),
>    locale('INVALID_BIRTH_HIRE_DATE'),
>]
Not to distract you, but I'm wondering what your rules would look like represented in simple
rule-per-line text, if you assume that all names are data base field names
unless otherwise declared, and just e.g. "name!" would be short for "have(name)",
then just use some format like (assuming 'rules' is a name for a particular group of rules)

    functions:: "is_ssn" # predeclare user-defined function names
    operators:: "and", "<" # might be implicit

    rules: first_name!; last_name!; ssn! and is_ssn(ssn); birth_date! and hire_date! and (birth_date < hire_date)

or with identation, and messages after a separating comma a la assert

    rules:
       first_name!, 'Missing first name' # or ('MISSING_VALUE', 'FIRST_NAME') tuple as args for your locale thing
       last_name!,  'Missing last name'
       ssn! and is_ssn(ssn), 'Invlaid ssn' # => 'ssn' in record and is_ssn(record['ssn']
       birth_date! and hire_date! and (birth_date < hire_date) # => 'birtdate' in record and 'hire_date' in record and
                                                               #       record['birth_date'] < record['hire_date']
    more_rules:
       field_name!, 'field name is missing'
       etc ...

Seem like it would be fairly easy to translate to code in a function per rule group,
without any fancy parsing.
I don't like XML that much, but that might be a possible representation too.

Regards,
Bengt Richter



More information about the Python-list mailing list