Technologicat / unpythonic

Supercharge your Python with parts of Lisp and Haskell.
Other
91 stars 3 forks source link

Top-down presentation style for definitions #11

Open Technologicat opened 5 years ago

Technologicat commented 5 years ago

Occasionally a top-down presentation style (like Haskell's where) is useful:

from pampy import match, _

from unpythonic.syntax import macros, topdown, definitions  # DOES NOT EXIST YET

with topdown:
    # First say what we want to do...
    result = match(data, pattern1, frobnicate,
                         pattern2, blubnify,
                         ...
                         patternN, percolate)
    # ...and only then give the details.
    with definitions:
        def frobnicate(...):
            ...
        def blubnify(...):
            ...
        ...
        def percolate(...):
            ...

The benefit of the top-down presentation is the reader will already have seen the overall plan when the detailed definitions start. The program logic reads in the same order it plays out.

This would expand to:

from pampy import match, _

# Python wants the details first...
def frobnicate(...):
    ...
def blubnify(...):
    ...
...
def percolate(...):
    ...
# ...so that the names will be bound by the time we do this.
result = match(data, pattern1, frobnicate,
                     pattern2, blubnify,
                     ...
                     patternN, percolate)

In pure Python, it is of course already possible to delay the match call by thunkifying it:

promise = lambda: match(data, pattern1, frobnicate,
                              pattern2, blubnify,
                              ...
                              patternN, percolate)
# ...definitions go here...
result = promise()

This works because - while values are populated dynamically - name resolution occurs lexically, so it's indeed pointing to the intended frobnicate (and it's fine if a value has been assigned to it by the time the lambda is actually called).

However, this introduces a promise, and we have to remember to force it at the end (after the many, many definitions) to actually compute the result. (For a proper evaluate-at-most-once promise, we would use lazy[] from MacroPy, and maybe for readability, force it using unpythonic.force instead of just calling it.)

In use cases like this, the main benefit of inlining lambdas to the match call (e.g. in Racket) is the approximately top-down presentation. The with topdown construct introduces this benefit for named functions.

Since 0.12.0, we already support top-down presentation for expressions:

from unpythonic.syntax import macros, let, where

result = let[2 + a + b,
             where((a, 17),
                   (b, 23))]

The with topdown construct would add top-down presentation for statements.

The goal is to allow arbitrary statements in the with definitions block, although it is recommended to use it only to bind names used by the body (whether by def or by assignment, doesn't matter).

The top-level explicit with topdown is a feature to warn the reader, because top-down style can destroy readability if used globally.

The topdown macro should apply first in the xmas tree.

At this stage the design still needs some thought, e.g.:

Technologicat commented 5 years ago

Maybe in the initial implementation:

This should yield a simple implementation that does the most important thing. This could be implemented already in 0.14.2.

The final detail are the names. with topdown is long, and not common programming terminology - the issue of presentation order (where the terms top-down and bottom-up in the sense intended here originate from) is more often discussed in the context of writing or making presentations.

So let's consider some alternatives. with clarity (the reason why one would use this construct) is just silly, and no shorter - but perhaps more memorable. with lifo invites a misleading association with queues. with td is short, but a tad cryptic. with aim can be misread as a TLA - what's this A.I.M. and how do you with with it? Maybe with goal, that's what it does?

As for with definitions, the obvious abbreviation with defs is almost self-documenting, except the detail that it actually accepts any statements, not just def statements. Those are the main use case, though, so perhaps this is acceptable.

So maybe this feature will be named with goal and with defs.

Technologicat commented 5 years ago

Meh, maybe better to feature-freeze 0.14.2 now and leave this for the next release.

Technologicat commented 5 years ago

Maybe the definitions should apply backwards, to remove the need of nesting with defs blocks when the definitions are top-down too?

with goal:
    x = a * b * c
    with sfed:
        a = f(...)
        f = ...

being equivalent to

with goal:
    x = a * b * c
    with defs:
        a = f(...)
        with defs:
            f = ...

which expands to

f = ...
a = f(...)
x = a * b * c

For simple use cases, a single with-form that applies its statements from last to first would be enough, but the point of the two-part design is to have more control in complex use cases (which is where this whole feature benefits the most).

Maybe this still needs some more thinking...