Ξ  


AlBasmala Archive Tags RSS About

💐 Repl Driven Development: Editor Integrated REPLs for all languages 🔁

Article image

  Ξ  

Abstract

The MELPA 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-e 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,

  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 🧟‍♂️

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-e ---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 MELPA software gives us this feature for any language! For example, Press C-x C-e at the end of the following line to get an Emacs-integrated REPL for Java —i.e., C-x C-j will now evaluate a selection, or the entire line, as if it were Java code.

  • Why C-x C-j  ?  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-j 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! 😲

Learn more Java from .

Say that again, but use Python please! 🐍

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-e ---E for Evaluate! 😉

;; Press “control and x, then control and e” to run the following code
(message-box "hello world")

The MELPA software gives us this feature for any language! For example, Press C-x C-e 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-p 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.3. Technically speaking, how is Emacs itself the REPL? 🤔

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. C-x C-e / 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

1.4. 🛗 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 MELPA software provides the Emacs built-in C-x C-e 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.5. 🤖 💪 🤖 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).

The “𝒳 as teaching” meme
  • 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.
Wait, I already do this RDD stuff everyday, in the shell!

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?

  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.

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.

withJSON: Alter the contents of a JSON file as if it were a JavaScript object
/** 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)
  }
}
shell: Run a shell command and provide its result as a string; or crash when there's an error
/** 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))
}
Quick Example of using JS as a command-line-language
 var axios = require('axios')
 var { name, blog, bio } = (await axios.get('https://api.github.com/users/alhassy')).data
Java: run a shell command and see the output
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.

rdd-teaching-a-js-runtime-to-be-a-webserver.png

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.

Programming   ≈   Definitions and re-definitions

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 MELPA 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.

rdd-workflow.png

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: Interesting Reads

Appendix: Recipes for a number of languages

🔗
JavaScript ---and a minimal server

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-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  ?  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-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.

// 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

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

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

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

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

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

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

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 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.




Generated by Emacs and Org-mode (•̀ᴗ•́)و
Creative Commons License
Life & Computing Science by Musa Al-hassy is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License