Scheme Web Fun With Guile And Guix

7 May 2024 | programming guile guix webdev

I've just finished working through Teach Yourself Scheme in Fixnum Days by Dorai Sitaram. It was a wonderful read and if you are into scheme, I highly recommend this book. The main highlight for me was the focus on mathematical approaches from different fields. There's some numerical analysis, some logic, some calculus etc. It's all epic.

Personally, I got into programming because I felt it had the potential to make my work infinitely easier and make myself that much more effective. Going through this book made me feel vindicated.

Here is an example from the book; Simpson's Rule. I remember when I first learned this formula however many years ago it was. Every time I wanted to play with the formula, or I wanted to work through a particular problem in a certain way, I had to painstakingly do tedious recalculations that offered me no new insight per se.

I mean just look at this definition;

(define integrate-simpson
  (lambda (f a b n)

    (unless (even? n) (set! n (+ n 1)))

    (let* ((h (/ (- b a) n))
           (h*2 (* h 2))
           (n/2 (/ n 2))

           (sum-every-other-ordinate-starting-from
            (lambda (x0 num-ordinates)
              (let loop ((x x0) (i 0) (r 0))
                (if (>= i num-ordinates) r
                    (loop (+ x h*2)
                          (+ i 1)
                          (+ r (f x)))))))

           (y0+yn (+ (f a) (f b)))

           (y1+y3+...+y.n-1
            (sum-every-other-ordinate-starting-from
             (+ a h) n/2))

           (y2+y4+...+y.n-2
            (sum-every-other-ordinate-starting-from
             (+ a h*2) (- n/2 1))))

      (* 1/3 h
         (+ y0+yn
            (* 4.0 y1+y3+...+y.n-1)
            (* 2.0 y2+y4+...+y.n-2))))))

Elegant, simple and removes all the tedium. You have already thought about the problem. Let the machine do the mechanics. This part here is my favorite:

(sum-every-other-ordinate-starting-from
 (lambda (x0 num-ordinates)
   (let loop ((x x0) (i 0) (r 0))
     (if (>= i num-ordinates) r
         (loop (+ x h*2)
               (+ i 1)
               (+ r (f x)))))))

It is a procedure that takes a list of ordinates and sums them up. Simple. I remember doing this manually every single time and it was always boring as hell every single time.

It was the opposite of working smart. It wasn't even hard, it was just boring. It drove me nuts. Especially because my laptop would be right next to me streaming some music. I could not ignore just how illogical what I was doing was. I had a machine more powerful than the one that took men to the moon and yet here I was manually working through all these math problems. Like, why the hell do you even have a computer?

Don't even get me started on Emacs. Infinite undo, pen and paper do not even come close. 'Nuff said.

Hacks, Immediately

Anyway, the hacks for today revolve around a problem I had to get over when working through the book. There is a chapter about CGI scripts and I had to get a web server with CGI support up and running.

Now, as far as I know, your best options are Apache and Nginx. Luckily for us, Guix provides services for both. However, I couldn't get the Apache httpd-service-type to work. I was able to get nginx-service-type to work nonetheless.

Here is the service definition:

(service nginx-service-type
         (nginx-configuration
          (server-blocks
           (list (nginx-server-configuration
                  (listen '("5000"))
                  (server-name '("kaizerpublications.xyz-server"))
                  (root "/srv/http/kaizerpublications.xyz-server")
                  (locations
                   (list
                    (nginx-location-configuration
                     (uri "~ .scm$")
                     (body
                      (list
                       "
autoindex     on

fastcgi_pass  127.0.0.1:9000;

fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;

fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;

fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;
fastcgi_param  REQUEST_SCHEME     $scheme;
fastcgi_param  HTTPS              $https if_not_empty;

fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;

fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;

# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param  REDIRECT_STATUS    200;

"))))))))))

I can hear it already. Why do it this way when you can just use the default normal configuration combined with soystemd?

First, this guix is declarative, functional and transactional. No more guess-work. Configuring Linux is fun but can be a chore if you have to manually keep track of everything in your head or if you have to manually check the status of your system by scouring the entire OS.

Take these fastcgi_param configuration options. The last thing I want, is to have to keep these random strings in my head until some arbitrary date. I had to dig for examples and then finally found the manual reference. Once I have this information, I want to store it indefinitely in an accessible manner.

And because you are doing this in a real programming language not some bastardized INI format, you can use comments, control flow, logic, all those wonderful programming tricks you've learned till now. And you can even do this in a literate programming fashion and just explain yourself in beautiful, succinct prose.

And not to mention, using Guile to configure your system means you have access to a very expressive language when otherwise you would be stuck with counting spaces, or some such ritual because of strange syntax and you not only have to deal with other similarly arbitrary and confusingly challenging mazes of subjective syntax rules paired with inexpressiveness and *gasp inelegance.

Nginx does not support CGI out of the box. It uses a FastCGI module and you need to have fastcgi and fastcgiwrapper installed on the system. There is a fcgiwrap-service-type to run this for you with sane defaults.

One thing to note that took me a day to figure out, services do not install programs into user-space. This is the best way I can describe this at the moment.

Let's use fcgiwrap-service-type as an example. This service runs as a special user, but that user "derives" permissions from your user. So how can the program run if your user has not installed it? Another example, the tor-service-type also runs as its own user and so it creates its own directories. However, if you remove this service without removing those directories, should you ever enable this service without deleting those files, it will fail to build those directories and consequently fail to run.

In general, this is a permissions thing, but I'm sure you can imagine all sorts of ways where the state of the system and the state you have stipulated get out of sync and pass through the cracks. The system will build but your services will not run, despite being configured correctly.

Personally, I enjoy these problems because it tests my understanding, but YMMV. I think it is more like Doom on Ultra Nightmare - sure the hoards may overwhelm you, but what other better canvas to create inspiration than overcoming desperation?

Plus, everything is in Guile Scheme, what more could you want?

Honestly, with your fancy server and fcgi setup, you can run guile scripts in the browser! Check it Dorai with a CGI calculator in Scheme FTW:

#!/bin/sh
# -*- scheme -*-
":";exec guile -s $0
!#

<<load-relative>>

;; Load the CGI utilities
(load-relative "cgi.scm")

(define uhoh #f)

(define calc-eval
  (lambda (e)
    (if (pair? e)
        (apply (ensure-operator (car e))
               (map calc-eval (cdr e)))
        (ensure-number e))))

(define ensure-operator
  (lambda (e)
    (case e
      ((+) +)
      ((-) -)
      ((*) *)
      ((/) /)
      ((**) expt)
      (else (uhoh "unpermitted operator")))))

(define ensure-number
  (lambda (e)
    (if (number? e) e
        (uhoh "non-number"))))

(define print-form
  (lambda ()
    (display "<form action=\"")
    (display (getenv "SCRIPT_NAME"))
    (display "\">
Enter arithmetic expression:<br>
<input type=textarea name=arithexp><p>
<input type=submit value=\"Evaluate\">
<input type=reset value=\"Clear\">
</form>")))

(define print-page-begin
  (lambda ()
    (display "content-type: text/html") (newline)
    (newline)

    (display "
<html>
<head>
<title>A Scheme Calculator</title>
</head>
<body>")))

(define print-page-end
  (lambda ()
    (display "</body>
</html>")))

(parse-form-data)

(print-page-begin)

(let ((e (form-data-get "arithexp")))
  (unless (null? e)
    (let ((e1 (car e)))
      (display-html e1)
      (display "<p>
=&gt;&nbsp;&nbsp;")
      (display-html
       (call/cc
        (lambda (k)
          (set! uhoh
                (lambda (s)
                  (k (string-append "Error: " s))))
          (number->string
           (calc-eval (read (open-input-string (car e))))))))
      (display "<p>"))))

(print-form)

(print-page-end)

That's like a reverse slam dunk bro.

Look at the invocation of guile:

":";exec guile -s $0

That is the definition of magic. It's parsed by two languages and both interpret it independently and converge beautifully.

From the book:

Everything following the first line is straight Scheme. However, the first line is the magic that makes this into a script. When the user types hello at the Unix prompt, Unix will read the file as a regular script. The first thing it sees is the ":", which is a shell no-op. The ; is the shell command separator. The next shell command is the exec. exec tells Unix to abandon the current script and run mzscheme -r $0 "$@" instead, where the parameter $0 will be replaced by the name of the script, and the parameter "$@" will be replaced by the list of arguments given by the user to the script. (In this case, there are no such arguments.)

Magic.

Oh, and we are only touching the surface here. You can get really wild with haunt. But if you really want all the power, then hoot is what you are looking for.

Of course, we will come back to these later.

I am not quite sure how to end this one. The thing is, I initially just wanted a faster, smarter calculator. Little did I know, I was getting a lifestyle. I can hardly imagine not being immersed in parentheses ever again. I mean, you practically grow your own language the more you use them. It is only natural, what with that .guile just sitting there. Your /srv/http/<your-website>/cgi-bin/ just sitting there ready for some scripts!

Personally, it is not just the elegance of using Scheme, it is the freedom of having powerful tools at your disposal. Of course, you still need a thorough understanding of mathematics and the hard sciences in general. However, at that point, you need capable tools otherwise you are limited to pencil and paper. And that is tedious. Boring.

One more thing. People are skeptical whether all this effort is worth it. And I say yes.

The thing is, even before anybody pays you for this knowledge, you benefit first. You get to have all the fun. For free. Is that not kind of the point of learning in the first place? If you do things just to impress people, you will be very unhappy when they move their attention elsewhere should you be even mildly successful. And beyond that, even as a professional, you should be using the best tools at your disposal.

This is free. You have no excuse.

This is fun. Trust me bro, those parens just melt away.