Frame-Based Editing [pdf]
kclpure.kcl.ac.uk
kclpure.kcl.ac.uk
Every so often the concept of visual programming comes up, where people wish that instead of editing text they could somehow directly manipulate an AST. Wouldn't it be funny if it turned out that it was Lisp they were looking for all along?
For instance, I like to use Paredit [1] for elisp and Racket hacking, and there's a similar mode for Haskell in emacs [2].
[1] https://www.emacswiki.org/emacs/ParEdit [2] https://github.com/chrisdone/structured-haskell-mode
Hmmm. Updating my grammars to ANTLR 4.x's LL(Star) has been very rewarding. The resulting parse tree and AST are the same, if done carefully.
I've never been smart enough to create a structured, incremental editor. Eg two-way editing. Your comment nudges me to pondering trying again.
That looked totally painful, and not at all fun. Granted, much of this is due to the fact that it's a touch interface, but I'm not seeing it as very useful.
I'm a huge Lisp/Scheme/Clojure fan, but I didn't find this at all impressive.
That literally takes 10x longer to enter anything than it does in a decent editor. I imagine some vim and emacs wizards can edit even faster than that.
Dumb snark incoming but it's like using a hammer to chop wood.
Editing is still in progress but I have a read-only demo at https://gashlin.net/tests/ecola/hn/
That doesn't mean that the ways we use text these days for programming couldn't be improved and augmented. For example, the example in Figure 3 is very pretty and readable. But there is no reason why this couldn't be what you see when still editing everything as text. Basically, it could be a more advanced form of syntax highlighting. The reason why this is difficult/impossible to achieve with Java is because of Java's syntax.
Therefore I think when approaching programming from a user interface point of view, it is crucial to also work on a better syntax for the programming language itself. Ideally, work like described in this paper should go hand in hand with work to develop better syntaxes.
Representing the flow of data and not the program is one advantage that many functional languages have, at the cost of removing the visualization of the program's flow. Often this doesn't matter, but sometimes it does. When it does matter, you're faced with the same challenge for the programmer.
So ultimately, even the best syntax can only help you model one thing - either the flow of the data or the flow of the program - and the other flow must be modeled internally. It would be cool if both could be visualized by our tooling simultaneously.
I've just recorded a video where I'll refactor some code for fun. This code adds two arrays one has strings in it like ["1" "2" "3"] the other has numbers like [1 2 3].
Video: https://s3.us-east-2.amazonaws.com/photoblobs/2017-06-22_003...
I do have a couple typos / mis-compiles but that's sort of par for the course. Hope you enjoy!
- Indentation based coloring of whitespace
- Nesting-based coloring of parentheses
You can try it out here:
https://broxp.github.io/ruby-ide
Of course, these ideas are not new.
There are plugins for IDEs for some languages, one that I recall is the F# depth colorizer:
https://cyanbyfuchsia.wordpress.com/2013/11/18/cool-visual-s...
And there is parentheses highlighting in Eclipse (when touched by the cursor).
I must be misunderstanding, how can this be more useful than plain text? Eliminating syntax errors?
while (...) {
Automatically inserted the next `}` and moved the cursor between it. But in the frame based editor, the `{` and `}` are elided from the presentation, replaced with a colored block or frame around the inner kernel of the while loop. +++ while (condition)
+ --- if (condition)
+ - *** actions
+ ------------------
++++++++++++++++++++++
Scratch (the example in Figure 1) is definitely a mouse driven interface, but beyond that and its visual presentation, it's more a cousin to the frame based model than a precise representation.It gives you a better (arguable?), structural way to view code, and to edit it with both text input and mouse.
(curmudgeon)A well-formed program is "defined" as what the compiler expects. Like literate programming, this hides from me what's being presented to the compiler, and sets me adrift from what the program actually is.(/curmudgeon)
It also reminds me vaguely of writing with Framemaker.
Isn't that the whole point of abstractions? Like, when you write a function call, you see "foo()" but the compiler sees all the individual instructions in the function. The same with macros, templates, etc.
And on a purely visual level, the example 'jtsummers gave just seems to be an extension of code folding and other tools that let you focus on one part of the code while ignoring another.
Abstractions are the wrong thing to chase.
Just because an abstraction will put you further away from a technical problem, doesn't mean will get you closer to solving your business problem.
Abstractions are transient. You want to use them when they help you, but it's good to be able to step down and verify what's going on.
Good, yes, desirable, often, but I wouldn't call it "essential" to the art of programming.
I once worked for a company whose data was managed by a COBOL application on a mainframe a couple hundred miles away; there was no walking down that abstraction ladder (without shelling out a five-figure support fee), so our in-house IT department had to take advantage of the fact that the 3270 terminal emulator we used to access the mainframe, could be driven through a COM interface.
I'm sure there are lots of other examples of jobs that don't have the luxury of being able to step down and verify lower-level code, and I wouldn't characterize them as missing out on some "essential" aspect of programming.
(idealist)Well, there's no reason the compiler has to accept programs only as plain text. It's actually a pretty poor representation of your program: the language has to go out of its way to parse it. Internally, your program is an AST; this editor works over frames, which are closer to the AST than they are to plain text; perhaps they could both use the same non-textual file format.(/idealist)
For that you have Lisps, which hit the sweet spot by being human-writable ASTs that are trivial to parse (at least if you don't play with reader macros too much).
An Imp is attacking my Feline Familiar - and my fireball wand grazing by a shit-ton of monsters and items and four long-bows..
http://d289qh4hsbjjw7.cloudfront.net/rpgmaker-20130522223546...
Greenfoot really was the biggest inspiration, but I'd really like to build something more general purporse. Aka pass in a grammar file and then you can use the editor for that language.
I could see this being one of the fundamental ways of overcoming Lisp bias. Lisp still isn't mainstream. Clojure was a nice attempt, but it fell short. You can find companies that use Clojure, but it's not the lingua franca of any domain (except perhaps text editors).
If you were to expose a way to write Lisp without dealing with any parens at all, it might have a chance of sparking the interest of younger programers long enough to seduce them to Lisp's other benefits: when you write in Lisp, you're writing in the abstract syntax tree normally generated by other languages. This allows you to write macros, which transform the tree in arbitrary ways. It's trivial to write a program to analyze your entire codebase in arbitrary ways (what are the most popular function calls? what's my dependency graph look like? which functions are unreferenced?) which is normally a herculean effort in other languages.
And it all comes down to syntax. `(if a (b) (or c d))` is so utterly foreign to most programmers compared to `if (a) { return b(); } else { return c || d; }` that it's nearly impossible to overcome the inertia long enough to persuade them that the tradeoff in readability is worth the power you get.
I think this work could be helpful here. When you throw a newcomer in front of a Lisp editor and say "Write a program," what goes through their minds? "How do I write an if statement?" "How do I call a function?" "How do I set a variable?" "How do I loop while a condition is true?" All of these are abstract concepts, not specific to any language, let alone Lisp. That'd let them avoid dealing with parens altogether. Then you can introduce the idea of a macro, which just combines new ways of using existing concepts.
Regarding color: I've often wished for colored quasiquotation. `(foo `(bar `(,baz))) would be more useful if your editor displayed the background color differently for each level of quasiquation. It would also de-mystify statements like (fn (x) `(define-macro foo (y) `(define ,y ,',x))) which can otherwise be incredibly difficult to reason about. (Thankfully nested quasiquotation is rare, but suffice to say I wish editors used color more effectively rather than stylistically.)
Sadly, emacs is one of the only editors flexible enough to implement the concepts presented in this paper, and the intersection between emacs users and novice programmers is about as large as the intersection between hackers and MBAs. But you can flip it around: emacs is flexible enough. That means you can ship a custom preconfigured emacs designed specifically for Lisp hacking, set up to mimic Atom/Coda/Sublime/whatever.
That's a very strong claim without much evidence. Most programmers routinely deal with multiple syntaxes many of which are weirder than and less readable than lisp. A more likely thing is that there just isn't a sufficiently useful lisp.
Don't worry, we can fix it.
I'm not worried but I appreciate the concern, I guess.
FOO=$(command ${path#${path#/*}})
stands in the way of Unix shell adoption. Also, this one hinders HTML adoption: <foo bar="attr"></foo>
This kind of thing (a direct snippet from the Linux kernel Makefile) hinders GNU Make adoption: $(if $(wildcard $(objtree)/.config),, $(error No .config exists, config your kernel first!))I look at the loader code I use in haskell w/ optparse-applicative and how much better it is than anything I've used in my damn life and I say, "Why is this considered unusable?"
Lisps just don't bring much to the table. Their dynamic-ness brings overhead that is not justified unless you're doing crazy macro stuff, and ad-hoc crazy macro stuff is not easy to put into code without hurting readability and comprehension.
Here's a sample session with SBCL, where I define a function, which is immediately compiled, and then I request a disassembly:
CL-USER> (defun foo (n m) (* (1+ m) n))
FOO
CL-USER> (disassemble #'foo)
; disassembly for FOO
; Size: 48 bytes. Origin: #x1007CED6F3
; 6F3: 498B4C2460 MOV RCX, [R12+96] ; thread.binding-stack-pointer
; no-arg-parsing entry point
; 6F8: 48894DF8 MOV [RBP-8], RCX
; 6FC: 488B55E8 MOV RDX, [RBP-24]
; 700: BF02000000 MOV EDI, 2
; 705: 41BBC0010020 MOV R11D, 536871360 ; GENERIC-+
; 70B: 41FFD3 CALL R11
; 70E: 488B7DF0 MOV RDI, [RBP-16]
; 712: 41BBA0020020 MOV R11D, 536871584 ; GENERIC-*
; 718: 41FFD3 CALL R11
; 71B: 488BE5 MOV RSP, RBP
; 71E: F8 CLC
; 71F: 5D POP RBP
; 720: C3 RET
; 721: CC10 BREAK 16 ; Invalid argument count trap CL-USER> (disassemble #'foo)
; disassembly for FOO
; Size: 19 bytes. Origin: #x100784BCCB
; CB: 48FFC0 INC RAX ; no-arg-parsing entry point
; CE: 480FAFC1 IMUL RAX, RCX
; D2: 48D1E0 SHL RAX, 1
; D5: 488BD0 MOV RDX, RAX
; D8: 488BE5 MOV RSP, RBP
; DB: F8 CLC
; DC: 5D POP RBP
; DD: C3 RET
As far as overhead goes, the SHL makes room for tag bits (and notice that's only done once, not per math op), and CLC indicates that the function doesn't return multiple values. That is not significant overhead, and will absolutely destroy reference Python in speed. (defun foo (n m)
(declare (type fixnum m n) (optimize (safety 0)))
(the fixnum (* (1+ m) n)))The optimization declaration can be made globally, or per-file; declaring the type of the foo function (including parameters & return values) externally will fully cover this function without having anything additional in the body; there's helper macros to explicitly manage static types, etc.
So the type declarations don't need to be intrusive into the expressions themselves, and can be as transparent as you want. Of course, type inference also means that you don't have to specify every single type, while still getting the speed & checking benefit of fully typed code.
(declaim (ftype (function (fixnum fixnum) fixnum) foo) (optimize (safety 0)))
(defun foo (m n) (* (1+ m) n))
That didn't optimize as fully. (declaim (ftype (function (fixnum fixnum) fixnum) foo)
(optimize (safety 0) (speed 3))
(defun foo (m n) (* (1+ m) n))
should have produced the optimized code (without any generic-*)? It didn't in my testing on SBCL 1.3.18.I think that is today's major barrier to Lisp, but not in the way you think.
Most of Lisp's inventive features have crept into other languages by now, except for the in-language compile-time code transformation/generation (ie macros & homoiconicity).
The major phrase I want to point out is "crazy macro stuff". To people from other programming languages, code generation and metaprogramming are way out there concepts, only suitable for insane scenarios. People said the same thing about functional programming, yet any well-seasoned programmer today has a reasonable handle on its usefulness and advantages, and FP style is now used by programmers in many languages to contain complexity.
Lisp-style macro programming is a simple way to do metaprogramming, while in other languages it's an impedance-mismatch-riddled nightmare of separate wonky build steps. Once it is made simple, it's easy to add great conveniences, compile-time optimizations, and architectural support to your programming, with almost no-brainer effort.
It is also much easier to human-parse Lisp code which utilizes a good per-project macro library, than it is to parse other languages with boilerplate and sprawling behavioral dependencies injected all over their usage scenarios.
The catch-22 is that this style of super-easy metaprogramming is linked to code-is-data-is-code homoiconicity, and people still reject that at face value, which slows the propagation of metaprogramming (however, I think there is a continually slow-rising acceptance). Functional programming doesn't require any unique syntax, just semantics, so it was able to spread without that barrier.
Macros make a tremendous contribution to readability and comprehension. I'm informed by 17 years of Lisp experience versus your zero.
Hacking up a friendly version of emacs does sound like an interesting project. I suspect it's an uphill battle to convince people to use it, but someone should try. :)
I would not want to program like that.
Looking forward there will be books on programming with FBE. fbeop will be all the rage. Naturally we'll discuss the form of code. Is that bulge too far out? What is better: a plain of declarations that meets a mountain range of control loops and flow, or, is it better to "sprinkle the frame" with mini frames of plains and hills? There will be debates. Conferences will be held. Papers will be presented with analysis of old school code rendered in FBE. There will also be attempts to finally bring generics to Go, with FBE frontends with little angular < slots >. Efforts begin to teach monkeys to code.
[p.s. and where exactly is that crab in Fig. 1? Very confusing ..]