Skip to content

noctuid/parinfer-notes

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 

Repository files navigation

Summary

Parinfer’s goal is to simplify how lisp is written and appeal to newcomers. It does this by using indentation-based inference to properly balance parentheses and allow for convenient functionality without requiring the use of any complicated keybindings. It currently has about as many stars on github as both lispy and smartparens combined.

I think parinfer is an interesting idea, and the fact that there are many implementations for various editors may make it a good starting point for those new to writing lisp (those who never try using emacs for writing lisp will probably miss out on a lot of functionality though).

However, I think that its biggest flaw is also its core principle. With indentation-based inference, the location of parentheses is fragile and waiting to be unknowingly broken. This requires the user to manually re-indent or know when to change to parinfer’s other mode of operation. Unless parinfer can somehow manage to automate this switching (see issue 86), I think that the cognitive load of having to know when parinfer’s “indent mode” will break your code actually complicates lisp editing, especially for a beginner.

If parinfer can add a “hybrid mode” that reliably prevents this issue, then I think it would become a decent editing method for newcomers to lisp. That said, it still lacks the power of other lisp editing styles. Compare it to lispy which has all of parinfer’s example functionality and more. Lispy also primarily uses unmodified letter keys for its functionality, but it provides more commands, and its commands are more versatile. Furthermore, since key behavior in parinfer based on inference, it is not necessarily intelligently chosen. For example, is barfing the closing paren to the point when pressing ) really that useful? What about the behavior of DEL before a closing paren? On the other hand, lispy’s keybindings were deliberately chosen and are more useful by default in my opinion.

UPDATE: Parinfer’s “Smart Mode” has been around for a while. I don’t know how solid/stable it is as I haven’t tested it extensively (and it doesn’t seem to be mentioned on the main page), but parinfer has gotten much better at preventing these issues (the defn renaming example below works correctly now). Some of the downsides listed above/below are still valid (mostly with regards to lack of a lot of advanced and some basic functionality and less sane default behaviors; AFAIK parinfer still can’t handle unbalanced quotes in comments). I don’t think that indentation-based inferences is an ideal method for achieving lisp editing functionality both in terms of simplicity of implementation and usage; the number of issues that have had to be addressed is a testament to that. The method is also less intuitive to me. I prefer indentation to be automatically inferred from parens, not the other way around; I don’t like the idea of sexp structure changing because of whitespace changes. That all said, parinfer should absolutely be given credit as a widely available method for keeping parentheses balanced and for basic lisp-editing functionality that doesn’t have a huge learning curve.

Disclaimer About Emacs Demos

The emacs demos shown here are outdated and done with multiple packages. All of the parinfer examples are now possible using lispy (see the documentation for the parinfer compat commands). I’ll keep the demos as-is since they more closely (but not perfectly) mimic parinfer’s useful behavior without any indentation-based inference.

Downsides of Parinfer

Parinfer determines where parentheses should go based on the code’s indentation. For the most part, it gets around requiring manual indentation by auto-indenting to the correct position when hitting enter. However, any time you change the indentation (or even line length in some cases!) after the initial auto-indentation happens, you will have to manually re-indent. If you don’t want to manually re-indent everything yourself, you must switch to parinfer’s “paren mode”, make the change, and then switch back to “indent mode.”

For example, if you wanted to bring a sexp up to the preceding line in “indent mode”, you would either have to backspace to the beginning of the line and then backspace through every closing paren or use the mouse to select the area you want to delete:

parinfer_demos/parinfer-downsides-1.gif

parinfer_demos/parinfer-downsides-2.gif

In emacs, this could be done with a single press of backspace. In the demo boxes, parinfer’s “paren mode” still requires either spamming backspace or using the mouse. This could be changed to only require a single backspace, but it still would require two presses of some modifier keybinding (which parinfer claims it wants to avoid) to get in and out of “paren mode.” The real problem here is that space and backspace change indentation. If indentation-based inference was not used, they could be made smarter.

Also, in “paren mode” you get none of the benefits you would normally have with something like paredit or smartparens. There is no auto-closing, for example, and parinfer’s nice features won’t work. It won’t ensure that your parentheses are balanced either. These features could be added to “paren mode” to make it better, but the features “indent mode” gives can be implemented without the need for indentation-based inference in the first place.

Furthermore, parinfer will mess up your code in certain cases, even when you’re not changing the indentation at the beginning of a line:

parinfer_demos/parinfer-downsides-3.gif

Parinfer will also work incorrectly if you have unbalanced quotes in a comment.

These limitations mean that you have to know exactly when parinfer will break your code or at least pay careful attention to catch when it does so that you can undo your changes and then redo them in “paren mode.” Is this really something a beginner should be expected to cope with? Once you’re used to parinfer’s modes, you’re still wasting keypresses and effort every time you switch between them.

With the normal way of editing (no indentation-based inference), your code can always be correctly indented (see aggressive-indent). Parentheses won’t disappear and appear all the time and will never be moved to different spots unless you explicitly tell them to. Instead of allowing all keys that change indentation to alter the parentheses, you dedicate just two keys to do this (tab and shift-tab; see adjust-parens.el).

You can see here for other examples of where parinfer’s “indent mode” fails to have the desired behavior.

Other Notes

