#+title: 💐 Repl Driven Development and The Wonders of <kbd>C-x C-e</kbd> 🔁, or <em>Editor Integrated REPLs for all languages</em>
#+description: Press “C-x C-e” to send any piece of code (in any language) to a REPL in the background, within Emacs!
#+property: header-args:emacs-lisp :tangle yes :exports code :eval never-export
#+options: d:nil toc:t
#+toc: headlines 2
#+author: Musa Al-hassy
#+email: alhassy@gmail.com
#+date: <2023-09-08 Fri>
#+fileimage: rdd-benefits.png
#+filetags: repl-driven-development vscode emacs javascript java python lisp clojure haskell arend purescript idris racket
#+begin_src latex-as-png :file ../images/rdd-benefits.pdf :resolution "520" :results raw value replace :exports none
\smartdiagram[constellation diagram]{{\sc REPL Driven \mbox{\hspace{-1em}Development}},
\mbox{Stay in the} \mbox{comfort of} \mbox{\hspace{-1em}your favourite} IDE,
Use your favourite language as a Bash replacement,
“Growing your program”,
Explore APIs \mbox{\hspace{-.4em}interactively},
\mbox{Easy way} \mbox{of creating} \mbox{test data}, “What You See Is What You Get”, Friendly way to learn a language \mbox{\hspace{-.5em}\footnotesize \& its libraries},
Rapid Design,
\mbox{\hspace{.7em}No build} {\footnotesize \mbox{i.e., change your} \mbox{running app } without \mbox{restarting it}},
\mbox{Immediate} Feedback
\iffalse Inspect living state \fi
}
{\newline \hphantom{\hspace{3em}} \color{gray}\texttt{www.alhassy.com/repl-driven-development}}
#+end_src
#+RESULTS:
* Abstract :ignore:
:PROPERTIES:
:CUSTOM_ID: Abstract
:END:
#+begin_center
badge:Warning|Incomplete_DRAFT|red||codeigniter
#+end_center
The melpa:repl-driven-development 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 green:aided development, you make software by starting with
an already working program (i.e., the repl) then green:incrementlly “teach it”
to be the program you want, by defining & redefining things. Until satisfied,
loop: Type/modify code green: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.
#+begin_center
badge:license|GNU_3|informational|https://www.gnu.org/licenses/gpl-3.0.en.html|read-the-docs
tweet:https://alhassy.com/repl-driven-development
badge:|buy_me_a_coffee|gray|https://www.buymeacoffee.com/alhassy|buy-me-a-coffee
TODO: FIX contributions URL
badge:contributions|welcome|green|https://github.com/alhassy/alhassy.github.io/issues
#+end_center
tdlr: This library provides the Emacs built-in kbd:C-x_C-e behaviour for
arbitrary languages, provided they have a primitive cli REPL.
* A Rapid Overview of RDD
:PROPERTIES:
:CUSTOM_ID: A-Rapid-Overview-of-RDD
:END:
** How do people usually code? 🌤️
:PROPERTIES:
:CUSTOM_ID: How-do-people-usually-code-️
:END:
Either you,
1. Use a code editor and edit multiple lines, then jump into a console to try
out what you wrote⏳, or
2. 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 🧟♂️
** If only we could have our cake, and eat it too! 🍰
:PROPERTIES:
:CUSTOM_ID: If-only-we-could-have-our-cake-and-eat-it-too
:END:
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 kbd:C-x_C-e ---E for Evaluate! 😉
#+begin_src emacs-lisp
(message-box "hello world")
#+end_src
The melpa:repl-driven-development software gives us this feature for any
language! For example, Press kbd:C-x_C-e at the end of the following line to get
an Emacs-integrated REPL for Java ---i.e.,
kbd:C-x_C-j will now
evaluate a selection, or the entire line, as if it were Java code.
- Why kbd:C-x_C-j ? Well, kbd:C-x_C-“e” for Emacs Lisp code, and kbd:C-x_C-“j”
for Java code!
#+name: startup-code
#+begin_src emacs-lisp :tangle nil
(repl-driven-development [C-x C-j] "jshell" :prompt "jshell>")
#+end_src
We can now press kbd:C-x_C-j to execute any Java code, and see results echoed inline in an overlay, such as:
#+begin_src java :tangle nil
1 + 2 System.out.println(2 + 3) "hello"[0]
IntStream.range(0, 100).toArray()
import javax.swing.*;
var frame = new JFrame(){{ setAlwaysOnTop(true); }};
JOptionPane.showMessageDialog(frame, "Super nice!"); #+end_src
#+html: <center>
👀
This extension is an easy Emacs-integrated-REPL builder for any language!
😲
#+html: </center>
Learn more Java from
badge:Java|Colourful PDF CheatSheet|success|https://alhassy.com/java-cheat-sheet.pdf|coffeescript
.
** Say that again, but use Python please! 🐍 :details:
:PROPERTIES:
:CUSTOM_ID: Say-that-again-but-use-Python-please
:END:
#+begin_center
badge:Python|Colourful PDF CheatSheet|success|https://alhassy.github.io/PythonCheatSheet/CheatSheet.pdf|python
#+end_center
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 kbd:C-x_C-e ---E for Evaluate! 😉
#+begin_src emacs-lisp
(message-box "hello world")
#+end_src
The melpa:repl-driven-development software gives us this feature for any
language! For example, Press kbd:C-x_C-e on the following line to get
an Emacs-integrated REPL for Python:
#+begin_src emacs-lisp :tangle nil
(repl-driven-development [C-x C-p] "python3")
#+end_src
We can now press kbd:C-x_C-p to execute any Python code, such as:
#+begin_src python
import os f"Hello, {os.environ['USER']}!"
#+end_src
More exciting fun is to produce an increasing family of colourful circles, in a GUI:
#+begin_src python
import turtle
it = turtle.Turtle()
for i in range(10):
it.pencolor("green" if i % 2 == 0 else "red")
it.pensize(i / 2)
it.circle(i * 10)
#+end_src
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!
😲
** Technically speaking, how is Emacs itself the REPL? 🤔
:PROPERTIES:
:CUSTOM_ID: Technically-speaking-how-is-Emacs-itself-the-REPL
:END:
Let's do what math-nerds call proof by definition-chasing:
1. Definition: REPL is any software that supports a Read-Evaluate-Print-Loop cycle.
2. kbd:C-x_C-e / kbd:C-x_C-j will echo the results next to your cursor, in your
editor
3. So it retains each of the read, eval, and print parts of the Read-Evaluate-Print-Loop
4. Moreover, since the program doesn't terminate, you're still in the loop part
until you close Emacs
** 🛗 Summarising Evaluator Pitch ⚾
:PROPERTIES:
:CUSTOM_ID: Summarising-Evaluator-Pitch
:END:
#+html: <center>
Make Emacs itself a REPL for your given language of choice
#+html: </center>
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 melpa:repl-driven-development software provides the Emacs built-in
kbd:C-x_C-e behaviour for arbitrary languages, provided they have a primitive
cli REPL.
#+html: <center>
Benefits
#+html: </center>
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.
** 🤖 💪 🤖 Features of RDD.el 💪 🤖 💪
:PROPERTIES:
:CUSTOM_ID: Features-of-RDD-el
:END:
+ 👀 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 kbd:C-h_k then your
keybinding.
+ 🩹 Press kbd: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 kbd:C-u_C-u_C-x_C-j and you get the
documentation of that function.
* Implementation of doc:repl-driven-development :noexport:
:PROPERTIES:
:CUSTOM_ID: Implementation-of-doc-repl-driven-development
:END:
This article does not discuss the source code, which can be found below, folded
away. If you're interested, consider consulting
badge:Elisp|Colourful PDF CheatSheet|success|https://alhassy.github.io/ElispCheatSheet/CheatSheet.pdf|Gnu-Emacs.
** Lisp Package Preamble :details_package_preamble:
:PROPERTIES:
:CUSTOM_ID: Preamble
:END:
#+BEGIN_SRC emacs-lisp :tangle ~/repl-driven-development/repl-driven-development.el
(require 's) (require 'dash) (require 'cl-lib) (require 'eros) (require 'org)
(require 'bind-key)
(defconst repl-driven-development-version (package-get-version))
(defun repl-driven-development-version ()
"Print the current repl-driven-development version in the minibuffer."
(interactive)
(message repl-driven-development-version))
#+END_SRC
#+RESULTS:
: repl-driven-development-version
** Implementation Code :details_source_code:
:PROPERTIES:
:CUSTOM_ID: Implementation-Code
:END:
#+name: startup-code
#+begin_src emacs-lisp :tangle ~/repl-driven-development/repl-driven-development.el
(defvar rdd---current-input nil
"Used to avoid scenarios where input is echoed thereby accidentally treating it as a repl output.")
(defvar rdd---current-output nil
"The output of the most recent repl call; this is used for testing.")
(cl-defun repl-driven-development (keys cli &key (prompt ">") docs (prologue ""))
"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.
Implementation & behavioural notes can be found in the JavaScript
Example below.
######################################################################
### JavaScript Example ---Basic usage, and a minimal server ##########
######################################################################
;; C-x C-j now evaluates arbitrary JavaScript code
(repl-driven-development [C-x C-j] \"node\")
That's it! Press “C-x C-e” 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? 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 JS file ---or just press “C-x C-j” to
evaluate them!
1 + 2 // ⮕ 3
1 + '2' // ⮕ '12'
let me = {name: 'Jasim'}; Object.keys(me) // ⮕ ['name']
me.doesNotExist('whoops') // ⮕ Uncaught TypeError
[ ...Array(45).keys() ] // ⮕ Multi-line overlay of 0..44
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” process, then
C-x C-j 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.
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.
######################################################################
### Description of Arguments #########################################
######################################################################
- KEYS [Vector]: A vector such as [C-x C-p] that declares the keybindings for
the new REPL evaluator.
- CLI [String]: A string denoting the terminal command to start your repl;
you may need an “-i” flag to force it to be interactive even though
we use it from a child process rather than a top-level shell.
- PROMPT [Regular Expression]:
What is the prompt that your REPL shows, e.g., “>”.
We try to ignore showing it in an overlay that would otherwise hide
useful output.
- DOCS [String]: A space-seperated string denoting a list of language documents
you'd like to associate with your repl.
Invoking your repl with “C-u C-u” will show the documentation
of the word at point. This is done using `devdocs'.
For example,
(repl-driven-development [C-x C-j] \"node\" :docs \"javascript express\")
Would allow us to invoke “C-u C-u C-x C-j” with the cursor on the
word, say, “listen” and we'll see some useful docs (along with
example uses) of this Express library method “listen”.
Visit https://devdocs.io/ to see the list of documented languages
and libraries.
- PROLOGUE [String | List<String>]: Any initial code you'd like your
repl to be initiated with. For example, imports of standard libraries
is probably something you'd always like to have on-hand; or perhaps
some useful variables/declarations/functions.
Finally, you may register callbacks via `repl-driven-development-output-hook'.
### Misc Remarks #####################################################
VSCode has a similar utility for making in-editor REPLs, by the
same author: http://alhassy.com/making-vscode-itself-a-java-repl
"
(cl-assert (or (stringp prologue) (listp prologue)))
(when (listp prologue) (setq prologue (s-join "\n" prologue)))
(cl-assert (stringp prologue))
(-let* (((cmd . args) (s-split " " cli))
(repl (apply #'start-process "repl-driven-development"
(format "*REPL/%s*" cli) cmd args)))
(with-current-buffer (format "*REPL/%s*" cli)
(setq buffer-display-table (make-display-table))
(aset buffer-display-table ?\^M [])
(setq buffer-read-only t))
(setq docs (rdd---install-any-not-yet-installed-docs docs))
(eval `(rdd---make-repl-function ,repl ,keys ,cmd ,docs
(repl-driven-development ,keys ,cli :prompt ,prompt :docs ,(s-join " " docs) :prologue ,prologue)))
(process-send-string repl prologue)
(process-send-string repl "\n")
(set-process-filter repl (rdd---main-callback prompt))
repl))
(defun rdd---main-callback (prompt)
`(lambda (process output)
(rdd---insertion-filter process output)
(setq output (s-trim (s-replace-regexp ,prompt "" (s-replace "\r\n" "" output))))
(require 'cl)
(cl-loop for fun in repl-driven-development/output-hook
do (setq output (funcall fun output)))
(rdd---insert-or-echo output)))
(defun rdd---install-any-not-yet-installed-docs (docs)
"Install any not-yet-installed docs; returns a List<String> of the intalled docs."
(when docs
(require 'devdocs)
(cl-assert (stringp docs))
(setq docs (--reject (s-blank? it) (s-split " " docs)))
(cl-assert (listp docs))
(-let [installed (mapcar #'f-base (f-entries devdocs-data-dir))]
(--map (unless (member it installed) (devdocs-install (list (cons 'slug it)))) docs))
docs))
(defun rdd---insert-or-echo (output)
"If there's a C-u, then insert the output; else echo it in overlay"
(cl-assert (stringp output))
(pcase current-prefix-arg
('(4) (unless (equal output (s-trim rdd---current-input)) (insert " " output)))
(_
(setq output (rdd---ignore-ansi-color-codes output))
(unless (s-blank? (s-trim output))
(setq repl-driven-development-current--output output)
(thread-yield)
(require 'eros)
(cl-letf (((symbol-function 'backward-sexp) (lambda (&rest _) 0)))
(eros--make-result-overlay output
:format " ⮕ %s"
:duration repl-driven-development/echo-duration)))))
)
#+end_src
#+RESULTS: startup-code
: repl-driven-development/echo-duration
*** repl-driven-development--make-repl-function and other helpers
:PROPERTIES:
:CUSTOM_ID: repl-driven-development-make-repl-function-and-other-helpers
:END:
#+name: startup-code
#+begin_src emacs-lisp :tangle ~/repl-driven-development/repl-driven-development.el
(defvar repl-driven-development--insert-into-repl-buffer t)
(defmacro rdd---make-repl-function (repl keys cmd docs incantation-to-restart-repl)
"Constructs code denoting a function that sends a region to a REPL process"
(-let* ((repl-fun-name (intern (concat "repl/" cmd))))
`(progn
TODO (defun ,(intern (format "%s/sync" repl-fun-name)) (string)
"Block until we see the snetiantial marker; then emit the repl output. This is an sync call to the repl."
(thread-join (make-thread `(lambda ()
(setq DONE (format "\"DONE TEST %s\"" (gensym)))
(process-send-string ,,repl (format "%s\n%s\n" ,string DONE))
(setq my/threshold 0)
(setq results nil)
(setq waiting-seconds .5) (loop
(sleep-for .01)
(incf my/threshold)
(push repl-driven-development-current--output results)
(when (or (< 1000 (* my/threshold waiting-seconds)) (s-matches? DONE repl-driven-development-current--output))
(return)))
(thread-yield)
(cadr (-uniq results))))))
(bind-key* (s-join " " (mapcar #'pp-to-string ,keys))
(defun ,repl-fun-name (region-beg region-end)
,(rdd---make-repl-function-docstring cmd "")
(interactive "r")
(require 'pulsar)
(setq pulsar-face 'pulsar-yellow)
(pulsar-mode +1)
(pulsar-pulse-line)
(pcase current-prefix-arg
TODO (0 (switch-to-buffer (--> (buffer-list) (--map (buffer-name it) it) (--filter (s-starts-with? "*REPL/jshell" it) it) car)))
(-1
(kill-buffer (process-buffer ,repl))
,incantation-to-restart-repl)
('(16) (rdd---docs-at-point (quote ,docs)))
(_
(if (use-region-p)
(deactivate-mark)
(beginning-of-line)
(setq region-beg (point))
(end-of-line)
(setq region-end (point)))
(setq rdd---current-input (s-trim-left (buffer-substring-no-properties region-beg region-end)))
(process-send-string ,repl rdd---current-input)
(process-send-string ,repl "\n")
))
)))))
(defun rdd---docs-at-point (docs)
(let ((devdocs-history nil) (current-prefix-arg nil) (devdocs-current-docs docs) (word (or (thing-at-point 'symbol) "")))
(minibuffer-with-setup-hook
`(lambda () (insert ,word))
(call-interactively #'devdocs-lookup))))
TODO(cl-defmethod rdd---make-repl-function-docstring ((cli string) (additional-remarks string))
"Makes the docstring for a repl function working with command CLI."
(s-replace-regexp "^\s+" ""
(format
"Executes the selected region, if any or otherwise the entire current line,
and evaluates it with the command-line tool “%s”.
Output is shown as an overlay at the current cursor position.
It is shown for `repl-driven-development/echo-duration' many seconds.
## C-u Prefix: Insert result ###################################################
With a “C-u” prefix, the output is inserted at point
(and not echoed in an overlay).
## C-u C-u Prefix: Documentation ##############################################
With a “C-u C-u” prefix, documentation is looked-up for the word at point.
This is done using `devdocs', and so the documentation generally provides
example uses as well. Visit https://devdocs.io/ to see the list of documented
languages and libraries.
## “C-u 0” Prefix: See associated buffer #####################################
Sometimes it may be useful to look at a large output in a dedicated buffer.
## “C-u -1” Prefix: Restart REPL #############################################
In the event you've messed-up your REPL, starting from a blank slate may be
helpful.
## Implementation Notes ########################################################
The interactive method is asynchronous: Whenever you send text for evaluation,
you immediately regain control in Emacs; you may send more text and it will be
queued for evaluation. For example, evaluating a sleep command for 3 seconds
does not block Emacs.
## See also ####################################################################
See `repl-driven-development' for more useful docs.
See www.alhassy.com/repl-driven-development to learn more about RDD and see
examples and many gifs.
"
cli
)))
(defun repl-driven-development--santise-output (output prompt input)
"Remove PROMPT from OUTPUT, and ensure OUTPUT does not contain a copy of INPUT."
(setq output (s-trim (s-replace "\r" "" (s-replace-regexp prompt "" output))))
(-let [no-input-echo (s-trim (s-chop-prefix input output))]
(if (s-blank? (s-trim (s-collapse-whitespace no-input-echo))) output no-input-echo)))
(defvar repl-driven-development/output-hook nil
"A list of functions to execute after REPL output has been computed.
Each function consumes a single argument: The output result, as a string.
For example:
;; I'd like “C-h e” to show eval result ---just as “C-x C-e” does.
(add-hook 'repl-driven-development/output-hook
(lambda (output)
(let ((inhibit-message t))
(message \"REPL⇒ %s\" output))
output))
")
#+end_src
*** Testing :noexport:
:PROPERTIES:
:CUSTOM_ID: Testing
:END:
#+name: startup-code
#+begin_src emacs-lisp :tangle no
(require 'ert)
(ert-deftest java ()
(repl-driven-development [C-x C-j] "jshell" :prompt "jshell>")
(loop with ERROR = "| Error:"
with NO_OUTPUT = "DONE TEST g" for (input expected-output)
in `( ("" "| Welcome to JShell -- Version 20.0.1\n| For an introduction type: /help intro")
("1 + 2 + 3" "6")
("\"hello\" + \" world\"" "\"hello world\"")
("IntStream.range(0, 7).toArray()" "int[7] { 0, 1, 2, 3, 4, 5, 6 }")
("var x = 3" "3")
("2 * x" "6")
("2 * \"nope\"" ,ERROR)
("Thread.sleep(10); 1 + 9" "10")
(" \n \n \n" ,NO_OUTPUT)
("import java.util.stream.*" ,NO_OUTPUT)
("System\n.out\n.println\n(\"Hiya buddo!\")" "\"Hiya buddo!\"")
("System\n\n\n.out\n.println(\"Hiya buddo!\")" ,ERROR)
)
do (unless (or (equal expected-output ERROR) (s-matches? (format ".*%s.*" NO_OUTPUT) expected-output))
(should (equal expected-output (s-replace-regexp ".*==> " "" (repl/jshell/sync input)))))))
#+end_src
*** Tell me something about them there Emacs threads! :noexport:
:PROPERTIES:
:CUSTOM_ID: Tell-me-something-about-them-there-Emacs-threads
:END:
#+begin_src emacs-lisp :tangle no
(progn
(make-thread (lambda ()
(message (format-time-string "Thread 1 ~ %H:%M:%S" (current-time)))
(thread-yield)))
(make-thread (lambda ()
(message (format-time-string "Thread 2 ~ %H:%M:%S" (current-time)))
(thread-yield))))
(progn
(thread-join (make-thread (lambda ()
(message (format-time-string "Thread 1 ~ %H:%M:%S" (current-time)))
(thread-yield))))
(make-thread (lambda ()
(message (format-time-string "Thread 2 ~ %H:%M:%S" (current-time)))
(thread-yield))))
#+end_src
#+RESULTS:
: #<thread 0x7ffb3f1ea9d8>
*** rdd---ignore-ansi-color-codes && rdd---insertion-filter
:PROPERTIES:
:CUSTOM_ID: rdd-ignore-ansi-color-codes-rdd-insertion-filter
:END:
#+name: startup-code
#+begin_src emacs-lisp :tangle ~/repl-driven-development/repl-driven-development.el
(defun rdd---ignore-ansi-color-codes (string-with-codes)
"Ignore ANSI color codes in a string"
(with-temp-buffer
(insert string-with-codes)
(ansi-color-apply-on-region (point-min) (point-max))
(buffer-string)))
#+end_src
#+RESULTS:
: repl-driven-development
#+name: startup-code
#+begin_src emacs-lisp :tangle ~/repl-driven-development/repl-driven-development.el
(defun rdd---insertion-filter (proc string)
"Src: https://www.gnu.org/software/emacs/manual/html_node/elisp/Filter-Functions.html"
(when (and repl-driven-development--insert-into-repl-buffer (buffer-live-p (process-buffer proc)))
(with-current-buffer (process-buffer proc)
(let ((moving (= (point) (process-mark proc))))
(save-excursion
(goto-char (process-mark proc))
(let (buffer-read-only)(insert (rdd---ignore-ansi-color-codes string))) (set-marker (process-mark proc) (point)))
(if moving (goto-char (process-mark proc)))))))
#+end_src
#+RESULTS:
: rdd---insertion-filter
#+name: startup-code
#+begin_src emacs-lisp :tangle ~/repl-driven-development/repl-driven-development.el
(defvar repl-driven-development/echo-duration 5)
#+end_src
#+RESULTS:
: repl-driven-development/echo-duration
** Lisp Postamble :noexport:
:PROPERTIES:
:CUSTOM_ID: Postamble
:END:
#+BEGIN_SRC emacs-lisp :tangle ~/repl-driven-development/repl-driven-development.el
(provide 'repl-driven-development)
#+END_SRC
* Teaching a runtime, green:incrementally, to be a web server 🍽️ 🔁 🤖
:PROPERTIES:
:CUSTOM_ID: Teaching-a-runtime-green-incrementally-to-be-a-web-server-️
:END:
#+html: <center> <em>
RDD by example: Let's start with a JavaScript runtime and incrementally turn it
into a web server.
#+html: </em> </center>
“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).
** The “𝒳 as teaching” meme :details:
:PROPERTIES:
:CUSTOM_ID: The-𝒳-as-teaching-meme
:END:
+ 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.
** <em>Wait, I already do this RDD stuff everyday, in the shell!</em> :details:
:PROPERTIES:
:CUSTOM_ID: em-Wait-I-already-do-this-RDD-stuff-everyday-in-the-shell-em
:END:
You can green:“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.
#+html: <center>
Anytime you execute a query, in some system, you're using a read-evaluate-print-loop!
#+html: </center>
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.
:boring_details:
Indeed, the following popular tools are either entirely
driven by a REPL or make great use of a REPL:
+ SQL ::
You green:“discover” the query you want, by incrementlly (1) running a number
of queries, (2) seeing the results, then (3) tweaking the previous query;
until you're happy with the resulting output.
- You can “modify the running system” in this case by adding or dropping
tables to the database.
+ shell :: Query & modify your operating system
- You can green:“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.
+ curl/httpie :: Explore http APIs
+ JS Console in your browser :: Inspect the state of objects during a running system.
+ URL :: The URL text area in your web browser is used to see HTML documents
residing on machines located elsewhere, by using The Internet infrastructure.
:End:
red: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 ⇒ Use your favourite language as a Bash replacement scripting language :ignore:
:PROPERTIES:
:CUSTOM_ID: RDD-Use-your-favourite-language-as-a-Bash-replacement-scripting-language
:END:
#+html: <details> <summary> <strong style="color:green"> <em> RDD let's you use your favourite language as a Bash replacement scripting language</em></strong></summary>
#+begin_quote
If your code-base is in language 𝐿, might as well write your scripts in 𝐿 as
well!
#+end_quote
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?
1. If your an 𝐿 language developer, writing scripts in 𝐿 lets you make use of
all of your existing experience, knowledge, and familiar tool-set of 𝐿.
2. Stay in the comfort of your favourite IDE: Autocomplete, syntax highlighting,
docs, tooltips, linting, etc.
3. Lots of libraries!
4. The gain in expressivity & clarity & test-ability.
5. 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.
6. An 𝐿-REPL is a shell with 𝐿-syntax, and features! ---Since you're actually using 𝐿.
7. Bash is imperative, but your favourite language is (probably) multi-paradigm
---you can do imperative or more!
8. 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.
#+begin_quote
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: [[https://www.freecodecamp.org/news/python-for-system-administration-tutorial/][How to Replace Bash with Python as Your Go-To Command Line Language]]
#+end_quote
“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.
*** withJSON: Alter the contents of a JSON file as if it were a JavaScript object :details:
:PROPERTIES:
:CUSTOM_ID: withJSON-Alter-the-contents-of-a-JSON-file-as-if-it-were-a-JavaScript-object
:END:
#+begin_src javascript
/** 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)
}
}
#+end_src
*** shell: Run a shell command and provide its result as a string; or crash when there's an error :details:
:PROPERTIES:
:CUSTOM_ID: shell-Run-a-shell-command-and-provide-its-result-as-a-string-or-crash-when-there's-an-error
:END:
#+begin_src javascript
/** 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))
}
#+end_src
*** Quick Example of using JS as a command-line-language :details:
:PROPERTIES:
:CUSTOM_ID: Quick-Example-of-using-JS-as-a-command-line-language
:END:
#+begin_src javascript
var axios = require('axios')
var { name, blog, bio } = (await axios.get('https://api.github.com/users/alhassy')).data
#+end_src
*** Java: run a shell command and see the output :details:
:PROPERTIES:
:CUSTOM_ID: Java-run-a-shell-command-and-see-the-output
:END:
#+begin_src java
execCmd("whoami")
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() : "";
}
#+end_src
*** Further reading :ignore:
:PROPERTIES:
:CUSTOM_ID: Further-reading
:UNNUMBERED: t
:END:
Further reading:
+ Your bash scripts are rubbish, use another language
+ Building command line tools with Node.js - Atlassian Developer Blog
+ Bashing the Bash — Replacing Shell Scripts with Python ⋋⋌ Medium
+ Bashing the bash: Why the shell is awful & what you can do about it ⋋⋌ YouTube
#+html: </details>
** Actual example :ignore:
:PROPERTIES:
:CUSTOM_ID: Actual-example
:END:
--------------------------------------------------------------------------------
| Goal: Make an web server with a route localhost:3030/about that shows information about the user's environment variables. |
#+begin_src latex-as-png :file ../images/rdd-teaching-a-js-runtime-to-be-a-webserver.pdf :resolution "520" :results raw value replace :exports none
\usetikzlibrary{decorations.pathmorphing} \begin{center}
\tikzset{my decoration/.style={decorate,decoration=zigzag}}
\smartdiagramset{module shape=rectangle,
insert decoration={my decoration},
uniform arrow color=true,
arrow color=gray!50!black,
}
{\color{gray} Teaching a JS runtime to be a web server}
\smartdiagram[descriptive diagram]{
{REPL Start, {We have a running JavaScript program}},
{\mbox{\hspace{-.3em}\scriptsize Continously} Modify, {Without ever stopping the program, add features to it}},
{Done?, {Can the program do what we wanted it to originally do?}},
}
\color{gray}\texttt{www.alhassy.com/repl-driven-development}
\end{center}
#+end_src
#+RESULTS:
#+attr_html: :width 75% :height 75%
First,
#+begin_src emacs-lisp :tangle nil
(repl-driven-development [C-x C-j] "node" :docs "javascript express")
#+end_src
Then, here's how we do this ...
Visit http://localhost:3030/about, if that works, then we're done!
#+begin_src javascript
let app = require('/usr/local/lib/node_modules/express')()
let server = app.listen(3030)
let visited = 1
app.get('/hi', (req, res) => res.send(`Hello × ${visited++}`))
app.get('/about', (req, res) => res.send(html()) )
let
html = _ => "<div style='color:green; background-color:cyan'>" + info() + "</div>"
let info = function () { return {visited, user: process.env.USER, time: new Date() } }
html = _ => "<div style='color:green; background-color:cyan'>" + JSON.stringify(info(), null, 3 ) + "</div>"
html = _ => `<h1>Welcome, visitor ${visited++}!</h1><pre style='color:green; background-color:cyan'>` + JSON.stringify(info(), null, 3 ) + "</pre>"
info = _ => ({user: process.env, time: new Date(), platform: os.platform(), architecture: os.arch(), home: os.homedir(), user: os.userInfo(), machine: os.machine()})
server.close()
#+end_src
TODO: Make the above into a short youtube video/*GIF*, where I keep
“improving” the definition of html / info and see it live!
#+begin_box 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.
#+end_box
** Programming ≈ Definitions and re-definitions :details:
:PROPERTIES:
:CUSTOM_ID: Programming-Definitions-and-re-definitions
:END:
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”.
#+begin_center
In RDD, we can green:re-define functions & types live, as the program is
running! html:<br> Future uses of the function/type will use the new definition!
#+end_center
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()$. 👍
:Neato_aside:
Thanks to the λ-Calculus, everything can be technically thought of as a function.
As such, data-types are fancy functions (Aside: In JavaScript, functions can be used
as “classes” that are instantiated with the “new” keyword!), and so re-defining
a data-type does not impact any existing instances: Existing instances are objects
of a class that we no longer have access to.
:End:
orange: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...
#+begin_src java
record Person(String name) { }
var me = new Person("Musa"); Function<Person, String> speak = p -> p.name() + " says HELLO!"
String greet(Person p) { return "Hello, I'm " + p.name(); }
record Person(int age) { }
me speak greet
greet(new Person(12)) #+end_src
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 melpa:repl-driven-development 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.
* Concluding Remarks
:PROPERTIES:
:CUSTOM_ID: Concluding-Remarks
:END:
I've found RDD to be a green: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.
#+begin_src latex-as-png :file ../images/rdd-workflow.pdf :resolution "520" :results raw value replace :exports none
\usepackage[utf8]{inputenc}
\DeclareUnicodeCharacter{FD3E}{\char"5D\relax}
\DeclareUnicodeCharacter{FD3F}{\char"5B\relax}
\usepackage[english, arabic]{babel}
\def\NUM#1{\textLR{\centerline{\ARmbox{﴾\color{gray}}#1\ARmbox{﴿\color{gray}}}}}
\def\NUMSpc#1#2{\textLR{\centerline{\hspace{#1}\ARmbox{﴾\color{gray}}#2\ARmbox{﴿\color{gray}}}}}
\selectlanguage{english}
\begin{center}
\smartdiagramset{planet color=orange!60,
}
\smartdiagram[connected constellation diagram]
{\small \mbox{\hspace{-.3em}REPL Driven} \mbox{Development} \mbox{\hspace{-.5em}\emph{\large\textbf{Workflow}}} to building a program,
\NUM{1} \emph{Start your REPLs!},
\NUM{4} Rephrase REPL \mbox{\hspace{-.5em}explorations} as unit tests,
\NUM{3} \mbox{\hspace{-1.8em}When you're done:} \mbox{Clean up} \mbox{your editor} and save the code,
\NUMSpc{.3em}{2} \emph{\mbox{\hspace{-.5em}Type in your} \mbox{\hspace{.5em} editor}}
{\scriptsize \mbox{\hspace{-2em}continously evaluating}
\mbox{\hspace{-.5em}expressions and}
\mbox{\hspace{-.9em}observing output}}
}
{\color{gray}\texttt{www.alhassy.com/repl-driven-development}}
\end{center}
#+end_src
#+RESULTS:
#+attr_html: :width 75% :height 75%
#+begin_box Some languages have tight integration with Emacs!
Programs in these languages are essentially “constructed incrementally” by
“interactive conversations” with Emacs (as the REPL).
#+begin_center
badge:Elisp|Colourful PDF CheatSheet|success|https://alhassy.github.io/ElispCheatSheet/CheatSheet.pdf|Gnu-Emacs
badge:Clojure|Colourful PDF CheatSheet|success|https://alhassy.github.io/ClojureCheatSheet/CheatSheet.pdf|awslambda
badge:Agda|Colourful PDF CheatSheet|success|https://alhassy.github.io/AgdaCheatSheet/CheatSheet.pdf|haskell
badge:Coq|Colourful PDF CheatSheet|success|https://alhassy.github.io/CoqCheatSheet/CheatSheet.pdf|twitter
badge:Oz|PDF CheatSheet|success|https://alhassy.github.io/OzCheatSheet/CheatSheet.pdf|pastebin
#+end_center
The first such language is Common Lisp.
Which also inspired a similar setup for Smalltalk ---e.g., Pharo and Squeak.
#+end_box
I hope you've enjoyed this article!
Bye! 👋 🥳
* Appendix: Interesting Reads
:PROPERTIES:
:UNNUMBERED: t
:CUSTOM_ID: Appendix-Interesting-Reads
:END:
+ CLOG: Learn Common Lisp by building real-world applications
+ A Python prompt into a running process: debugging with Manhole
* Appendix: Recipes for a number of languages
:PROPERTIES:
:UNNUMBERED: t
:CUSTOM_ID: Appendix-Recipes-for-a-number-of-languages
:END:
** JavaScript ---and a minimal server :details_javascript:
:PROPERTIES:
:CUSTOM_ID: JavaScript-and-a-minimal-server
:END:
#+begin_center
badge:JavaScript|Colourful PDF CheatSheet|success|https://alhassy.github.io/JavaScriptCheatSheet/CheatSheet.pdf|javascript
#+end_center
We can set up a JavaScript REPL in the background as follows...
#+begin_src emacs-lisp :tangle nil
(repl-driven-development [C-x C-j] "node -i")
#+end_src
That's it! Press kbd:C-x_C-e on the above line so that kbd:C-x C-j will now
evaluate a selection, or the entire line, as if it were JavaScript code.
- Why kbd:C-x C-j ? Well, kbd:C-x_C-“e” for Emacs Lisp code, and kbd:C-x_C-“j”
for JavaScript code!
- For instance, copy-paste the following examples into a JavaScript file ---or just
press kbd:C-x C-j in any buffer to evaluate them!
#+begin_src javascript
1 + 2
1 + '2'
let me = {name: 'Jasim'}; Object.keys(me)
me.doesNotExist('whoops') #+end_src
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
kbd:C-x_C-j 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.
#+begin_src javascript
let app = require('express')()
let clicked = 1
app.get('/hi', (req, res) => res.send(`Hello World × ${clicked++}`))
let server = app.listen(3000)
let axios = require('axios')
console.log((await axios.get('http://localhost:3000/hi')).data)
server.close()
#+end_src
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 :details_python_#add8e6:
:PROPERTIES:
:CUSTOM_ID: Python
:END:
#+begin_center
badge:Python|Colourful PDF CheatSheet|success|https://alhassy.github.io/PythonCheatSheet/CheatSheet.pdf|python
#+end_center
We can set up a Python REPL in the background as follows...
#+begin_src emacs-lisp :tangle nil
(repl-driven-development [C-x C-p] "python3 -i")
#+end_src
Example use...
#+begin_src python
1 + 2
hello = 'world!'
print(hello)
2 + 'hi' #+end_src
Learn more by reading... Python: A Gentle Introduction to Socket Programming
** Java :details_java:
:PROPERTIES:
:CUSTOM_ID: Java
:END:
#+begin_center
badge:Java|Colourful PDF CheatSheet|success|https://alhassy.com/java-cheat-sheet.pdf|coffeescript
#+end_center
We can set up a Java REPL in the background as follows...
#+begin_src emacs-lisp
(repl-driven-development [C-x C-j] "jshell --enable-preview" :prompt "jshell>")
#+end_src
Now, we can select the following and press C-x C-j to evaluate the Java code:
#+begin_src java :tangle no
import javax.swing.*;
JOptionPane.showMessageDialog(new JFrame(), "Super nice!");
#+end_src
Or doing algebraic datatypes in Java:
#+begin_src java :tangle no
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)) #+end_src
** Clojure :details_clojure:
:PROPERTIES:
:CUSTOM_ID: Clojure
:END:
We can set up a REPL in the background as follows...
#+begin_src emacs-lisp
(repl-driven-development [C-x C-k] "clojure" :prompt "user=>")
#+end_src
For example...
#+begin_src clojure
(+ 1 2) ;; ⮕ 3
(defn square [x] (* x x)) ;; ⮕ #'user/square
(square 3) ;; ⮕ 9
#+end_src
** Haskell :details_haskell_#add8e6:
:PROPERTIES:
:CUSTOM_ID: Haskell
:END:
#+begin_center
badge:Haskell|Colourful PDF CheatSheet|success|https://alhassy.github.io/HaskellCheatSheet/CheatSheet.pdf|awslambda
#+end_center
We can set up a REPL in the background as follows...
#+begin_src emacs-lisp :tangle nil
(repl-driven-development [C-x C-h] "ghci" :prompt "ghci>")
#+end_src
For example...
#+begin_src haskell
sum [ x ** 2 | x <- [1..100]]
[x | x <- [1..12], x `mod` 2 == 0]
myLast = head . reverse
myLast [1, 2, 3] #+end_src
Note that Haskell has “typed holes” with the syntax _A:
#+begin_src haskell :tangle nil
1 + _A #+end_src
Another language with typed holes is Arend...
** Arend: Quickly making a terse Emacs interface for a language without one :details_recipe:
:PROPERTIES:
:CUSTOM_ID: Arend-Quickly-making-a-terse-Emacs-interface-for-a-language-without-one
:END:
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...
#+begin_src emacs-lisp :tangle nil
(repl-driven-development [C-x C-a]
(format "java -jar %s -i"
(f-expand "~/Downloads/Arend.jar")))
#+end_src
Then,
#+begin_src arend :tangle nil
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
#+end_src
** PureScript :details_purescript_#add8e6:
:PROPERTIES:
:CUSTOM_ID: PureScript
:END:
First brew install spago, then we can set up a PureScript REPL in the background
as follows...
#+begin_src emacs-lisp :tangle nil
(repl-driven-development [C-x C-p] "spago repl")
#+end_src
For example....
#+begin_src purescript :tangle nil
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
#+end_src
** Idris :details_idris:
:PROPERTIES:
:CUSTOM_ID: Idris
:END:
First brew install idris2, then we can set up an Idris REPL in the background as
follows...
#+begin_src emacs-lisp :tangle nil
(repl-driven-development [C-x C-i] "idris2")
#+end_src
Here's some random code...
#+begin_src purescript :tangle nil
-- 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
#+end_src
** Racket :details_racket_#add8e6:
:PROPERTIES:
:CUSTOM_ID: Racket
:END:
| 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...
#+begin_src emacs-lisp :tangle nil
(repl-driven-development [C-x C-r] "racket -I slideshow")
#+end_src
Here's some random code...
#+begin_src racket :tangle nil
(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))
#+end_src
Meeting Racket for the first time is probably best done with DrRacket.
** TODO COMMENT Prolog ---saving the REPL for future use :not_ideal_use_case:
:PROPERTIES:
:CUSTOM_ID: COMMENT-Prolog-saving-the-REPL-for-future-use
:END:
#+begin_center
badge:Prolog|Colourful PDF CheatSheet|success|https://alhassy.github.io/PrologCheatSheet/CheatSheet.pdf|prolog
#+end_center
First get the Prolog interpreter with brew install swi-prolog, then we can set
up an Racket REPL in the background as follows...
#+begin_src elisp
(setq prolog (repl-driven-development [C-x C-p] "swipl"))
#+end_src
Notice that we saved the doc:prolog variable... 🤖
- This is done since Prolog is “modal”: One declares facts, then queries them.
- To avoid a query accidentally being considered a delaraction of a true fact,
we use [user]. to begin declaring facts, then invoke :doc:process-send-eof to
the Prolog REPL to begin querying our database.
:Idea_To_improve_RDD_el:
#+begin_src elisp
TODO#+end_src
:End:
First, let's declare some facts.
#+begin_src prolog
[user].
parent(musa, yusuf).
parent(musa, zaynab).
parent(malak, yusuf).
#+end_src
Now, we can continue using kbd:C-x C-p to query facts.
#+begin_src prolog
parent(X, yusuf).
;
parent(musa, X).
findall(X, parent(musa, X), Xs).
#+end_src
** TODO COMMENT Ruby
:PROPERTIES:
:CUSTOM_ID: COMMENT-Ruby
:END:
#+begin_center
badge:Ruby|Colourful PDF CheatSheet|success|https://alhassy.github.io/RubyCheatSheet/CheatSheet.pdf|ruby
#+end_center
We can set up a REPL in the background as follows...
#+begin_src emacs-lisp :tangle nil
(repl-driven-development [C-x C-r] "irb --inf-ruby-mode" :prompt "irb(main):.*>")
#+end_src
For example...
#+begin_src ruby
2 + 2
33 + 4
5.times { print "Odelay!" }
['ruby', 'is', 'readable'].map { | food | food.capitalize }
require 'tk'
#+end_src
** TODO COMMENT TypeScript
:PROPERTIES:
:CUSTOM_ID: COMMENT-TypeScript
:END:
We can set up a REPL in the background as follows...
#+begin_src emacs-lisp :tangle nil
(repl-driven-development [C-x C-t] "npx ts-node")
#+end_src
** COMMENT F# :Does_not_work:fsharpi_requires_ansi_term__not_eshell:
:PROPERTIES:
:CUSTOM_ID: COMMENT-F
:END:
badge:F#|Colourful PDF CheatSheet|success|https://alhassy.github.io/FSharpCheatSheet/CheatSheet.pdf|f-sharp
#+begin_center
badge:OCaml|Colourful PDF CheatSheet|success|https://alhassy.github.io/OCamlCheatSheet/CheatSheet.pdf|OCaml
#+end_center
First brew install mono, then we can set up an F# REPL in the background as
follows...
#+begin_src emacs-lisp :tangle nil
(repl-driven-development [C-x C-j] "fsharpi")
#+end_src
#+begin_src fsharp :tangle nil
-- F# has “ranges with a step”
[0..3..14];; -- ⇒ [0; 3; 6; 9]
-- Which are a shorthand for guarded comprehensions
[for i in 0..14 do if i % 3 = 0 then yield i];;
-- Experiment with a typed hole
1 + __;; -- ⇒ The type 'obj' does not match the type 'int'
#+end_src
If you like F#, take a look at F*: A Proof-Oriented Programming Language.
** TODO COMMENT Java MWE
:PROPERTIES:
:CUSTOM_ID: COMMENT-Java-MWE
:END:
#+begin_center
badge:Java|Colourful PDF CheatSheet|success|https://alhassy.com/java-cheat-sheet.pdf|coffeescript
#+end_center
We can set up a Java REPL in the background as follows...
#+begin_src emacs-lisp
(repl-driven-development [C-x C-j] "jshell --enable-preview" :prompt "jshell>")
#+end_src
Now, we can select the following and press C-x C-j to evaluate the Java code:
#+begin_src java :tangle no
import javax.swing.*;
JOptionPane.showMessageDialog(new JFrame(), "Super nice!");
#+end_src
Or doing algebraic datatypes in Java:
#+begin_src java :tangle no
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)) #+end_src
* COMMENT MELPA Checks
:PROPERTIES:
:CUSTOM_ID: COMMENT-MELPA-Checks
:END:
https://github.com/riscy/melpazoid
1. In Github repo: Add file ⇒ Create new file ⇒ License.txt ⇒ Select template ⇒ GNU 3
2. Ensure first line ends with: -- lexical-binding: t; --
3. Include appropriate standard keywords;
#+begin_src emacs-lisp :tangle no
(pp finder-known-keywords)
#+end_src
#+RESULTS:
#+begin_example
((abbrev . "abbreviation handling, typing shortcuts, and macros")
(bib . "bibliography processors")
(c . "C and related programming languages")
(calendar . "calendar and time management tools")
(comm . "communications, networking, and remote file access")
(convenience . "convenience features for faster editing")
(data . "editing data (non-text) files")
(docs . "Emacs documentation facilities")
(emulations . "emulations of other editors")
(extensions . "Emacs Lisp language extensions")
(faces . "fonts and colors for text")
(files . "file editing and manipulation")
(frames . "Emacs frames and window systems")
(games . "games, jokes and amusements")
(hardware . "interfacing with system hardware")
(help . "Emacs help systems")
(hypermedia . "links between text or other media types")
(i18n . "internationalization and character-set support")
(internal . "code for Emacs internals, build process, defaults")
(languages . "specialized modes for editing programming languages")
(lisp . "Lisp support, including Emacs Lisp")
(local . "code local to your site")
(maint . "Emacs development tools and aids")
(mail . "email reading and posting")
(matching . "searching, matching, and sorting")
(mouse . "mouse support")
(multimedia . "images and sound")
(news . "USENET news reading and posting")
(outlines . "hierarchical outlining and note taking")
(processes . "processes, subshells, and compilation")
(terminals . "text terminals (ttys)")
(tex . "the TeX document formatter")
(tools . "programming tools")
(unix . "UNIX feature interfaces and emulators")
(vc . "version control")
(wp . "word processing"))
#+end_example
4. Use #' instead of ' for function symbols
5. Use ‘-’ as a separator, not ‘/’.
6. Consider reading:
https://github.com/bbatsov/emacs-lisp-style-guide#the-emacs-lisp-style-guide
7. Use cl-loop, cl-first, cl-second, cl-third instead of loop, first, second, third
8. byte-compile and address any concerns
9. M-x checkdoc on the lisp file to ensure it passes expected style issues.
- Symbols nil, t should not appear in single quotes.
- (progn (setq fill-column 80) (display-fill-column-indicator-mode))
10. Ensure it byte-compiles without any problems.
11. Ensure that package-linter raises no issues; i.e., the following has no result.
#+BEGIN_SRC emacs-lisp :tangle no
(use-package package-lint)
(-let [it "repl-driven-development.el"]
(ignore-errors (kill-buffer it))
(find-file-other-window it)
(package-lint-buffer it)
(switch-to-buffer "*Package-Lint*")) #+END_SRC
12. Commit and push everything in your project's repo!
13. Create a recipe file by invoking: M-x package-build-create-recipe
---first: (use-package package-build)
- Place it in: melpa/recipes/
- The name of the file should be the name of the package, no extension.
Or: Uncomment this section & just tangle the following.
#+BEGIN_SRC emacs-lisp :tangle ~/melpa/recipes/repl-driven-development
(repl-driven-development :fetcher github :repo "alhassy/repl-driven-development")
#+END_SRC
14. Ensure the recipe builds successfully:
#+BEGIN_SRC shell :tangle no
cd ~/melpa; rm ~/melpa/packages/repl-driven-development-*; make recipes/repl-driven-development
#+END_SRC
If you have trouble, make a file "~/bin/emacs" with
the following which ensures “emacs” can be run
from the command line within macos.
#+begin_src shell :tangle "~/bin/emacs"
/Applications/Emacs.app/Contents/MacOS/Emacs "$@"
#+end_src
15. Ensure the package installs properly from within Emacs:
#+BEGIN_SRC emacs-lisp :tangle no
(package-install-file "~/melpa/packages/repl-driven-development-")
#+END_SRC
16. Produce a dedicated pull request branch
#+begin_src emacs-lisp :tangle no
(magit-status "~/melpa")
#+end_src
+ F p to update the repo.
+ Now b c to checkout a new branch: Select master then name the branch by
the name of the package, e.g., repl-driven-development.
+ Commit your recipe.
+ Push this branch on your melpa fork: P p.
+ Go to the https://github.com/melpa/melpa repo and
there'll be a big green PR button ^_^
* COMMENT Making README.org
:PROPERTIES:
:CUSTOM_ID: COMMENT-Making-README-org
:END:
Evaluate the following source block with C-c C-c to produce a README file.
#+NAME: make-readme
#+BEGIN_SRC emacs-lisp
(with-temp-buffer
,#+EXPORT_FILE_NAME: README.md
,#+HTML: <h1> An Emacs interface to the Quran and the Bible: Interactive lookup, Org-mode links, tooltips, and Lisp look-ups </h1>
# +HTML: <h2> ¯\\_(ツ)_/¯ </h2>
,#+OPTIONS: toc:nil d:nil broken-links:t
,#+html: <div align=\"center\">
# +INCLUDE: ~/repl-driven-development/repl-driven-development.org::#Abstract :only-contents t
,#+html: </div>
,#+html: <div align=\"center\">
Let's use Org-mode links to look-up Quranic and Biblical verses!
“Live” examples & documentation: https://alhassy.github.io/repl-driven-development/
badge:repl-driven-development|1.3|informational|https://github.com/alhassy/repl-driven-development|Gnu-Emacs
# #+html: <a href=\"https://melpa.org/#/repl-driven-development\"><img alt=\"MELPA\" src=\"https://melpa.org/packages/repl-driven-development-badge.svg\"/></a>
# #+html: </span>
tweet:https://github.com/alhassy/repl-driven-development
badge:contributions|welcome|green|https://github.com/alhassy/repl-driven-development/issues
badge:author|musa_al-hassy|purple|https://alhassy.github.io/|nintendo-3ds
badge:|buy_me_a coffee|gray|https://www.buymeacoffee.com/alhassy|buy-me-a-coffee
badge:Hire|me|success|https://alhassy.github.io/about
,#+html: </div>
,#+TOC: headlines 2
,* Short Example
,#+attr_html: :width 600px
file:images/short_example.png
,* Long Example
,#+attr_html: :width 600px
file:images/long_example.png
,* Summary
,#+INCLUDE: ~/repl-driven-development/repl-driven-development.org::#Summary :only-contents t
# ,* Minimal working example
# #+INCLUDE: ~/repl-driven-development/repl-driven-development.org::#Minimal-working-example :only-contents t
")
(let ((org-export-use-babel nil) (org-export-with-broken-links t))
(org-mode)
(org-md-export-to-markdown)))
#+END_SRC
#+RESULTS: make-readme
: README.md
Then use grip to see that this looks reasonable.