The user’s side

Your configuration is init.ss; your live changes are made in M-: and with C-M-x. Every change belongs to where you wrote it, so it can be taken back exactly.

init.ss and (use-plugin p …)user

Does. init.ss runs once, top to bottom. (use-plugin p) chooses plugin p: it starts at its line, or earlier if a line above or another plugin’s file needs it first. A plugin not listed never starts. For a contract with several implementations, the use-plugin line picks which one runs. (use-plugin a b c) chooses several at once when none has options; options need one plugin per line. Other lines are any Scheme: set!, verbs such as keys:bind!, redefine, advice-add, your own definitions. init.ss sees every contract by its prefix without importing.

Underneath. Before anything runs, the whole file is checked: every plugin exists, every option name and value fits, every c:x exists in its contract. Each line is an owner: what it does belongs to it. Because chosen plugins start on first need, the order of use-plugin lines does not matter for correctness; it decides only the order of entries from different plugins.

Errors. All at once, each with its line: init.ss:4: no plugin is named rop-store; init.ss:9: contract settings has no fill-colum. A line that fails while running is undone and reported; the rest still run.

Tuna · init.ss
(use-plugin journal-store commands settings keys rope-store buffers)
(use-plugin undo limit: 5000)
(set! settings:fill-column 70)
(keys:bind! "C-c n" (lambda () (files:find-file "~/notes.txt")))
Emacs · init.el
(require 'undo-tree)
(setopt undo-limit 5000)
(setopt fill-column 70)
(keymap-global-set "C-c n" (lambda () (interactive) (find-file "~/notes.txt")))

Where Tuna differs. Emacs starts with its editor built in, and init.el adds to it. Tuna starts with only the core, and init.ss lists the plugins that make it an editor.

defaults.ssuserproposed

Tuna · init.ss (proposed)
(include "defaults.ss")
(use-plugin highlight terminal)
Emacs
;; Emacs needs no such line: its defaults are built in,
;; and a starter kit is loaded with one require.
(require 'my-starter-kit)

p::xuser

Does. In init.ss and M-: only, reaches plugin p’s private name x on purpose: read it, call it, set! it, redefine it. Plugin code cannot.

Errors. In a plugin: …: plugins cannot reach a private name.

Tuna · M-:
(dabbrev::helper "x")
Emacs
(dabbrev--helper "x")

Where Tuna differs. Every Emacs name is reachable by anyone; the double hyphen only asks you not to. Tuna lets the user reach in, on purpose, and no plugin.

(redefine (name arg …) body …)usercore

Does. Replaces a function, c:name or p::name, with a new body. Everyone who calls it, including stored references such as key bindings, gets the new one.

Underneath. A layer on the function, owned by where the form is written. Remove the owner and the original returns. A redefinition survives a reload of the plugin that defines the function: it is applied to the new code.

Errors. Redefining a macro: … is a macro; changing it needs a reload of its plugin.

Tuna · init.ss
(redefine (english::helper n) (string-append "Yo, " n))
Emacs
(defun english--helper (n) (concat "Yo, " n))

Where Tuna differs. An Emacs defun overwrites the function until the package is loaded again, which then overwrites yours. A Tuna redefinition is a layer that outlives the plugin’s reloads and goes when you remove the line.

(advice-add target how procedure [name: 'n]), (advice-remove target [name: 'n])usercore

Does. Wraps function target without replacing it. how is one of before, after, around (your procedure gets the original as its first argument), override, filter-args, filter-return, as in Emacs. You have one advice of each kind per function unless you name more with name:.

Underneath. A layer owned by where it is written, like redefine. Several advices stack.

Errors. advice-add: unknown how ….

Tuna · init.ss
(advice-add dabbrev:expand filter-return
  (lambda (s) (string-append s "+i")))
Emacs
(advice-add 'dabbrev-expand :filter-return
            (lambda (s) (concat s "+i")))

Where Tuna differs. Emacs identifies an advice by its function, so a lambda cannot be removed without a name property. Tuna identifies it by its owner, so removing the line removes it.

M-: (evaluate an expression)user

Does. Evaluates one expression in your session. You can call any contract, reach private names (p::x), define your own names, and make changes: set!, verbs, redefine, advice-add, even define-worker.

Underneath. The session is an owner like a plugin’s run: its changes are layers and entries on its receipt. They last until you end the session or Tuna exits. A worker written here is compiled apart, as in a plugin.

Tuna · M-:
(keys:bind! "C-c t" (lambda () (files:find-file "~/todo.txt")))
Emacs
(keymap-global-set "C-c t" (lambda () (interactive) (find-file "~/todo.txt")))

Where Tuna differs. Emacs’s eval-expression changes things for good. Ending a Tuna session undoes everything done in it.

C-M-x (evaluate the definition at point, in a plugin’s file)user

Does. In a plugin’s plugin.ss, evaluates the definition around the cursor inside that plugin, at once. It lasts until the plugin is next reloaded from its file.

Underneath.

Errors. … is off; evaluating in it is refused; on define-syntax or define-record-type: … cannot change live; reload …, which rebuilds the plugins that use it.

Tuna · highlight/plugin.ss, cursor in the form
(define (highlight! id)
  (start-job find-hello (buffers:text id)
    (lambda (spans) (buffers:set-faces! id spans))
    for: id))
Emacs
;; C-M-x on a defun: eval-defun
(defun my-highlight () (my-scan (buffer-string)))

Where Tuna differs. C-M-x on a defvar in Emacs resets the variable; on a Tuna define-saved it resets the value but sets the old one aside, because it is your data.

reload-configuser

Does. Applies only what changed in init.ss since it last ran. A removed line is undone. A new line runs. A changed line runs anew, and its old version is undone only after the new one succeeded: if the new one fails, the old stays and you are told. A changed use-plugin line restarts that plugin with its new options. Replacing one implementation’s use-plugin line with another’s is a swap.

Underneath. Lines are matched by their text. The whole file is checked first, as at start; if the check fails nothing changes.

Tuna · M-x
(commands:run! 'reload-config)
Emacs
(load-file user-init-file)

Where Tuna differs. Loading init.el again runs it all again and undoes nothing you removed.

Turning off, reloading, swappinguser

Does. The core offers the operations; a plugin-management plugin offers the commands, whose names are not decided yet. The same results come from editing init.ss and reload-config: remove a line to turn a plugin off, add one to turn it on, replace one to swap. See reload, turn off and swap.

Underneath. The turn-off command first asks any question the plugin’s views would ask on closing (a running compile); the core itself never asks.

Errors. A reload whose new code drops a name another plugin uses is refused before anything changes: reload of dabbrev refused: tweaks uses dabbrev:expand at …/plugin.ss:3, which the new version does not export. A swap to a plugin that does not fill every contract the old one filled is refused.

Tuna · M-:
(commands:run! 'turn-off-plugin 'flymake)
Emacs
(unload-feature 'flymake t)

Ending the sessionuser

Does. Undoes everything you did in M-:, and with it your C-M-x edits. The command’s name is not decided.

Tuna · M-:
(commands:run! 'end-session)
Emacs
;; No equivalent: Emacs cannot undo evaluated expressions.
(restart-emacs)

(describe name)user

Does. Says what the core knows about a name: a plugin’s file, status, and the reason it is off; a function’s definer and layers (who redefined or advised it); an option’s value and layers, with their scopes; a table’s or hook’s entries and owners; saved data’s value and anything set aside. Commands that format this nicely belong to a help plugin.

Tuna · M-:
(describe 'settings:fill-column)
;; => (option settings:fill-column value 72
;;     layers (("init.ss:8" 72) ("settings (run 1)" 70)))
Emacs
(describe-variable 'fill-column)

Where Tuna differs. Emacs tells you a variable’s value and where it was defined, not who set it. Tuna lists every layer and its owner.

tuna checkuser

Does. From the shell, checks init.ss and the plugins it names without starting Tuna, and lists every error with its line. Planned.

shell
$ tuna check
init.ss:4: no plugin is named rop-store
init.ss:9: contract settings has no fill-colum
Emacs
;; Nearest: byte-compile the init file and read the warnings.
(byte-compile-file user-init-file)