Parinfer will fix incorrect indentation when reading a file. I don’t really consider this to be a downside, but parinfer does list it as one. I do wonder how parinfer reacts to code with tabs and spaces though.

Benefits of Parinfer

Parinfer claims the following:

Inferring parentheses based on indentation seems to lead to simpler editing mechanics for Lisp code. It leads to a system that keeps our code formatted well. And it allows us to use paredit-like features without hotkeys.

I think the biggest win is its potential to quell fear of managing end-of-line parens by enforcing a direct driving relationship with indentation.

All of this can be achieved without any of parinfer’s methods and consequently without its downsides. As far as I am aware, there are no benefits that the parinfer method has for the user.

Gears

Parinfer has some pretty diagrams of gears representing the relationship between indentation and parentheses. It claims the following:

Existing tools automate some of these editing tasks. For example, Paredit forces you to transform or add parens in a balanced way through special hotkeys. And Auto-indent allows you to auto-correct indentation of selected lines when desired. This automates the tasks, but the back-and-forth actions are still manually triggered.

This is completely false. Again, refer to aggressive-indent which will automatically correct indentation without ever requiring the user to manually hit a key . The parinfer page even mentions aggressive-indent, so I don’t know why it ignores it here.

Parinfer also claims the following:

Parinfer is a new tool to combine and simplify this type of automation by naturally keeping Parens and Indentation in lockstep. It formally infers changes to one based on the other. The back-and-forth actions have been reduced with special modes, which we will explore next.

As stated in the last section, I will give examples showing that either parentheses or indentation can be changed without the need to have two distinct modes. You can think of this as being able to spin either gear without having to switch contexts.

Parinfer Examples Done Without Inference

The demos I will be referring to can be found here.

Note that for the first examples, parinfer does it in more keypresses than necessary by first moving the point to the beginning of the line. Parinfer will actually preserve the current scope when you hit enter normally. It also allows you to change scope by pressing ) to move the point past a closing paren much like you would in emacs.

Rearrange Parens with Indentation

emacs_demos/rearrange-parens.gif

This is done using the adjust-parens emacs plugin. This can also be done in reverse. Smartparens also has commands called sp-indent-adjust-sexp and sp-dedent-adjust-sexp that do the same thing, and lispy now has lispy-indent-adjust-parens and lispy-dedent-adjust-parens. The lispy implementation is the only one that works with a selection currently (which is why this old demo uses evil’s visual column mode). Counts are also supported.

Generally these commands are bound to tab and shift-tab. Unlike with parinfer, the parentheses are only adjusted when you explicitly choose to adjust them using these commands. Also note that tab will still indent a line correctly as it normally does if the indentation is incorrect.

Insert or Delete a Line Without Rearranging Parens

emacs_demos/adding-and-deleting-lines-region.gif

The above example shows that a selected region can be deleted without unbalancing parentheses just like in parinfer (without the need for “special hotkeys”). This can be done with lispy, lispyville, evil-cleverparens, etc.

emacs_demos/adding-and-deleting-lines-evil.gif

This second example is done using the evil-cleverparens plugin which allows evil’s deleting and changing commands to maintain paren balance (evil-smartparens or lispyville can also be used).

The downside to this approach is that all commands for deleting text must be wrapped to be safe. However, there is no burden on the user (only on the implementer), and all common commands for deletion already have safe versions in emacs (with multiple implementations in the case of evil commands).

That said, safe deletion, copying, and even pasting are all fairly simple to implement. I’ve created some functions that allow for these operations in lispy (see lispy--find-unmatched-delimiters for the basis for all of them).

Comment a Line Without Rearranging Parens

emacs_demos/comment-line.gif

This uses lispy-comment which has the same effect except that it only requires one press of ; to correctly format everything (including adding a space).

Basic Paredit Without Hotkeys

emacs_demos/basic-paredit-without-hotkeys.gif

In this example, I have bound DEL (backspace) to splice before an open paren or to slurp as far as possible before a close paren, ( to a command that wraps to the end of the line, and ) to a command that barfs to the point.

There is nothing about this that requires inference. The behavior is not exactly the same since parinfer could wrap further depending on the indentation, but consider that with lispy (see the relevant documentation) the behavior is always clearly defined. You can wrap an exact number of sexps or as far as possible.

I also think that the lispy defaults are more useful for DEL (deletes the entire sexp) and ) (jumps to closing paren). The fact that DEL slurps when you delete a closing paren in parinfer makes sense knowing how parinfer works but otherwise seems strange.

As a final note, lispy provides full paredit functionality using mostly unmodified letter keys.

Preserve indentation

See the demos for aggressive-indent.

Final Thoughts

These are the final thoughts listed for parinfer:

Regardless of how we choose to edit our Lisp code, there seems to always be a balancing act between maintaining the simplicity of how we interact with the editor and accepting some editor complexity to gain automation over these powerful but numerous parens.

Building the interactive examples for this page has allowed me to explore how well Parinfer can play this balancing act, but only in a demo environment. The real test will come once it becomes available to major editors. See editor plugins for progress.

I disagree with the conclusion reached. Lispy, as an example, still requires simple keypresses but allows for more powerful functionality. To me it seems that indentation-based inference adds needless complexity without providing any unique functionality.

About

My notes about parinfer

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published