Skip to content

Conversation

@aisha-w
Copy link
Contributor

@aisha-w aisha-w commented Aug 26, 2019

Moved some of the documentation out of README.md into their own files to improve navigability.

#2

In order to reduce the size of README.md, I moved some documentation to their own files, and updated README.md with appropriate navigation.
Fixed formatting.
@Technologicat
Copy link
Owner

Hi, and welcome to GitHub!

This looks good. I see you also made some formatting and grammar corrections, which is nice.

Optionally, if you want, there are some design notes also in the macro readme, which could be moved into design-notes.md. See the section titled Meta. Of the information currently in that section, the macro readme needs just a short description of the xmas tree combo, the rest are design notes. (Though maybe the "this is semantics, not syntax" disclaimer could be left in. Or maybe it could be moved. I'll leave it for you to decide which you think is better.)

Also, when reviewing the changes, I noticed that maybe it would be better if the silly attempts at Python humor (eels etc.) in the original text were moved out of the headings and into the main text, but technically that's an issue separate from the actual splitting. Basically, to improve usability, the heading being jumped to should match the text visible on the link.

If you feel this is already enough, I can merge the PR as-is - just post a comment here to tell me which option you prefer.

@aisha-w
Copy link
Contributor Author

aisha-w commented Aug 27, 2019

Hello! And thanks.
I think you've made some good suggestions, so I'll work on integrating those.

@Technologicat
Copy link
Owner

Thanks for the updates, looking good!

I wasn't aware GitHub-flavored Markdown supported expandable sections - that's exactly what this documentation needed. Nice!

I see you've also removed the added/changed notes, which is good - we can tick off another item from the TODO list in #2.

Actually, I'm thinking that since the pace of development has slowed somewhat, and with the polishing of the documentation, the next release (currently codenamed 0.14.2) could be called 1.0. Then it would be possible to start using semantic versioning.

Tell me when you're ready with the changes, I'll review and merge then.

@aisha-w
Copy link
Contributor Author

aisha-w commented Aug 28, 2019

Thank you! And apologies for the commit storm, I'm still getting used to contributing.

So far I've looked through the macros document and the design notes, but I'd like to go through the pure-python document (and everything else one more time) before finally merging.

Thank you for your patience!

@Technologicat
Copy link
Owner

A commit storm is a regular day in a git repo... :)

Ok, I'll wait until you say it's ready.

If you want to combine (some of?) the commits into a single commit, this can be done before merging. Look into interactive rebasing, especially how to squash commits. But be careful, it is possible to make mistakes during the process that may permanently erase commits with no way to restore them.

I've done it myself using Magit (the git frontend for Emacs; squashing instructions), but it should also be possible from the git command line. Git-scm.com has a section on interactive rebasing. There's also a general explanation of rebasing in their Pro Git book (it's free to read).

If there are just a few commits, I usually don't bother squashing them, unless the project maintainer specifically requests it. Different projects on GitHub have different guidelines. Large, popular projects obviously have to be picky to keep their commit history manageable, but for small projects, the commit volume is so low anyway that often it doesn't matter that much.

Of course, then there's the viewpoint of semantics - many developers prefer that a commit identifies one meaningful set of changes. This makes it easier to select just those changes for rebasing later, should a need arise e.g. to backport a bugfix or feature onto an earlier version of the codebase. (Then you just pick that one commit and you're done. Otherwise, will have to dig through the commit log to find which ones to take.) But unpythonic only has a master branch, so it doesn't really matter.

I'll leave the choice up to you. :)

(Magit is great, but perhaps Emacs has a bit too much of a learning curve if you don't need it otherwise.)

@Technologicat Technologicat added the enhancement New feature or request label Aug 29, 2019
@Technologicat Technologicat added this to the 0.14.2 milestone Aug 29, 2019
@Technologicat
Copy link
Owner

I closed the separate TODO issue I opened yesterday for merging the documentation improvements - let's just use this PR to track the status.

So don't worry, even though the referring issue above is closed, the improvements are going to be merged into 0.14.2 (or 1.0 as it'll probably be called) :)

Looking at the milestone, there's just one more code enhancement to make, after which the final tasks for the upcoming release are to merge your documentation improvements (once complete), and to document the new features and changes. (No hurry - the release schedule is "when it's ready".)

