Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Top-down presentation style for definitions #11

Open
Technologicat opened this issue Aug 8, 2019 · 3 comments
Open

Top-down presentation style for definitions #11

Technologicat opened this issue Aug 8, 2019 · 3 comments
Assignees
Labels
enhancement New feature or request
Milestone

Comments

@Technologicat
Copy link
Owner

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.:

  • First pass (outside-in) or second pass (inside-out)?
  • Require with definitions to always appear in tail position, or allow it in any position?
  • Allow several with definitions blocks?
  • If mixing and/or several blocks are allowed, what's the desired evaluation order?
  • Especially, regarding lexically nested with definitions blocks?
  • Better short, descriptive names, instead of topdown and definitions?
@Technologicat Technologicat added the enhancement New feature or request label Aug 8, 2019
@Technologicat Technologicat self-assigned this Aug 8, 2019
@Technologicat Technologicat modified the milestones: 0.14.3, 0.14.2, 0.14.x Aug 8, 2019
@Technologicat
Copy link
Owner Author

Technologicat commented Sep 3, 2019

Maybe in the initial implementation:

  • Require with definitions to appear in tail position in the with topdown. Allow only one.
  • But allow nesting a with definitions block in tail position in another with definitions block (so that the definitions themselves can also use top-down style, recursively).
  • Shouldn't matter whether with topdown itself is first- or second-pass.
  • We need just one expander to handle a "topdown context". The top-level with topdown body is such as context; as are the bodies of any with definitions. The handler should first check for the presence of a with definitions block in tail position, recurse into it if present, and paste the result to the beginning of the output. Then just append everything else to the output.

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 Technologicat modified the milestones: 0.14.x, 0.14.2 Sep 3, 2019
@Technologicat Technologicat modified the milestones: 0.14.2, 0.14.x Sep 26, 2019
@Technologicat
Copy link
Owner Author

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

@Technologicat
Copy link
Owner Author

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...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant