27 April 2024 |
People wonder why Emacs is so important to me. I wonder how to explain it to them. When I found about Emacs, I had to change everything about the way I worked.
It was only natural.
I wrote a lot, both ways - paper/pencil and electronically, and I used to write professionally. I read a lot as well. I have always loved doing this since I was a kid.
So, I was beyond shocked that my whole life I had never seen Emacs, I felt cheated. I had not even seen the vim or even heard of Linux. Or maybe I just was not ready for it…
Dude, I can even resize images in Emacs and the export will have the same size!!
Emacs has this wonderful thing called Org-mode. Org itself is a lightweight syntax like markdown. Only infinitely better. It is simple, consistent and very expressive. Combined with Emacs, it is a thrill.
We are going take a wild ride, you and me. And Org-mode. And the rest of Emacs.
So, I am learning Scheme. The most fantastic language ever written. It is so beautiful, it cleanses the soul. Do not just take my word for it, Scheme is going to feature heavily today as well.
Dorai Sitaram has a wonderful book on Scheme. We are going to be borrowing some examples from it.
I am currently on the chapter on nondeterminism and Dorai uses the amb procedure made by McCarthy to show us how to use nondeterminism in our programs. It was a hell of a lot of fun. The final algorithm uses continuations, depth-first search and chronological backtracking for failure cases.
When defining the algorithm, or working through the chapters of the book, you would structure your notes in pieces that make sense to you. But what if you need some result from an earlier part? Or some procedure in another file?
For those who do not know, Org-mode syntax is not just static semantic markup. Things like headers or whatever. Which are powerful in their own right. But not what is important at the moment.
We can have, source code blocks that we can evaluate, get the results and use like building blocks. A common use case is tangling the blocks so that we can build programs from a document.
We can use Org-mode source code blocks to write these examples, complete with syntax-highlighting, formatting and the ability to evaluate them on the spot.
We can place these blocks wherever we want and evaluate almost any language. With a bit of creativity, almost anything is possible, including dealing with our note-taking problems.
There are many creative ways to use source code blocks but we can use the :session header tag to specify that our code is running within a particular session. We can use this in order to break up our code in a way that makes sense to us.
For example according to the book, we define the amb-fail variable first then amb procedure itself. By using :session tags, we can break them up and write everything conveniently.
(define amb-fail '*) (define initialize-amb-fail (lambda () (set! amb-fail (lambda () (error "amb tree exhausted"))))) (initialize-amb-fail)
#<unspecified>
We evaluate the block to get the results by pressing ENTER. I cannot even.
BTW, I use a heavily modified version of Doom Emacs that grows only more complicated by the day but this is why I do it. For satisfying moments like this.
(define-macro amb (lambda alts... `(let ((+prev-amb-fail amb-fail)) (call/cc (lambda (+sk) ,@(map (lambda (alt) `(call/cc (lambda (+fk) (set! amb-fail (lambda () (set! amb-fail +prev-amb-fail) (+fk 'fail))) (+sk ,alt)))) alts...) (+prev-amb-fail)))))) (amb 1 2)
1
Can you see how working through this in a static way is sub-optimal to say the least? When working through complicated topics, you have to juggle these concepts in your head but if your tools are not flexible enough, you have to compensate by doing funny things.
There is an art to everything, for sure, but I remember lugging my precious notebooks around just for reference and while it made for good exercise, every time I never used I book, I doubt I got over the resentment over wasted effort.
The note-taking process itself is tedious, error-prone and worst of all, you cannot undo! There is no infinite undo, unlike in Emacs, just infinite redo!!! 😱
This post is being made in Emacs. I can write as the ideas come into my head and Emacs is just where I dump these ideas. It does not get in the way. I craft it to fit what I want to do. I can get lost in the parens and just focus.
There is so much we can talk about. Mu4e for email, emms for music/video/url playback, jabber.el, circe, ement.el, mastodon.el for all the social media you could ever want. eww for browsing. pdftools and nov.el for pdf and epub viewing. calibredb for library management. dired! How can we forget about dired! Where would we be without dired? We just used geiser as well for our Scheme programming.
I can go on and on. We have not even talked about evil-mode the vi-emulation layer for vim addicts like myself. Emacs has a gui btw. And you can have such beautiful typesetting while typing its unreal.
magit!! So I never have to learn git.
We have not even talked about the fact that Emacs is at its heart an elisp interpreter. This is Emacs real killer feature. Everything is alive and kicking and just waiting to be moulded in a weapon of mass text construction, destruction and reconstruction.
But I digress, however, I hope the point is not lost. Our tools are very important!!!
Now, not only are we taking advantage of a digital format coupled with amazing tools, we are getting things done!
Ok, maybe not quite.
Remember the aforementioned tangling? We can use that to write out files to disk and then run them interactively with a shell source block.
Like so:
(define-module (nondeter) #:export (amb) #:export (amb-fail)) (define amb-fail '*) (define initialize-amb-fail (lambda () (set! amb-fail (lambda () (error "amb tree exhausted"))))) (initialize-amb-fail) (define-macro amb (lambda alts... `(let ((+prev-amb-fail amb-fail)) (call/cc (lambda (+sk) ,@(map (lambda (alt) `(call/cc (lambda (+fk) (set! amb-fail (lambda () (set! amb-fail +prev-amb-fail) (+fk 'fail))) (+sk ,alt)))) alts...) (+prev-amb-fail))))))
#!/bin/bash # -*- scheme -*- exec guile -l ./scripts/amb.scm -e '(@ (test) main)' -s "$0" "$@" !# (define-module (test) #:export (main)) (use-modules (nondeter)) (define (main args) (newline) (display (amb (cadr args) (caddr args))))
./scripts/amb-1.scm 1 2 2>&1
1
Okay, let us look at what we have done.
The :tangle ./scripts/amb-1.scm part just tells Emacs where to tangle (copy/send) the block as a file.
The :tangle-mode (identity #o755) sets the permissions on the tangled file.
The :results output is self-explanatory but it is relevant because we are redirecting stderr to stdout when running our script, using 2>&1. It helps when debugging the program as you write it while still in org and it also allows us to use the display procedure to show our results.
We tangled out two script files because we might want to reuse the amb procedure elsewhere and because we want a concise definition for our main example as we progress through the notes.
Because we did that, we can leverage the scripting features of guile in our example which allows us to create simple program we can call interactively from wherever. Including our org buffer.
The header is what is particularly significant:
#!/bin/bash # -*- scheme -*- exec guile -l ./scripts/amb.scm -e '(@ (test) main)' -s "$0" "$@" !#
We make the script as portable as possible and on guix this is the best way so far I have found to run Guile scripts.
# -*- scheme -*-
This is a wonderful convenience for Emacs that allows us to specify that the main mode for viewing the file should be the Scheme mode. Emacs can interpret the shebang as the start of a shell script and put you in shell-mode instead.
Guile scripts themselves are simple enough to setup but you just need to keep in mind that you can load files with the -l flag, execute an exported procedure with the -e flag and finally the -s file loads the file as a script.
The $0 is an in-built shell variable meaning the current file. The $@ is another inbuilt variable that stands for the arguments passed to script as well as a reference to the program. However, we do not this variable name in our program if we do not want to. We can just pass args to main and be done with it. We will manipulate args in the program itself.
Ok, I know. It looks clunky, hacky, or even gasp! inelegant.
I think it is fun and might be useful depending on the context but we can do better. We can use the :noweb header argument to specify that we want to use noweb syntax.
This allows us to insert a named org src block into our file verbatim like so:
First, we must have a #+name: <preferred name> argument just above the src block like so:
#+name: amb-fail #+begin_src scheme ;; code #+end_src
The following example:
#+name: amb-fail
#+begin_src scheme
(define amb-fail '*)
(define initialize-amb-fail
(lambda ()
(set! amb-fail
(lambda ()
(error "amb tree exhausted")))))
(initialize-amb-fail)
#+end_src
#+name: amb
#+begin_src scheme :noweb yes
<<amb-fail>>
(define-macro amb
(lambda alts...
`(let ((+prev-amb-fail amb-fail))
(call/cc
(lambda (+sk)
,@(map (lambda (alt)
`(call/cc
(lambda (+fk)
(set! amb-fail
(lambda ()
(set! amb-fail +prev-amb-fail)
(+fk 'fail)))
(+sk ,alt))))
alts...)
(+prev-amb-fail))))))
#+end_src
#+begin_src scheme :noweb yes :results output
<<amb>>
(display (amb))
#+end_src
expands to:
(define amb-fail '*) (define initialize-amb-fail (lambda () (set! amb-fail (lambda () (error "amb tree exhausted"))))) (initialize-amb-fail)
(define-macro amb (lambda alts... `(let ((+prev-amb-fail amb-fail)) (call/cc (lambda (+sk) ,@(map (lambda (alt) `(call/cc (lambda (+fk) (set! amb-fail (lambda () (set! amb-fail +prev-amb-fail) (+fk 'fail))) (+sk ,alt)))) alts...) (+prev-amb-fail))))))
(display (amb))
ice-9/boot-9.scm:1676:22: In procedure raise-exception: Unbound variable: amb Entering a new prompt. Type `,bt' for a backtrace or `,q' to continue. scheme@(guile-user) [1]>
It could pass for scheme…
We can reuse them as much as we want within the document.
It is also exported completed.
Now, these are just some basic ideas. But I really want to emphasize the power of the using the right tools. Maybe it seems like this is only useful for programming but that is a limited view of what you can do.
You can work with multiple languages and what is more you still have the rest of the Org syntax to help you write.
The simplicity. The effectiveness! The limitless possibilities.
You can use Org-mode to create agendas, track time, manage finances, create reproducible research and so much more. The ability to use source code blocks interactively is just a fraction of what Emacs does that blows all the competition out of the water.
You can export to any format by the by. This blog is written in Org-mode, and generated using Guile scheme. When making a post, I just open the kaizerpub-haunt/org directory and write away. It automatically exports it to a format compatible with haunt - a static site generator written and configured in Guile scheme.
From there, I just haunt build, then haunt serve -w to see as I make edits live and finally haunt publish to export the build to the git repo. SPC g g and I am in magit staging changes with s (I still cannot believe it), c c, to commit and then put the commit message, finally, p u and I am done. It asks for the password and stuff and it is only cause I have not set that up yet.
Do you see? It is so empowering. You can reach the whole world in Schemin Fashion.
This is on your local machine. No subscriptions. Use Guix and you have got a completely moldable system. Doom Emacs for maximum efficiency.
I have got more to say but it will have to wait for now. This is getting too long anyway!
Hack the great hack!
See ya!