Some new functions have been added, and islice has been extended to accept negative start and/or stop. When it's time, I'll scrape the commit log for a full list.

I can write the initial version of the new parts of the documentation. If you want, proofreading is welcome - this would ensure it gets the same QA treatment as the rest - or if you feel that's too much, we can just leave the new parts at my version for now.

@aisha-w
Copy link
Contributor Author

aisha-w commented Sep 4, 2019

I've looked through everything and am fine with the changes for now.

I can proofread the new parts of the documentation! I'll wait on standby until you're ready.

@Technologicat
Copy link
Owner

Great!

The next step is for me to review and merge this PR.

It's more convenient to handle the proofreading in a separate PR, so I can easily make my updates on top of the already reformatted documentation. I'll post a comment here (after merging and closing) to let you know when I have the new parts ready.

@Technologicat
Copy link
Owner

I looked at the changes and mostly the PR looks great!

I have some minor issues for which I'll be requesting changes - tell me if you want to make the changes yourself, or leave them for me to make when I merge. (I'll make a detailed list as soon as I can, and send it as a review here.)

One important point I noticed when looking at the changes is that I see now my original documentation may not have been clear enough on the overall layout of unpythonic, so let me explain. :)

The unpythonic library consists of two parts - the pure-Python layer and the macro layer. It is correct that the pure-Python layer acts as a core for the macro layer, but that's not the full story.

What I had missed to point out clearly is that the pure-Python layer also provides some functionality, which has no macro equivalent. Those parts of the pure-Python layer are intended to be used directly.

As someone once put it, macros are the nuclear option of software engineering. In order to keep your design clean and elegant, the general guideline is to avoid writing a macro whenever a regular function could perform the same job. However, there are some jobs regular functions cannot perform. Some language extensions can be extremely inconvenient to use or even impossible to build without macros. This is the role of the macro layer.

For example, continuations is one such feature. There is a library by Thomas Baruchel for manual continuation-passing, but I think automating the CPS conversion makes the code much more readable.

(Though to be fair, I don't know whether Python needs continuations - Paul Graham happened to provide a recipe in his book, and it was fun to build something similar in Python, so I just went ahead and did it. The general opinion is that continuations are a feature of which much more is written about than it is actually used. Python already has a limited suspend-and-resume facility in the guise of generators and coroutines. It's difficult to come up with an actual use case where the resume multiple times from an already executed point of your code feature of general continuations makes a crucial difference.)

As for the let and do constructs, it is technically possible to use them without macros, but the macro version is so much more convenient to use, that I'm tempted to call the pure-Python version an implementation detail. (In particular, the macro version hides the unwieldy environment parameter, as well as performs lexical scoping automatically.)

Some features are borderline cases. For example, TCO can be used without macros, but you have to remember to decorate each function with @trampolined manually, and to return jump(f, ...) instead of return f(...) to make a tail call. The tco macro writes these parts for you (incidentally, this is what Lisp programmers mean when they talk about programs that write programs). In such a case, whether or not to rely on macros, I think is a trade-off each user must decide for themselves.

At the other extreme, the "batteries" for itertools and functools are designed to be used as-is - no macros needed. If these features are all the user wants from unpythonic, then MacroPy is not needed.

Having only a soft dependency on the macro system is for both cultural and technical reasons. To most programmers outside the Lisp community, macro systems are unfamiliar, and not understood very well. The title of Greg Hendershott's macro tutorial for Racket I think summarizes the situation well.

In the case of Python, there is also the technical aspect that Python was not designed to have macros. Specifically, there are no backward compatibility guarantees regarding its AST representation. In fact, some parts of the AST have changed several times during the 3.x series (see GTS). So in Python, depending on a package that relies on the AST structure may cause your software to break later, when you upgrade to a later minor release of Python (3.x → 3.y), if the macro package happens to go out of support. This includes MacroPy itself, as well as any packages that depend on it. For projects that must remain runnable in the long term with minimal maintenance, this is a concern.

So, I think the main points of this should be explained in the documentation, particularly the fact that the pure-Python layer essentially consists of two parts - underlying implementations for macros, and features that are meant to be used directly.

