The Joys Of Data-Driven Programming

Chris Angelico rosuav at gmail.com
Thu Aug 18 01:43:52 EDT 2016


On Thu, Aug 18, 2016 at 2:47 PM, Marko Rauhamaa <marko at pacujo.net> wrote:
> Lawrence D’Oliveiro <lawrencedo99 at gmail.com>:
>
>> Solution: set up a table of rules
>
> Without judging the individual merits of rules for your use case, I just
> want to mention that as a rule, I dislike rules. Rule languages tend to
> grow out of all bounds, always remain deficient and have impenetrable,
> ad-hoc semantics.

So you'd be with the Queen Elsa in singing "Doğru yanliş, bir kural
yok"? (I figure this song's famous enough that you probably know what
I mean even without seeing it in English.)

Rule-based code adds a level of abstraction above raw code. Thing is,
though, I actually have no idea about the merits of this exact
example, because (a) the actual rules don't seem to be there, and (b)
there's a lot of guff in the code that makes it very hard to skim. (I
include the "#end if" lines as guff. They're nothing but distraction,
plus a good chance at mismatching stuff when you edit.

Here's how *I* would do this kind of problem.
> Problem: let the user specify certain parameters for a screen display (width, height, diagonal, pixel density, pixels across, pixels down, optimum viewing distance) and from the ones specified, work out the parameters which were not specified.
>

For each parameter, define zero or more sets of parameters from which
it can be calculated. Then iterate over the parameters you don't have,
iterate over the sets, and when you find one that's <= the parameters
you have, call the corresponding function. It's a variant of a
rule-based solution, where the rules are functions tagged with a
decorator.

parameters = defaultdict(dict)
def param(func):
    target = func.__name__
    source = func.__code__.co_varnames[:func.__code__.co_argcount]
    parameters[target][frozenset(source)] = func
    return func

@param
def width(height, diagonal):
    """Calculate the width from the height and diagonal"""
    # Probably the wrong way to calculate this but whatev, this is an example
    return diagonal*diagonal - height*height

@param
def width(pixel_density, pixels_across):
    return pixels_across / pixel_density

@param
def diagonal(height, width):
    return math.hypot(height, width)

# ... etc etc etc

def make_display(**args):
    for kwd, sources in parameters.items():
        if kwd in args: continue
        for source, func in sources.items():
            if source <= set(args):
                args[kwd] = func(**{arg:args[arg] for arg in source})
                break
        else:
            # TODO: Queue this for retry down below, or something
    if set(args) <= set(parameters):
        raise InsufficientInformation


This is a variant of data-driven code. The work is broken out into
individual functions whose names and parameters are used to create the
lookups. Then the actual searching is fairly compact (and could be
done better than this, too). You may notice that very little of this
code is actually aware of specific parameter names - only the formula
functions themselves. Everything else is completely generic.

(There may be bugs in the above code. It's completely untested.
Correction: The above code is completely untested, therefore there are
definitely bugs in it.)

ChrisA

PS. "No right, no wrong, no rules for me".



More information about the Python-list mailing list