š Repl Driven Development: /Editor Integrated REPLs for all languages/ š

Ī Ā
- 1. A Rapid Overview of RDD
- 1.1. How do people usually code? š¤ļø
- 1.2. If only we could have our cake, and eat it too! š°
- 1.3. Say that again, but use Python please! š
- 1.4. Technically speaking, how is Emacs itself the REPL? š¤
- 1.5. š Summarising Evaluator Pitch ā¾
- 1.6. š¤ šŖ š¤ Features of RDD.el šŖ š¤ šŖ
- 2. Teaching a runtime, green:incrementally, to be a web server š½ļø š š¤
- 2.1. The āš³ as teachingā meme
- 2.2. <em>Wait, I already do this RDD stuff everyday, in the shell!</em>
- 2.3. withJSON: Alter the contents of a JSON file as if it were a JavaScript object
- 2.4. shell: Run a shell command and provide its result as a string; or crash when there's an error
- 2.5. Quick Example of using JS as a command-line-language
- 2.6. Java: run a shell command and see the output
- 2.7. Programming Ā Ā āĀ Ā Definitions and re-definitions
- 3. Concluding Remarks
- Appendix: Interesting Reads
- Appendix: Recipes for a number of languages
The package makes the philosophy of REPL Driven
Development (RDD) accessible to any language that has a primitive CLI repl: The
result is an Emacs interface for the language, where code of your choosing is
evaluated, and results are echoed at your cursor in overlays.
That is, with Repl aided development, you make software by starting with an already working program (i.e., the repl) then incrementlly āteach itā to be the program you want, by defining & redefining things. Until satisfied, loop: Type/modify code in your editor, press some keys to evaluate what you wrote/modified in the currently running system, and explore/test the resulting runtime.
RDD is programming emphasising fast & rich feedback from a running system. RDD is fantastic for quickly teaching/exploring an idea; as such, the running example of this article will be on servers —no prior experience with servers is assumed. The main examples will be in JavaScript, Python, and Java. (Since JavaScript is just Lisp in C clothing, we will not discuss Lisp.) Since Java is verbose, the power of REPLs really pays off when exploring a new idea. We see how many imports and setup-code simply disappear in the RDD approach, letting you focus on the core idea you're exploring/teaching. For comparison, a traditional self-contained Java server program is ~30 lines long whereas the focused RDD approach is ~4 lines long.
tdlr: This library provides the Emacs built-in C-x C-eC-x C-e ā· eval-last-sexp
Evaluate sexp before point; print value in the echo area.
Interactively, EVAL-LAST-SEXP-ARG-INTERNAL is the prefix argument.
With a non ā-ā prefix argument, print output into current buffer.
This commands handles ādefvarā, ādefcustomā and ādeffaceā the
same way that āeval-defunā does. See the doc string of that
function for details.
Normally, this function truncates long output according to the
value of the variables āeval-expression-print-lengthā and
āeval-expression-print-levelā. With a prefix argument of zero,
however, there is no such truncation.
Integer values are printed in several formats (decimal, octal,
and hexadecimal). When the prefix argument is -1 or the value
doesnāt exceed āeval-expression-print-maximum-characterā, an
integer value is also printed as a character of that codepoint.
If āeval-expression-debug-on-errorā is non-nil, which is the default,
this command arranges for all errors to enter the debugger.
(fn EVAL-LAST-SEXP-ARG-INTERNAL)
behaviour for
arbitrary languages, provided they have a primitive cli REPL.
1. A Rapid Overview of RDD
1.1. How do people usually code? š¤ļø
Either you,
- Use a code editor and edit multiple lines, then jump into a console to try out what you wroteā³, or
- You use an interactive command line, and work with one line at a time —continuously editing & evaluating š
The first approach sucks because your code and its resulting behaviour occur in different places š¢ The second is only realistic for small experimentation —after all, you're in a constrained environment and don't generally have the same features that your code editor provides š§āāļø
1.2. If only we could have our cake, and eat it too! š°
With an editor-integrated REPL, we get both approaches! No need to switch
between the two any more! For example, Emacs out-of-the-box lets us just select
some code and press C-x C-eC-x C-e ā· eval-last-sexp
Evaluate sexp before point; print value in the echo area.
Interactively, EVAL-LAST-SEXP-ARG-INTERNAL is the prefix argument.
With a non ā-ā prefix argument, print output into current buffer.
This commands handles ādefvarā, ādefcustomā and ādeffaceā the
same way that āeval-defunā does. See the doc string of that
function for details.
Normally, this function truncates long output according to the
value of the variables āeval-expression-print-lengthā and
āeval-expression-print-levelā. With a prefix argument of zero,
however, there is no such truncation.
Integer values are printed in several formats (decimal, octal,
and hexadecimal). When the prefix argument is -1 or the value
doesnāt exceed āeval-expression-print-maximum-characterā, an
integer value is also printed as a character of that codepoint.
If āeval-expression-debug-on-errorā is non-nil, which is the default,
this command arranges for all errors to enter the debugger.
(fn EVAL-LAST-SEXP-ARG-INTERNAL)
---E for Evaluate! š
;; Press “control and x, then control and e”, at the end of the line, to run the following code
(message-box "hello world")
The software gives us this feature for any
language! For example, Press C-x C-e
C-x C-e ā· eval-last-sexp
Evaluate sexp before point; print value in the echo area.
Interactively, EVAL-LAST-SEXP-ARG-INTERNAL is the prefix argument.
With a non ā-ā prefix argument, print output into current buffer.
This commands handles ādefvarā, ādefcustomā and ādeffaceā the
same way that āeval-defunā does. See the doc string of that
function for details.
Normally, this function truncates long output according to the
value of the variables āeval-expression-print-lengthā and
āeval-expression-print-levelā. With a prefix argument of zero,
however, there is no such truncation.
Integer values are printed in several formats (decimal, octal,
and hexadecimal). When the prefix argument is -1 or the value
doesnāt exceed āeval-expression-print-maximum-characterā, an
integer value is also printed as a character of that codepoint.
If āeval-expression-debug-on-errorā is non-nil, which is the default,
this command arranges for all errors to enter the debugger.
(fn EVAL-LAST-SEXP-ARG-INTERNAL)
at the end of the following line to get
an Emacs-integrated REPL for Java —i.e.,
C-x C-jC-x C-j ā· dired-jump
Jump to Dired buffer corresponding to current buffer.
If in a buffer visiting a file, Dired that fileās directory and
move to that fileās line in the directory listing.
If the current buffer isnāt visiting a file, Dired ādefault-directoryā.
If in Dired already, pop up a level and goto old directoryās line.
In case the proper Dired file line cannot be found, refresh the Dired
buffer and try again.
When OTHER-WINDOW is non-nil, jump to Dired buffer in other window.
When FILE-NAME is non-nil, jump to its line in Dired.
Interactively with prefix argument, read FILE-NAME.
(fn &optional OTHER-WINDOW FILE-NAME)
will now
evaluate a selection, or the entire line, as if it were Java code.
- Why C-x C-j
C-x C-j ā· dired-jump Jump to Dired buffer corresponding to current buffer. If in a buffer visiting a file, Dired that fileās directory and move to that fileās line in the directory listing. If the current buffer isnāt visiting a file, Dired ādefault-directoryā. If in Dired already, pop up a level and goto old directoryās line. In case the proper Dired file line cannot be found, refresh the Dired buffer and try again. When OTHER-WINDOW is non-nil, jump to Dired buffer in other window. When FILE-NAME is non-nil, jump to its line in Dired. Interactively with prefix argument, read FILE-NAME. (fn &optional OTHER-WINDOW FILE-NAME)
Ā ?Ā Well, C-x C-āeā for Emacs Lisp code, and C-x C-ājā for Java code!
;; C-x C-j now evaluates arbitrary Java code
;; ⟦Emacs spawns a new “jshell” process, then “C-x C-j” sends text to that process.⟧
(repl-driven-development [C-x C-j] "jshell" :prompt "jshell>")
We can now press C-x C-jC-x C-j ā· dired-jump
Jump to Dired buffer corresponding to current buffer.
If in a buffer visiting a file, Dired that fileās directory and
move to that fileās line in the directory listing.
If the current buffer isnāt visiting a file, Dired ādefault-directoryā.
If in Dired already, pop up a level and goto old directoryās line.
In case the proper Dired file line cannot be found, refresh the Dired
buffer and try again.
When OTHER-WINDOW is non-nil, jump to Dired buffer in other window.
When FILE-NAME is non-nil, jump to its line in Dired.
Interactively with prefix argument, read FILE-NAME.
(fn &optional OTHER-WINDOW FILE-NAME)
to execute any Java code, and see results echoed inline in an overlay, such as:
1 + 2 // ⇒ 3
System.out.println(2 + 3) // ⇒ 5
"hello"[0] // 🚫 Type error: Java strings are not arrays
IntStream.range(0, 100).toArray() // A multi-line overlay of numbers 0..99
import javax.swing.*; // (No output; this is an effectful operation)
var frame = new JFrame(){{ setAlwaysOnTop(true); }}; // ⇒ ‘frame’ is defined
JOptionPane.showMessageDialog(frame, "Super nice!"); // ⇒ A GUI appears 💝
š This extension is an easy Emacs-integrated-REPL builder for any language! š²
1.3. Say that again, but use Python please! š
Details
With an editor-integrated REPL, we get both approaches! No need to switch
between the two any more! For example, Emacs out-of-the-box lets us just select
some code and press C-x C-eC-x C-e ā· eval-last-sexp
Evaluate sexp before point; print value in the echo area.
Interactively, EVAL-LAST-SEXP-ARG-INTERNAL is the prefix argument.
With a non ā-ā prefix argument, print output into current buffer.
This commands handles ādefvarā, ādefcustomā and ādeffaceā the
same way that āeval-defunā does. See the doc string of that
function for details.
Normally, this function truncates long output according to the
value of the variables āeval-expression-print-lengthā and
āeval-expression-print-levelā. With a prefix argument of zero,
however, there is no such truncation.
Integer values are printed in several formats (decimal, octal,
and hexadecimal). When the prefix argument is -1 or the value
doesnāt exceed āeval-expression-print-maximum-characterā, an
integer value is also printed as a character of that codepoint.
If āeval-expression-debug-on-errorā is non-nil, which is the default,
this command arranges for all errors to enter the debugger.
(fn EVAL-LAST-SEXP-ARG-INTERNAL)
---E for Evaluate! š
;; Press “control and x, then control and e” to run the following code
(message-box "hello world")
The software gives us this feature for any
language! For example, Press C-x C-e
C-x C-e ā· eval-last-sexp
Evaluate sexp before point; print value in the echo area.
Interactively, EVAL-LAST-SEXP-ARG-INTERNAL is the prefix argument.
With a non ā-ā prefix argument, print output into current buffer.
This commands handles ādefvarā, ādefcustomā and ādeffaceā the
same way that āeval-defunā does. See the doc string of that
function for details.
Normally, this function truncates long output according to the
value of the variables āeval-expression-print-lengthā and
āeval-expression-print-levelā. With a prefix argument of zero,
however, there is no such truncation.
Integer values are printed in several formats (decimal, octal,
and hexadecimal). When the prefix argument is -1 or the value
doesnāt exceed āeval-expression-print-maximum-characterā, an
integer value is also printed as a character of that codepoint.
If āeval-expression-debug-on-errorā is non-nil, which is the default,
this command arranges for all errors to enter the debugger.
(fn EVAL-LAST-SEXP-ARG-INTERNAL)
on the following line to get
an Emacs-integrated REPL for Python:
;; C-x C-p now evaluates arbitrary Python code
(repl-driven-development [C-x C-p] "python3")
We can now press C-x C-pC-x C-p ā· mark-page
Put mark at end of page, point at beginning.
A numeric arg specifies to move forward or backward by that many pages,
thus marking a page other than the one point was originally in.
(fn &optional ARG)
to execute any Python code, such as:
import os # ⇒ Module OS imported
f"Hello, {os.environ['USER']}!" # ⇒ “Hello, musa!”
More exciting fun is to produce an increasing family of colourful circles, in a GUI:
# Ensure we have some GUI capabilities installed; press “C-x C-e”:
# (async-shell-command "brew install python-tk")
import turtle
it = turtle.Turtle() # This opens a new window
# The following fragment was “discovered” by trying things out repeatedly with “C-x C-p”.
for i in range(10):
it.pencolor("green" if i % 2 == 0 else "red")
it.pensize(i / 2)
it.circle(i * 10)
# Note for Python, the above “for” loop is “complete” if we also send the extra
# newline after it.
Learn more with The Beginner's Guide to Python Turtle.
TODO: Make this into a Gif, that incrementlly shows the turtle appearing? ie it starts off with an experiment of the loop body, then it wraps it in the for, then re-runs and all of this is discovered live!
š This extension is an easy Emacs-integrated-REPL builder for any language! š²
1.4. Technically speaking, how is Emacs itself the REPL? š¤
Let's do what math-nerds call proof by definition-chasing:
- Definition: REPL is any software that supports a Read-Evaluate-Print-Loop cycle.
- C-x C-e
C-x C-e ā· eval-last-sexp Evaluate sexp before point; print value in the echo area. Interactively, EVAL-LAST-SEXP-ARG-INTERNAL is the prefix argument. With a non ā-ā prefix argument, print output into current buffer. This commands handles ādefvarā, ādefcustomā and ādeffaceā the same way that āeval-defunā does. See the doc string of that function for details. Normally, this function truncates long output according to the value of the variables āeval-expression-print-lengthā and āeval-expression-print-levelā. With a prefix argument of zero, however, there is no such truncation. Integer values are printed in several formats (decimal, octal, and hexadecimal). When the prefix argument is -1 or the value doesnāt exceed āeval-expression-print-maximum-characterā, an integer value is also printed as a character of that codepoint. If āeval-expression-debug-on-errorā is non-nil, which is the default, this command arranges for all errors to enter the debugger. (fn EVAL-LAST-SEXP-ARG-INTERNAL)
/ C-x C-jC-x C-j ā· dired-jump Jump to Dired buffer corresponding to current buffer. If in a buffer visiting a file, Dired that fileās directory and move to that fileās line in the directory listing. If the current buffer isnāt visiting a file, Dired ādefault-directoryā. If in Dired already, pop up a level and goto old directoryās line. In case the proper Dired file line cannot be found, refresh the Dired buffer and try again. When OTHER-WINDOW is non-nil, jump to Dired buffer in other window. When FILE-NAME is non-nil, jump to its line in Dired. Interactively with prefix argument, read FILE-NAME. (fn &optional OTHER-WINDOW FILE-NAME)
will echo the results next to your cursor, in your editor - So it retains each of the read, eval, and print parts of the Read-Evaluate-Print-Loop
- Moreover, since the program doesn't terminate, you're still in the loop part until you close Emacs
1.5. š Summarising Evaluator Pitch ā¾
Make Emacs itself a REPL for your given language of choice
Suppose you're exploring a Python/Ruby/Java/JS/TS/Haskell/Lisps/etc API, or experimenting with an idea and want immediate feedback. You could open a terminal and try things out there; with no editor support, and occasionally copy-pasting things back into your editor for future use. Better yet, why not use your editor itself as a REPL.
The software provides the Emacs built-in
C-x C-e
C-x C-e ā· eval-last-sexp
Evaluate sexp before point; print value in the echo area.
Interactively, EVAL-LAST-SEXP-ARG-INTERNAL is the prefix argument.
With a non ā-ā prefix argument, print output into current buffer.
This commands handles ādefvarā, ādefcustomā and ādeffaceā the
same way that āeval-defunā does. See the doc string of that
function for details.
Normally, this function truncates long output according to the
value of the variables āeval-expression-print-lengthā and
āeval-expression-print-levelā. With a prefix argument of zero,
however, there is no such truncation.
Integer values are printed in several formats (decimal, octal,
and hexadecimal). When the prefix argument is -1 or the value
doesnāt exceed āeval-expression-print-maximum-characterā, an
integer value is also printed as a character of that codepoint.
If āeval-expression-debug-on-errorā is non-nil, which is the default,
this command arranges for all errors to enter the debugger.
(fn EVAL-LAST-SEXP-ARG-INTERNAL)
behaviour for arbitrary languages, provided they have a primitive
cli REPL.
Benefits
Whenever reading/refactoring some code, if you can make some of it self-contained, then you can immediately try it out! No need to load your entrie program; nor copy-paste into an external REPL. The benefits of Emacs' built-in āC-x C-eā for Lisp, and Lisp's Repl Driven Development philosophy, are essentially made possible for arbitrary languages (to some approximate degree, but not fully).
Just as āC-u C-x C-eā inserts the resulting expression at the current cursour position, so too all repl-driven-development commands allow for a C-u prefix which inserts the result. This allows for a nice scripting experience where results are kept for future use —e.g., when writing unit tests where you have an expression but do not know what it results to.
1.6. š¤ šŖ š¤ Features of RDD.el šŖ š¤ šŖ
- š Evaluation results are echoed at your cursor, in your editor, by your code, in an overlay
- š You can specify whatever keys you want, for evaluating code. That keybinding is itself well-documented, just invoke C-h k then your keybinding.
- 𩹠Press C-u C-x C-j to get the results of your evaluated expression printed inline, at your cursor.
- š Documentation is supported out of the box: Put the cursor over a function name (like "print" or "error"). Then press C-u C-u C-x C-j and you get the documentation of that function.
2. Teaching a runtime, incrementally, to be a web server š½ļø š š¤
RDD by example: Let's start with a JavaScript runtime and incrementally turn it into a web server.
āRDD ā Programming as Teachingā: Start from a program that already works and āteach itā to be the program we actually want. This makes programming a goal-directed activity.
Below we demonstrate this idea by starting a runtime and, like talking to a person, we teach it new behaviours. Once it has all the desired behaviours, then we're done and the text we've written (in our editor) is the resulting program. Most importantly, we actively interact with the running program as it evolves; where each āteaching stepā is influenced by observing the program's reactions to various stimuli (e.g., how things look, how they function, etc).
2.1. The āš³ as teachingā meme
Details
- The āš³ as teachingā meme is about accomplishing the goal š³ as if you were talking to a friend in-person, explaining how to do something.
- Almost everything in programming can stand-in for š³; e.g., writing a function or a git commit is a good way to āteachā your colleagues how to improve the code-base —as such, if the function/commit does ātoo muchā then it is a āpoor teacherā and so not ideal.
- Related video: āHow to Write a Great Research Paper (7 Excellent Tips)ā by Simon Peyton Jones.
2.2. <em>Wait, I already do this RDD stuff everyday, in the shell!</em>
Details
You can ādiscoverā a bash script by running various incantations at the terminal, pressing the up-arrow key, tweaking your incantation —and repeating until you're happy with the result. In this way, you are teaching the shell a new skill —by repeatedly checking whether it can perform the skill and if not, then refining your definitions.
Anytime you execute a query, in some system, you're using a read-evaluate-print-loop!
Examples include: Writing shell & SQL queries, visiting web-pages by writing URLs, exploring HTTP APIs using curl/httpie, and using the JavaScript Console in your browser.
Sadly, the interface to such REPLs is generally very limited. There is no syntax highlighting, no code completion, no linting, it is difficult to work with multi-line input. This article proposes instead to use your editor as the interface to a REPL: You write some code in your feature-rich editor then press some keys to have only the newly written code executed.
RDD let's you use your favourite language as a Bash replacement scripting language
If your code-base is in language šæ, might as well write your scripts in šæ as well!
For example, if you want to say run a simple for-loop on a payload of an HTTP request then might as well use your favourite language šæ —and not Bash. Likewise, want to run a for-loop on the results of a SQL query: Use your favourite language šæ, not a SQL scripting language that you're not terribly comfortable with.
Why script in your favourite language šæ, and not Bash?
- If your an šæ language developer, writing scripts in šæ lets you make use of all of your existing experience, knowledge, and familiar tool-set of šæ.
- Stay in the comfort of your favourite IDE: Autocomplete, syntax highlighting, docs, tooltips, linting, etc.
- Lots of libraries!
- The gain in expressivity & clarity & test-ability.
- Rich data structures, error checking, and compositionality.
- Since Bash only has unstructured data via strings, this means to compose two different Bash programs you have to get them to āunderstand a common structureā and this means you have to convert unstructured data to JSON somehow (e.g., using jc, which JSONifies the output of many CLI tools) or parse it yourself! Might as well use your favourite language, since it probably has support for JSON and has real structured objects.
- An šæ-REPL is a shell with šæ-syntax, and features! —Since you're actually using šæ.
- Bash is imperative, but your favourite language is (probably) multi-paradigm —you can do imperative or more!
- By trying out API calls in your language šæ instead of Bash, you get working code in your language right away that you can build an app around —no need to figure out how to do that later on in your language.
The next time you need to write a loop in Bash, consider breaking out your REPL and seeing what you can come up with instead!
Slightly paraphrasing from: How to Replace Bash with Python as Your Go-To Command Line Language
āBash ⦠JavaScriptā Personal anecdote: One time I automated a bunch of tedious tasks at work with Bash by using jc, which JSONifies the output of many CLI tools, alongside jq, a JSON query language; along with a friendly-alternative to curl known as httpie. However, as the Bash incantations grew larger and larger, it became more practical to switch to JavaScript and read the http payloads as proper JavaScript objects (rather than use jc), and quickly work with them via the usual JS methods .map, .filter, .reduce. With Bash, I used jq and it's special syntax, but with JavaScript I just use JS in both places š Finally, this automated work required updating JSON configurations, but I wanted the result to be pretty-printed for future human readers. Since JSON is literally JS, it's most natural to use JS to work with JSON and so that's what I did. Below are 2 very useful methods from this Bashā¦JavaScript move.
2.3. withJSON: Alter the contents of a JSON file as if it were a JavaScript object
Details
/** Alter the contents of a JSON file as if it were a JavaScript object.
*
* - `path : string` is a filepath to a `.json` file.
* - `callback : function` is a (possibly async) function that mutates a given JS object.
* - `newFile : boolean` indicates whether this is a completely new file, in which case `callback` is provided with an empty object.
*
* Trying to access a JSON file that does not exist, when not enabling `newFile`, will result in an error.
*
* Write the JSON file, and format it nicely.
*
* ### Example use
* ```
* // Add a new `WOAH` key to the configuration file.
* withJSON("~/myConfig.json", data => data.WOAH = 12)
* ```
*
* ### Warning! ---Also Design Decision Discussion
*
* A purely functional approach would require `callback` to have the shape `data => {...; return data}`.
* However, we anticipate that most uses will be to update a field of `data` and so `callback` will
* have the shape `data => {data.x = y; return data}` and we want to reduce the ceremony: We work with mutable references,
* so that `data => data.x = y` is a sufficient shape for `callback`. However, this comes at the cost that we cannot
* wholesale alter a JSON file ---which is an acceptable tradeoff, since this is likely a rare use case.
*
* ```
* withJSON(`~/myfile.json`, data => data = {x: 1, y: 2}) // BAD! Will not alter the underyling JSON file.
* withJSON(`~/myfile.json`, data => {data.x = 1; data.y = 2}) // GOOD!
* ```
*
* A program should not just compute, it should also motivate, justify & discuss.
* This human nature makes it easier to follow, detect errors, use elsewhere, or extend.
* After all, the larger part of the life of a piece of software is maintenance.
*
* Flawed programs with good discussion may be of more use in the development of related correct code,
* than working code that has no explanation.
*/
function withJSON(file, callback, newFile) {
file = file.replace(/~/g, process.env.HOME)
try {
let data = newFile ? {} : JSON.parse(fs.readFileSync(file))
callback(data)
fs.writeFileSync(file, JSON.stringify(data, null, 2))
} catch (error) {
console.error(`🤯 Oh no! ${error}`)
console.error(callback.toString())
process.exit(0)
}
}
2.4. shell: Run a shell command and provide its result as a string; or crash when there's an error
Details
/** Run a shell command and provide its result as a string; or crash when there's an error.
* This is intentionally synchronous; i.e., everything stops until the command is done executing.
*
* @param {string} command - A Unix bash incantation to be executed.
* @param {boolean} ignore - Whether to actually avoid doing any execution; useful for testing/experimentation.
* @returns {string} The textual stdout result of executing the given command.
*
* - TODO: Consider switching to ShellJS.
* - Why ShellJS? https://julialang.org/blog/2012/03/shelling-out-sucks/
* - See also `Executing shell commands from Node.js`, https://2ality.com/2022/07/nodejs-child-process.html
*
* ### Examples
* ```
* // Who is the current user?
* console.log( shell('whoami') )
*
* // Make me smile!
* console.log( shell('fortune') )
*
* // See your Git credentials: Name, email, editor, etc.
* shell("git config --list")
*
* // Crashes if the provided command does not exist
* shell(`nonexistentprogram`); console.log(`You wont see this msg!`) // Boom!
*
* // Pass non-falsy second argument to invoke as a dry-run only
* shell(`nonexistentprogram`, true); console.log(`You WILL see this msg!`)
* ```
*
* Consider a program to be written primarily to explain to another human what it is that we want the computer to do,
* how it is to happen, and why we can believe that we have achievied our aim.
* (The “another human” might be you in a few months time when the details have escaped your mind.)
*/
function shell(command, ignore) {
return ignore
? `\n🤖 This is a DRY RUN, so I haven't done anything but I would have:\n🧪🧪🧪\n${command}\n🧪🧪🧪`
: require('child_process').execSync(command).toString().trim()
}
/** NodeJS dislikes `~` in file paths, so this helper lets you read files with `~` in their path.
* @param {string} path - The (possibily relative) path to a file
* @returns {string} The contents of the file located at the given path
*/
function readFile(path) {
return fs.readFileSync(path.replace(/~/, process.env.HOME))
}
2.5. Quick Example of using JS as a command-line-language
Details
var axios = require('axios')
var { name, blog, bio } = (await axios.get('https://api.github.com/users/alhassy')).data
2.6. Java: run a shell command and see the output
Details
execCmd("whoami") // ==> "musa\n"
// Source: https://stackoverflow.com/a/20624914/3550444
public static String execCmd(String cmd) throws java.io.IOException {
java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
}
Further reading:
Goal: Make an web server with a route localhost:3030/about that shows information about the user's environment variables. |
First,
;; C-x C-j now evaluates arbitrary JavaScript code, I'd also like docs for JS and Express
(repl-driven-development [C-x C-j] "node" :docs "javascript express")
Then, here's how we do this …
Visit http://localhost:3030/about, if that works, then we're done!
// First get stuff with C-x C-e:
// (async-shell-command "npm install -g express axios@0.21.1")
let app = require('/usr/local/lib/node_modules/express')()
let server = app.listen(3030) // 📚 Press “C-u C-u C-x C-j” to see docs about “listen” ;-)
// Now visit http://localhost:3030/
// ... and see “Cannot GET /”
// ... Neat, it works but it does nothing! Importantly it works!
// Let's add a route...
let visited = 1
app.get('/hi', (req, res) => res.send(`Hello × ${visited++}`))
// Now visit: http://localhost:3030/hi
// Refresh the page a few times 😉
// Excellent; let's add an end-point to return the variables in scope
app.get('/about', (req, res) => res.send(html()) )
// Whoops, there's no “html”! So we see an error!
// Let's define that!
let
html = _ => "<div style='color:green; background-color:cyan'>" + info() + "</div>"
// Whoops, there's no “info”! So we see an error!
// Let's define that!
let info = function () { return {visited, user: process.env.USER, time: new Date() } }
// Uh-oh, we see “[object Object]” since we didn't convert the
// JS object into a JSON string, so let's fix that!
html = _ => "<div style='color:green; background-color:cyan'>" + JSON.stringify(info(), null, 3 /* indentation */) + "</div>"
/* uh-oh, the output doesn't look good; let's redefine `html` using <pre> tags.
pre tells the browser engine that the content inside is pre-formatted and it can be displayed without any modification. So browser will not remove white spaces, new lines etc. code is for making it more semantic and denotes that the content inside is a code snippet. It has nothing to with formatting.
*/
html = _ => `<h1>Welcome, visitor ${visited++}!</h1><pre style='color:green; background-color:cyan'>` + JSON.stringify(info(), null, 3 /* indentation */) + "</pre>"
// Notice how we built this end-point from the top-down: We knew what we wanted, and saw some
// errors ---on the client side--- then fixed them right here, with no reloading!
// Actually, let's add more info: It's not enough to see the current user, let's see all environvment variable values
info = _ => ({user: process.env, time: new Date(), platform: os.platform(), architecture: os.arch(), home: os.homedir(), user: os.userInfo(), machine: os.machine()})
// So cool!
// Eventually, consider closing the server!
server.close()
TODO: Make the above into a short youtube video/*GIF*, where I keep
āimprovingā the definition of html / info and see it live!
RDD is about Unobtrusive Redefining
Notice that our demonstration above is mostly redefining things, making interactive observations about them, then redefining them to be better.
Most importantly, this redefining cycle is not impeded by the need to restart the program each time.
Instead, the already-working program ālearnsā what we have taught it —and continues to be a working program.
2.7. Programming Ā Ā āĀ Ā Definitions and re-definitions
Details
In the previous section we saw how easy it was to add & redefine things without having to restart our program; as such we have the motto āRDD ā Interactive Programmingā.
In RDD, we can re-define functions & types live, as the program is
running!
Future uses of the function/type will use the new definition!
In stark contrast, the traditinal approach forces us to restart the whole program whenever we make a modification, no matter how small! That's like rebuilding your entire house when you only wanted to put up a shelf! š¤®
| Let's explore the issue of redefinitions a bit more. |
If you define a function \(f\) and declare \(x = f()\), but then decide to redefine \(f\), what should happen to \(x\)? Well, \(x\) is already declared and already has a value, so nothing happens to it! If you want it to be the result of the re-defined \(f\), then re-evaluate \(x = f()\). š
However, when re-defining a data-type/class/record/struct, languages such as Java and Common Lisp, will insist that any previously defined instances now conform to the new data-type formulation! Likewise, for methods whose inputs are of the old formulation, they need to be updated to the new one.
Take a look at this interactive Java session…
record Person(String name) { }
var me = new Person("Musa"); // New instance.
// Can operate on it, using a functional *variable* or a *method*
Function<Person, String> speak = p -> p.name() + " says HELLO!"
String greet(Person p) { return "Hello, I'm " + p.name(); }
// Redefining our data-type
record Person(int age) { }
//
// ⇒
// record Person(int age) { }
// | replaced record Person
// | update replaced variable me which cannot be referenced until this error is corrected:
// | incompatible types: java.lang.String cannot be converted to int
// | var me = new Person("Musa");
// ⇒ As such, since “me” cannot be updated to be an instance of the reformulated data-type, it is kicked out of scope!
me // ⇒ No such variable is declared!
speak // ⇒ No such *variable* is declared!
greet // ⇒ No problem, but can only run it if you define a Person::name instance method! 😲
greet(new Person(12)) // ⇒ Attempted to call method greet(Person) which cannot be
// invoked until method name() is declared
Whereas Java says you can no longer use stale instances, Common Lisp tries to
āre-initialiseā existing instances —and prompts the user if it cannot do so
automatically. The Common Lisp approach may have benefits, but it comes at a
dangerous cost: Your runtime is now no longer tied to text you've written! It is
for this reason, that intentionally does not allow
users to run code in the
*REPL/āÆ* buffers: If you want to modify the running
system, write your modification down (in your working buffer, then save,) then
evaluate it.
3. Concluding Remarks
I've found RDD to be a fun way to code. I get fast feedback from my code as I design it, being able to test my assumptions against how the code actually works. When I'm satisfied with something, I codify that behaviour via unit tests so that I'm aware when my evolving design diverges —as I continue iterating on the design in the REPL.
Some languages have tight integration with Emacs!
Programs in these languages are essentially āconstructed incrementallyā by āinteractive conversationsā with Emacs (as the REPL).
The first such language is Common Lisp. Which also inspired a similar setup for Smalltalk —e.g., Pharo and Squeak.
I hope you've enjoyed this article!
Bye! š š„³
Appendix: Recipes for a number of languages
JavaScript —and a minimal server
javascript
We can set up a JavaScript REPL in the background as follows…
;; C-x C-j now evaluates arbitrary JavaScript code
(repl-driven-development [C-x C-j] "node -i")
That's it! Press C-x C-eC-x C-e ā· eval-last-sexp
Evaluate sexp before point; print value in the echo area.
Interactively, EVAL-LAST-SEXP-ARG-INTERNAL is the prefix argument.
With a non ā-ā prefix argument, print output into current buffer.
This commands handles ādefvarā, ādefcustomā and ādeffaceā the
same way that āeval-defunā does. See the doc string of that
function for details.
Normally, this function truncates long output according to the
value of the variables āeval-expression-print-lengthā and
āeval-expression-print-levelā. With a prefix argument of zero,
however, there is no such truncation.
Integer values are printed in several formats (decimal, octal,
and hexadecimal). When the prefix argument is -1 or the value
doesnāt exceed āeval-expression-print-maximum-characterā, an
integer value is also printed as a character of that codepoint.
If āeval-expression-debug-on-errorā is non-nil, which is the default,
this command arranges for all errors to enter the debugger.
(fn EVAL-LAST-SEXP-ARG-INTERNAL)
on the above line so that C-xĀ C-j will now
evaluate a selection, or the entire line, as if it were JavaScript code.
- Why C-xĀ C-j Ā ?Ā Well, C-x C-āeā for Emacs Lisp code, and C-x C-ājā for JavaScript code!
- For instance, copy-paste the following examples into a JavaScript file —or just press C-xĀ C-j in any buffer to evaluate them!
1 + 2 // ⮕ 3
1 + '2' // ⮕ '12'
let me = {name: 'Jasim'}; Object.keys(me) // ⮕ ['name']
me.doesNotExist('whoops') // ⮕ Uncaught TypeError
All of these results are echoed inline in an overlay, by default. Moreover, there is a REPL buffer created for your REPL so you can see everything you've sent to it, and the output it sent back. This is particularly useful for lengthy error messages, such as those of Java, which cannot be rendered nicely within an overlay.
How this works is that Emacs spawns a new ānode -iā process, then
C-x C-jC-x C-j ā· dired-jump
Jump to Dired buffer corresponding to current buffer.
If in a buffer visiting a file, Dired that fileās directory and
move to that fileās line in the directory listing.
If the current buffer isnāt visiting a file, Dired ādefault-directoryā.
If in Dired already, pop up a level and goto old directoryās line.
In case the proper Dired file line cannot be found, refresh the Dired
buffer and try again.
When OTHER-WINDOW is non-nil, jump to Dired buffer in other window.
When FILE-NAME is non-nil, jump to its line in Dired.
Interactively with prefix argument, read FILE-NAME.
(fn &optional OTHER-WINDOW FILE-NAME)
sends text to that process. Whenever the process emits
any output —on stdout or stderr— then we emit that to the
user via an overlay starting with āā®ā.
Finally, āC-h k C-x C-jā will show you the name of the function that is invoked when you press C-x C-j, along with minimal docs.
A useful example would be a minimal server, and requests for it.
// First get stuff with C-x C-e:
// (async-shell-command "npm install -g express axios")
let app = require('express')()
let clicked = 1
app.get('/hi', (req, res) => res.send(`Hello World × ${clicked++}`))
let server = app.listen(3000)
// Now visit http://localhost:3000/hi a bunch of times!
// Better yet, see the output programmatically...
let axios = require('axios')
// Press C-x C-j a bunch of times on the following expression ♥‿♥
console.log((await axios.get('http://localhost:3000/hi')).data)
// Consider closing the server when you're done with it.
server.close()
Just as āEmacs is a Lisp Machineā, one can use āVSCodeJSā to use āVSCode as a JS Machineā. See http://alhassy.com/vscode-is-itself-a-javascript-repl.
Python
python #add8e6
We can set up a Python REPL in the background as follows…
;; C-x C-p now evaluates arbitrary Python code
(repl-driven-development [C-x C-p] "python3 -i")
Example use…
1 + 2 # ⮕ 3
hello = 'world!' # (No output; this is an effectful operation)
print(hello) # ⮕ world!
2 + 'hi' # 🚫 TypeError: unsupported operand type(s) for +
Learn more by reading… Python: A Gentle Introduction to Socket Programming
Java
java
We can set up a Java REPL in the background as follows…
(repl-driven-development [C-x C-j] "jshell --enable-preview" :prompt "jshell>")
Now, we can select the following and press C-x C-j to evaluate the Java code:
// Ensure you're not fullscreen, and you'll see a dialog window appear.
import javax.swing.*;
JOptionPane.showMessageDialog(new JFrame(), "Super nice!");
Or doing algebraic datatypes in Java:
sealed interface Maybe {
record None() implements Maybe {}
record Just(int x) implements Maybe {}
}
var thisPrettyPrintsNicelyInTheREPL = new Maybe.Just(3);
new Maybe.Just(3).equals(new Maybe.Just(3)) // yay
Clojure
clojure
We can set up a REPL in the background as follows…
;; C-x C-k now evaluates arbitrary Clojure code
(repl-driven-development [C-x C-k] "clojure" :prompt "user=>")
For example…
(+ 1 2) ;; ā® 3
(defn square [x] (* x x)) ;; ā® #'user/square
(square 3) ;; ā® 9
Haskell
haskell #add8e6
We can set up a REPL in the background as follows…
;; C-x C-h now evaluates arbitrary Haskell code
(repl-driven-development [C-x C-h] "ghci" :prompt "ghci>")
For example…
-- Sum of the first 100 squares
sum [ x ** 2 | x <- [1..100]] -- ā 338350.0
-- The positive evens at-most 12
[x | x <- [1..12], x `mod` 2 == 0] -- [2,4,6,8,10,12]
-- Define a function...
myLast = head . reverse
-- Then use it...
myLast [1, 2, 3] -- ā 3
Note that Haskell has ātyped holesā with the syntax _A:
1 + _A -- ā Found hole: _A::a; it :: forall {a}. Num a = a
Another language with typed holes is Arend…
Arend: Quickly making a terse Emacs interface for a language without one
recipe
The Arend Theorem Prover has an IntelliJ interface (since it's a JetBrains proof assistant), but no Emacs counterpart —which may be annoying for Agda/Coq programmers accustomed to Emacs but want to experiment with Arend.
We can set up an Arend REPL in the background as follows…
;; C-x C-a now evaluates arbitrary Arend code
(repl-driven-development [C-x C-a]
(format "java -jar %s -i"
(f-expand "~/Downloads/Arend.jar")))
Then,
1 Nat.+ 1 -- ā 2
:type 4 -- ā Fin 5
-- Declare a constant
\\func f => 1
:type f -- ā Nat
f -- ā 1
-- Declare a polymorphic identity function, then use it
\\func id {A : \\Type} (a : A) => a
id 12 -- ā 12
-- Arend has ātyped holesā
1 Nat.+ {?} -- ā Nat.+{?}: Goal: Expectedtype: Nat
PureScript
purescript #add8e6
First brew install spago, then we can set up a PureScript REPL in the background
as follows…
;; C-x C-p now evaluates arbitrary PureScript code
(repl-driven-development [C-x C-p] "spago repl")
For example….
import Prelude
-- Define a function
add1 = (\x -> x + 1)
-- Use the function
add1 2 -- ā 3
-- Experiment with a typed hole
1 + ?A -- ā Hole ?A has the inferred type Int
Idris
idris
First brew install idris2, then we can set up an Idris REPL in the background as
follows…
;; C-x C-i now evaluates arbitrary Idris code
(repl-driven-development [C-x C-i] "idris2")
Here's some random code…
-- Like Lisp, Idris uses ātheā for type annotations
the Nat 4 -- ā 4 : Nat
with List sum [1,2,3] -- ā 6
-- defining a new type (REPL specific notation)
:let data Foo : Type where Bar : Foo
:t Bar -- ā Foo
-- Experiment with a typed hole [Same notation as Haskell]
1 + ?A -- prim__add_Integer 1 ?A
Racket
racket #add8e6
| Racket is a modern programming language in the Lisp/Scheme family. |
First brew install --cask racket, then we can set up an Racket REPL in the
background as follows…
;; C-x C-i now evaluates arbitrary Racket code
(repl-driven-development [C-x C-r] "racket -I slideshow")
Here's some random code…
(define (series mk) (hc-append 4 (mk 5) (mk 10) (mk 20)))
;; Shows 3 circles of increasing radius, in an external window
(show-pict (series circle))
Meeting Racket for the first time is probably best done with DrRacket.
Ruby
We can set up a REPL in the background as follows…
;; C-x C-r now evaluates arbitrary Ruby code
(repl-driven-development [C-x C-r] "irb --inf-ruby-mode" :prompt "irb(main):.*>")
For example…
2 + 2
33 + 4
5.times { print "Odelay!" } # ⮕ Odelay! Odelay! Odelay! Odelay! Odelay! 5
['ruby', 'is', 'readable'].map { | food | food.capitalize } # ⮕ ["Ruby", "Is", "Readable"]
require 'tk'
Life & Computing Science by Musa Al-hassy is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License