Also, it should be marked clearly which is which. I suppose this is marking is something for me to do, since I designed the thing. :)

Copy link
Owner

@Technologicat Technologicat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's the review as promised. The PR looks great!

Just some minor things that we should probably fix before this goes live. See detailed comments.

README.md Outdated

This README documents the pure-Python part of ``unpythonic``. See also [documentation for the macros](macro_extras/).
**This is semantics, not syntax!**
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section belongs in the macro README, since the macro part is where we add syntaxes.

(Granted, semantically speaking s (mathematical sequences), fup (functional update) and islice (regular slicing syntax for iterables) are borderline cases, but they are regular functions that don't need MacroPy - so, technically, procedures, not syntaxes.)

README.md Outdated
- [``setescape``, ``escape``: escape continuations (ec)](#setescape-escape-escape-continuations-ec)
- [``call_ec``: first-class escape continuations](#call_ec-first-class-escape-continuations), like Racket's ``call/ec``.
- [``forall``: nondeterministic evaluation](#forall-nondeterministic-evaluation), a tuple comprehension with multiple body expressions.
[pure-Python (basic implementation) documentation](doc/features.md)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be clearer to just refer to the "pure-Python part" and "macro part" (or something similar - would you happen to have a better term in mind?), since there are (actually quite many) features that are directly provided by the pure-Python part.

- ``recur.tco`` in [fn.py](https://github.com/fnpy/fn.py), the original source of the approach used here.
- [MacroPy](https://github.com/azazel75/macropy) uses an approach similar to ``fn.py``.

### Comboability
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could mark explicitly that this section pertains to the macro part. Maybe "Comboability of macros"?

doc/features.md Outdated
@@ -0,0 +1,2601 @@
# Unpythonic: Python meets Lisp and Haskell

Documentation for the underlying pure-Python API, which can be used directly if you don't want to depend on MacroPy. See also [documentation for syntactic macros](macro_extras/).
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Underlying API for macros, correct, but also providing all the features that don't need macros. I think we need some rewording to indicate this.

Maybe something like (feel free to edit):


Documentation for the pure-Python API, which provides any features that need no macros and are meant to be used directly, as well as acts as a core for the macro layer. See also documentation for syntactic macros.

As for features that have both a macro layer and an underlying API, if you don't want to depend on MacroPy, you can use the API directly, but this will be less convenient.


*This document doubles as the API reference, but despite maintenance on a best-effort basis, may occasionally be out of date at places. In case of conflicts in documentation, believe the unit tests first; specifically the code, not necessarily the comments. Everything else (comments, docstrings and this guide) should agree with the unit tests. So if something fails to work as advertised, check what the tests say - and optionally file an issue on GitHub so that the documentation can be fixed.*

**This document is up-to-date for v0.14.1.**
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll have to remember to update this when I'm done documenting 0.14.2, or 1.0 as it'll be called :)

But yes, it's good to have this note in a central location.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Just to clarify, not requesting any changes to this particular location - just a comment.)

doc/features.md Outdated
The values of dynamic variables remain bound for the dynamic extent of the `with` block. Exiting the `with` block then pops the stack. Inner dynamic scopes shadow outer ones. Dynamic variables are seen also by code that is outside the lexical scope where the `with dyn.let` resides.

<details>
<summary>Each thread has its own dynamic scope stack. </summary>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the summary could mention also the global level?


Each thread has its own dynamic scope stack; in addition, there is a global dynamic scope for default values, shared between threads.

doc/features.md Outdated

When ``fupdate`` constructs its output, the replacement occurs by walking *the input sequence* left-to-right, and pulling an item from the replacement sequence when the given replacement specification so requires. Hence the replacement sequence is not necessarily accessed left-to-right. (In the last example above, ``tuple(range(5))`` was read in the order ``(4, 3, 2, 1, 0)``.)

The replacement sequence must have at least as many items as the slice requires (when applied to the original input). Any extra items in t
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could delete these notes on old versions (0.10.0 and 0.11.1).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean remove the paragraph?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, could remove the two paragraphs, the one starting with v0.10.0 and the one starting with v0.11.1.

- Expression-assignment to an unpythonic environment, ``f << (lambda ...: ...)``

- Let bindings, ``let[(f, (lambda ...: ...)) in ...]``, using any let syntax supported by unpythonic (here using the haskelly let-in just as an example).
- Env-assignments are now processed lexically, just like regular assignments. Added support for let-bindings.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of the wording left over from the old changed-in-version-x note, should be:


  • Env-assignments are processed lexically, just like regular assignments.

(Extra "now" in the first sentence. Also, support for let-bindings is already explained above, so it's not needed here.)

*Changed in v0.13.0.* The special mode for uninspectables used to be enabled for the dynamic extent of the ``with curry`` block; the new lexical behavior is easier to reason about. Also, manual uses of the ``curry`` decorator (on both ``def`` and ``lambda``) are now detected, and in such cases the macro now skips adding the decorator.
**CAUTION**: Some built-ins are uninspectable or may report their arities incorrectly; in those cases, ``curry`` may fail, occasionally in mysterious ways. The function ``unpythonic.arity.arities``, which ``unpythonic.fun.curry`` internally uses, has a workaround for the inspectability problems of all built-ins in the top-level namespace (as of Python 3.7), but e.g. methods of built-in types are not handled.

The special mode for uninspectables used to be enabled for the dynamic extent of the ``with curry`` block; the new lexical behavior is easier to reason about. Also, manual uses of the ``curry`` decorator (on both ``def`` and ``lambda``) are now detected, and in such cases the macro now skips adding the decorator.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This paragraph needs new wording now that it is no longer inside a changed-in-version-x note.

Also, the first point of the paragraph is already given further above ("Lexically inside..." ... "...in a special mode that..."), so it can be removed from here. Here we could have just:


Manual uses of the curry decorator (on both def and lambda) are detected, and in such cases the macro skips adding the decorator.

Lexically inside a ``with prefix`` block, any literal tuple denotes a function call, unless quoted. The first element is the operator, the rest are arguments.

*Changed in v0.11.0.* Bindings of the ``let`` macros and the top-level tuple in a ``do[]`` are now left alone, but ``prefix`` recurses inside them (in the case of bindings, on each RHS).
Lexically inside a ``with prefix`` block, any literal tuple denotes a function call, unless quoted. The first element is the operator, the rest are arguments. Bindings of the ``let`` macros and the top-level tuple in a ``do[]`` are now left alone, but ``prefix`` recurses inside them (in the case of bindings, on each RHS).
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Misplaced "now" left over from a changed-in-version-x note, should be:


Lexically inside a with prefix block, any literal tuple denotes a function call, unless quoted. The first element is the operator, the rest are arguments. Bindings of the let macros and the top-level tuple in a do[] are left alone, but prefix recurses inside them (in the case of bindings, on each RHS).

@Technologicat
Copy link
Owner

Ah, one more thing that I didn't yet explain clearly - there are three kinds of features in unpythonic, not just two:

  1. Features that only exist in the pure-Python core, and are meant to be used directly (e.g. batteries for itertools),
  2. Features that have a pure-Python core, plus a macro layer that improves the user experience (e.g. do and let), and
  3. Features that only exist as macros (e.g. continuations, lazify and dbg).

Item 3 was missing from the previous explanation.

This classification still leaves some sporks - for example, curry itself can be used manually, but automatic currying is only available as the with curry macro - but perhaps it's close enough as a short general explanation.

@aisha-w
Copy link
Contributor Author

aisha-w commented Sep 9, 2019

Thanks for reviewing! I'll look at the changes you've raised, and look into adding the distinction between the three sets of features.

@Technologicat
Copy link
Owner

Great!

Looking forward to the update!

@aisha-w
Copy link
Contributor Author

aisha-w commented Sep 18, 2019

Alright! I've finished up the changes and adding further explanation about the types of features offered.

@Technologicat Technologicat merged commit 3263291 into Technologicat:master Sep 24, 2019
@Technologicat
Copy link
Owner

Great work. Merged!

Thanks for your contribution!

@aisha-w
Copy link
Contributor Author

aisha-w commented Sep 26, 2019

Thank you! If you have any more documentation needs, feel free to ping me :-)

@Technologicat
Copy link
Owner

Ok, I'll drop you a note here when I have finished the documentation for the new features for 0.14.2. :)

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

Successfully merging this pull request may close these issues.

2 participants