Elmord's Magic Valley

Computers, languages, and computer languages. Às vezes em Português, sometimes in English.

Updates on Fenius and life

2024-03-26 15:19 +0000. Tags: comp, prog, pldesign, fenius, lisp, life, in-english


Over the last couple of months (but mainly over the last four weeks or so), I’ve been working on the Fenius interpreter, refactoring it and adding features. The latest significant feature was the ability to import Common Lisp packages, and support for keyword arguments in a Common-Lisp-compatible way, i.e., f(x, y=z) ends up invoking (f x :y z), i.e., f with three arguments, x, the keyword :y, and z. Although this can lead to weird results if keyword arguments are passed where positional arguments are expected or vice-versa (a keyword like :y may end up being interpreted as a regular positional value rather than as the key of the next argument), the semantics is exactly the same as in Common Lisp, which means we can call Common Lisp functions from Fenius (and vice-versa) transparently. Coupled with the ability to import Common Lisp packages, this means that we can write some useful pieces of code even though Fenius still doesn’t have much in its standard library. For example, this little script accepts HTTP requests and responds with a message and the parsed data from the request headers (yes, I know that it’s not even close to fully supporting the HTTP standard, but this is just a demonstration of what can be done):

# Import the Common Lisp standard functions, as well as SBCL's socket library.
let lisp = importLispPackage("COMMON-LISP")
let sockets = importLispPackage("SB-BSD-SOCKETS")

# We need a few Common Lisp keywords (think of it as constants)
# to pass to the socket library.
let STREAM = getLispValue("KEYWORD", "STREAM")
let TCP = getLispValue("KEYWORD", "TCP")

# Import an internal function from the Fenius interpreter.
# This should be exposed in the Fenius standard library, but we don't have much
# of a standard library yet.
let makePort = getLispFunction("FENIUS", "MAKE-PORT")

# Add a `split` method to the builtin `Str` class.
# This syntax is provisional (as is most of the language anyway).
# `@key start=0` defines a keyword argument `start` with default value 0.
method (self: Str).split(separator, @key start=0) = {
    if start > self.charCount() {
    } else {
        let position = lisp.search(separator, self, start2=start)
        let end = (if position == [] then self.charCount() else position)
            lisp.subseq(self, start, end),
            self.split(separator, start=end+separator.charCount()),

# Listen to TCP port 8000 and wait for requests.
let main() = {
    let socket = sockets.makeInetSocket(STREAM, TCP)
    sockets.socketBind(socket, (0,0,0,0), 8000)
    sockets.socketListen(socket, 10)


# Process one request and call itself recursively to loop.
let serveRequests(socket) = {
    print("Accepting connections...")

    let client = sockets.socketAccept(socket)
    print("Client: ", client)
    let clientStream = sockets.socketMakeStream(client, input=true, output=true)
    let clientPort = makePort(stream=clientStream, path="<client>")
    let request = parseRequest(clientPort)

    clientPort.print("HTTP/1.0 200 OK")
    clientPort.print("Hello from Fenius!")



# Remove the "\r" from HTTP headers. We don't have "\r" syntax yet, so we call
# Common Lisp's `(code-char 13)` to get us a \r character (ASCII value 13).
let strip(text) = lisp.remove(lisp.codeChar(13), text)

# Define a structure to contain data about an HTTP request.
# `@key` defines the constructor as taking keyword (rather than positional) arguments.
record HttpRequest(@key method, path, headers)

# Read an HTTP request from the client socket and return an HttpRequest value.
let parseRequest(port) = {
    let firstLine = strip(port.readLine()).split(" ")
    let method = firstLine[0]
    let path = firstLine[1]
    let protocolVersion = firstLine[2]

    let headers = parseHeaders(port)

    HttpRequest(method=method, path=path, headers=headers)

# Parse the headers of an HTTP request.
let parseHeaders(port) = {
    let line = strip(port.readLine())
    if line == "" {
    } else {
        let items = line.split(": ") # todo: split only once
        let key = items[0]
        let value = items[1]
        lisp.cons((key, value), parseHeaders(port))


Having reached this stage, it’s easier for me to just start trying to use the language to write small programs and get an idea of what is missing, what works well and what doesn’t, and so on.

One open question going forward is how much I should lean on Common Lisp compatibility. In one direction, I might go all-in into compatibility and integration into the Common Lisp ecosystem. This would give Fenius easy access to a whole lot of existing libraries, but on the other hand would limit how much we can deviate from Common Lisp semantics, and the language might end up being not much more than a skin over Common Lisp, albeit with a cleaner standard library. That might actually be a useful thing in itself, considering the success of ReasonML (which is basically a skin over OCaml).

In the opposite direction, I might try to not rely on Common Lisp too much, which means having to write more libraries instead of using existing ones, but also opens up the way for a future standalone Fenius implementation.


I quit my job about 6 months ago. My plan was to relax a bit and work on Fenius (among other things), but I’ve only been able to really start working on it regularly over the last month. I’ve been mostly recovering from burnout, and only recently have started to get back my motivation to sit down and code things. I’ve also been reading stuff on Old Chinese (and watching a lot of great videos from Nathan Hill’s channel), and re-reading some Le Guin books, as well as visiting and hosting friends and family.

I would like to go on with this sabbatical of sorts, but unfortunately money is finite, my apartment rental contract ends by the end of July, and the feudal lord wants to raise the rent by over 40%, which means I will have to (1) get a job in the upcoming months, and (2) probably move out of Lisbon. I’m thinking of trying to find some kind of part-time job, or go freelancing, so I have extra time and braincells to work on my personal projects. We will see how this plays out.


That’s all for now, folks! See you next time with more thoughts on Fenius and other shenanigans.

Comentários / Comments

Fixing audio device priorities in PulseAudio

2023-10-31 16:45 +0000. Tags: comp, unix, audio, in-english

Ever since I started using my current laptop (a ThinkPad E14 Gen 4, currently running Debian 12), I have had the following issue: when I connect headphones to the laptop, the audio goes to the headphones as expected, but when I disconnect the headphones, the audio goes to the HDMI output instead of going back to the laptop speakers. As it happens, my external monitor (an Eizo FlexScan EV2360) has audio output, but it’s pretty low-quality and I don’t use it. But PulseAudio (or is it ALSA?) assigns higher priority to the HDMI outputs, so as soon as the headphones are disconnected, it looks for the available device with the highest priority and picks the HDMI one. You can see the priority of each audio sink (output) by using pactl:

$ pactl list sinks | grep priority:
        [Out] HDMI3: HDMI / DisplayPort 3 Output (type: HDMI, priority: 700, not available)
        [Out] HDMI2: HDMI / DisplayPort 2 Output (type: HDMI, priority: 600, not available)
        [Out] HDMI1: HDMI / DisplayPort 1 Output (type: HDMI, priority: 500, available)
        [Out] Speaker: Speaker (type: Speaker, priority: 100, availability unknown)
        [Out] Headphones: Headphones (type: Headphones, priority: 200, not available)

In this case, the HDMI1 output is available and has priority 500, whereas the speakers have priority 100.

The solution is to change the HDMI priorities. The problem is where to set this. In my particular case, this was set in /usr/share/alsa/ucm2/Intel/sof-hda-dsp/Hdmi.conf (included from /usr/share/alsa/ucm2/Intel/sof-hda-dsp/Hdmi.conf), which looks like this:

# Use case Configuration for sof-hda-dsp

Include.hdmi.File "/codecs/hda/hdmi.conf"

If.hdmi1 {
    Condition { Type AlwaysTrue }
    True.Macro.hdmi1.HDMI {
        Number 1
        Device 3
        Priority 500

If.hdmi2 {
    Condition { Type AlwaysTrue }
    True.Macro.hdmi1.HDMI {
        Number 2
        Device 4
        Priority 600

If.hdmi3 {
    Condition { Type AlwaysTrue }
    True.Macro.hdmi1.HDMI {
        Number 3
        Device 5
        Priority 700

I just changed the values 500, 600, 700 manually to 50, 60, 70 in this file. This is not a very good solution because this file belongs to the alsa-ucm-conf package and will get overridden whenever I upgrade it, but since this is Debian stable, I don’t have to worry about this any time soon. There is probably a better way to override these values, but I don’t know enough about either ALSA or PulseAudio (and I’m not particularly keen on learning more, unless my fellow readers know and want to leave a helpful comment), so this will have to do for now.

* * *

Another recurring issue is getting Bluetooth headphones to become the selected default device when they are connected. It seems that Bluetooth devices get created dynamically with a bunch of hardcoded priorities (and worse, sometimes I get a device with priority 40 and sometimes 0, I don’t know why). But it also seems that the priorities just don’t have any effect on the selection of the Bluetooth device. I was having a curious issue where some programs would pick the headphones and some wouldn’t, and the default device would remain unchanged (which among other things meant that my multimedia keys set the volume of the wrong device). What seems to be going on is that PulseAudio remembered the associations of certain programs (e.g., VLC) with the headphones, but only for those programs where I had at some point manually changed the output sink manually via pavucontrol. The solution here was two-step:

  1. In /etc/pulse/default.pa, replace the line that says:

    load-module module-stream-restore


    load-module module-stream-restore restore_device=false

    This will make PulseAudio not try to remember associations between specific programs and devices. From now on, all programs get the default sources/sinks when they connect to PulseAudio.

  2. Set the default sink manually to the Bluetooth headphones. Use pactl list sinks to figure out the sink name:

    $ pactl list sinks  | grep Name:
            Name: alsa_output.pci-0000_00_1f.3-platform-skl_hda_dsp_generic.HiFi__hw_sofhdadsp_5__sink
            Name: alsa_output.pci-0000_00_1f.3-platform-skl_hda_dsp_generic.HiFi__hw_sofhdadsp_4__sink
            Name: alsa_output.pci-0000_00_1f.3-platform-skl_hda_dsp_generic.HiFi__hw_sofhdadsp_3__sink
            Name: alsa_output.pci-0000_00_1f.3-platform-skl_hda_dsp_generic.HiFi__hw_sofhdadsp__sink
            Name: bluez_sink.C8_7B_23_9F_B3_21.a2dp_sink

    Then set it (replacing with the appropriate device name):

    pactl set-default-sink bluez_sink.C8_7B_23_9F_B3_21.a2dp_sink

The result of this is that PulseAudio will remember the headphones as the default sink device when they are present, but will revert to the built-in sound card when not.

I still don’t know why this works even though the Bluetooth device’s priority is lower than the built-in sound card. (There is a PulseAudio module called module-switch-on-connect that provides behavior like this, but it is not enabled on my system, and it does not show up as loaded in the pactl list output.) But It Works For Me™.

Comentários / Comments

The day I almost switched to Wayland

2023-10-10 19:50 +0100. Tags: comp, unix, x11, wayland, in-english

A couple of days ago, I decided to give Wayland a go. For those of you who live under a rock, Wayland is meant to be the successor of X11, the traditional graphical server/protocol in the GNU/Linux world, responsible for things such as drawing windows on your screen, passing keyboard and mouse events to the correct programs, etc. (I live under a rock too, but sometimes I stretch my head out to see what is going on in the world, only to crawl back not long after.)

Like with most technology transitions, there is a lot of drama going around Wayland vs. X11 and their respective merits and demerits. Reading such discussions can be quite frustrating; not only people can have widely different usage and requirements from graphics functionality – gaming (with different kinds of games), watching videos with varying resolutions and refresh rates, different graphics cards, accessibility settings, desktop environments, etc. –, but people can also differ in how they perceive little functionality changes just because of variation in how their eyes or brains work. A relatively minor glitch (such as screen tearing while playing a video, a couple milliseconds extra delay to process a keystroke in a game, or a font that renders slightly differently) can be a huge annoyance to one person, barely noticeable to another, and literally invisible to a third one. The result is that such discussions feel like people are talking past one another, unable to understand why others would make a different choice from them and insist on being wrong on the internet. People in the GNU/Linux world are also used to enjoying immense freedom to choose, customize and build their own desktop environments, which also contributes to the wide variety of experiences and difficulties in switching from one graphical stack to another.

If you want to understand why Wayland exists, you can watch The real story behind Wayland and X, a Linux.conf.au presentation by Daniel Stone, a Wayland and former X.org developer. Basically, X.org contains a huge number of features that are not used by modern clients but have to be kept around for compatibility, and limit the ways in which problems with X can be solved. Originally, the X server used to be responsible for font rendering, drawing graphical primitives, and a variety of other functions that nowadays are done by the clients themselves; modern clients usually just want to send a fully rendered image for the server to display. All the old cruft accumulated across the four decades of X11’s existence make it hard to maintain for developers.

One thing that is noticeable in these discussions about X11 vs Wayland is that one hears a lot from X11 users defending X11, Wayland users defending Wayland, Wayland developers defending Wayland, but not much from X11 developers. The main reason for this is that X11 developers are Wayland developers by and large. The X.org server is pretty much in maintenance mode, and much if not most of development that still goes on in the xserver repo is related to Xwayland, the compatibility layer that allows X11 clients to run on Wayland. As much as we may like X.org, if developers don’t want to work on it, there’s not much we can do about it (and it seems that it’s pretty hard for new developers to get started on it, due to the accumulated complexity). Granted, X.org isn’t going away any time soon, but it’s also not going anywhere. Regardless of the technical merits of Wayland vs. X11, it seems pretty clear that Wayland is the future going forward.

First impressions

So far I have stayed in the comfort of my old X11 setup, mainly because I had no reason to put an effort into switching. A reason finally showed up, though: on my current laptop (a ThinkPad E14 4th generation), I see quite a bit more tearing while watching videos than on my previous PCs. Although it is within the range of what I can live with (after all I’ve been using this computer for almost a year like this), all else being equal, it’s something I would like to get rid of.

The first step into switching to Wayland is picking a compositor: the application responsible for managing windows and drawing on the screen. On X11, the window manager and the X server are two different programs; on Wayland, both of these roles are taken by the compositor. The idea here is to cut out the middleman since (1) nowadays the graphics card driver lives in the kernel, which exposes it as a framebuffer device, unlike in the olden days where you would have different X drivers to handle different graphics cards, and (2) most modern window managers do compositing anyway, so instead of having the window manager composite the image of the whole desktop, then give it to X to draw it on the screen, the compositor can write it directly to the graphics card.

This means that there are effectively as many Wayland servers as there are window managers out there. This is annoying because the compositor is not only responsible for managing windows, but also handling input devices, keyboard layouts, accessibility features, clipboard, and a variety of other things that were traditionally handled by the X server. Each compositor has to implement these features on its own, and although there are common libraries that are used by different compositors to implement some of these features (e.g., libinput), there is often no standard way to access those features that is portable across different compositors. For instance:

Some of these features may end up being standardized as protocol extensions (see wlr-protocols and wayland-protocols), but which protocols will be supported by each compositor will vary. This feels like the situation in Scheme with its various SRFIs that different implementations may or may not support, or XMPP where support for a feature depends on the client and server supporting the desired set of extensions. I suppose the situation will improve in the upcoming years as the set of protocol extensions gets more standardized, but the current situation is this. The thing is that this is a non-issue in X: new window managers don’t need to care about any of this, because the X server handles these the same way regardless of what is your window manager.

As soon as I open the Sway session and start up lxterminal, I notice an issue: lxterminal on Wayland is not honoring my FREETYPE_PROPERTIES='truetype:interpreter-version=35' environment variable. This setting changes the font rendering algorithm such that fonts look crispier, especially in non-HiDPI displays. This is well within the “some people won’t even notice” category, but for me the difference is noticeable (particularly in my external display), it took me ages to figure out this setting existed, and I’m not willing to give it up easily (at least not until I switch to an HiDPI external display, something that probably won’t happen within the next couple of years). I noticed that Emacs did not suffer from this issue, but it turns out Emacs was running under Xwayland. It’s nice indeed to see that X11 apps run seamlessly enough under Wayland that it took me some work to realize that it was running under Xwayland and not natively. (I figured it out by calling xprop: it only reacts to clicks on X11 windows.) I installed foot, a lightweight native Wayland terminal that is recommended by the sway package on Debian, and it also suffers from this issue. So it seems to be a general issue with Freetype under Wayland, which is weird because font rendering should be a client-side problem and should be the same under X11 and Wayland.

Finally, I tried to start up the NetworkManager applet. Just running nm-applet won’t show anything, because by default nm-applet uses the Xembed protocol to create a tray icon, which is not supported by Swaybar; you have to run nm-applet --indicator instead. However, clicking on the icon does nothing; it seems that the context menu on tray icons is currently not supported (as of Sway 1.7). It does work with Waybar (and the context menu has the same font rendering issue; in fact I’m not even sure it’s using the same font), but Waybar has a lot more bells and whistles I’m not interested in, plus I would have to figure out how to adapt my current status bar script to it (assuming it’s possible), which is pretty important to me as I use the i3 status bar to display desktop notifications.

The lack of Xembed tray icon support is a problem for another program I use as well: Thunderbird. As of 2023, I’m still using Thunderbird 52 (released in 2018) because it’s the last version that supports FireTray, which shows me a glorious tray icon with a number of unread messages whenever there are unread messages in selected folders, and no icon otherwise. I know that one day I will probably have to switch to a different mail client and/or workflow, but that day is not going to be now.

It does eliminate screen tearing, though. But also it turns out I could fix that on X.org by using picom --backend glx --vsync. [Update: Actually that doesn't fully fix it, and sometimes causes other glitches of its own. Wayland wins in this regard.]

Crawling back to under my rock

In The technical merits of Wayland are mostly irrelevant, Chris Siebenmann argues that everyone who would switch from X to Wayland by virtue of its technical merits has already switched. The people who haven’t done so fall into a bunch of categories, one of which is:

People using desktop environments or custom X setups that don’t (currently) support Wayland. Switching to Wayland is extremely non-transparent for these people because they will have to change their desktop environment (so far, to GNOME or KDE) or reconstruct a Wayland version of it.

I happen to be in this category. Sway can mostly replace i3, but then I have to find a replacement for nm-applet (or use Waybar and change my status bar script), a replacement for FireTray, rewrite my jump-to-window script, figure out what the heck is going on with the font rendering, change my scripts that currently use xrandr, etc. All of this to get a desktop just as good as my current X11 one; switching to Wayland does not really bring me any new functionality or improvements. Maybe a few years from now, as X.org starts to bit-rot and new stuff starts to be developed for Wayland exclusively, switching will become more compelling. As of 2023, though, I don’t really have much to gain from it, and I’d rather spend my time on other adventures, at least for now.

But I don’t share the Wayland hate I see in various places around the interwebs. When the time comes (or when I feel like it), I will make the switch. By then, hopefully some of the issues above will have been fixed (e.g., more standardized protocol extensions, menu support for Swaybar tray icons), hopefully I will have found a replacement for FireTray, and maybe I will have switched to a HiDPI external monitor. Until then, long live X.org.

2 comentários / comments

Trying ChatGPT

2023-02-11 14:26 +0000. Tags: comp, ai, in-english

I have been curious about ChatGPT for a while after seeing some examples by Crista Lopes at Tagide Research (1 2 3 4). A couple of days ago I decided to try it myself.

The experience has been very enlightening, both in showing me what ChatGPT can do and what it can’t do (which sheds some light into what it is and what it isn’t). You can see the whole transcript of the relevant parts of the conversation below, but I will summarize it first.

The good

First, about what it can do:

Up to this point, the answers had been flawless.

Below is the transcript of this part of the conversation. If you don’t want to read the whole thing, you can skip ahead.

Me: Ok! Now let me ask you some programming questions. Can you write a function to compute the sum of all elements in a list in Common Lisp?

ChatGPT: Yes, you can write a function to compute the sum of all elements in a list in Common Lisp. Here’s one example implementation:

(defun sum-list (list)
  (if (null list)
      (+ (car list) (sum-list (cdr list)))))

In this implementation, the sum-list function takes a list as an argument and uses recursion to iterate through each element in the list. The if statement checks if the list is empty (i.e., null). If the list is empty, the function returns 0. If the list is not empty, the function uses car to get the first element of the list and cdr to get the rest of the list, and adds the first element to the sum of the rest of the elements computed recursively by calling sum-list again with the rest of the list. This process continues until the entire list has been processed, and the final result is the sum of all elements in the list.

Me: Good! Are there any drawbacks in using this recursive approach?

ChatGPT: Yes, there are some potential drawbacks to using the recursive approach in computing the sum of all elements in a list in Common Lisp:

  • Performance: Recursive algorithms can be less efficient than iterative algorithms in terms of time and space complexity, especially when the size of the list is large. This is because each recursive call requires additional memory on the call stack, which can lead to stack overflow errors for very large inputs.

  • Complexity: Recursive algorithms can sometimes be more complex to understand and debug compared to iterative algorithms, especially for those who are not familiar with recursion.

  • Tail recursion: Common Lisp supports tail recursion optimization, which means that if the recursive call is the last operation performed in the function, the implementation can optimize the call so that it does not add a new frame to the call stack, avoiding stack overflow errors. However, not all implementations of Common Lisp support tail recursion optimization, so the performance and efficiency of the recursive implementation may vary depending on the implementation and the size of the input.

That being said, recursive algorithms can still be a good choice in some cases, particularly for problems that can be naturally expressed in a recursive manner. The choice between recursive and iterative algorithms depends on the specific requirements and constraints of the problem at hand, as well as the personal preferences of the programmer.

Me: Could you write an iterative version of that function?

ChatGPT: Yes, here’s an iterative version of the function to compute the sum of all elements in a list in Common Lisp:

(defun sum-list (list)
  (let ((sum 0))
    (loop for element in list do
          (setf sum (+ sum element)))

In this implementation, the function uses a loop construct to iterate through the elements in the list. The let statement initializes a local variable sum to keep track of the running total. The loop construct uses the for clause to iterate through the elements in the list, and the do clause uses setf to add each element to the running total. Finally, the function returns the running total stored in sum.

This iterative implementation is more efficient and has a simpler control flow than the recursive version, especially for large inputs, as it does not add a new frame to the call stack for each element in the list. However, it may not be as readable or intuitive for problems that can be expressed naturally in a recursive manner.

Me: Ok. Now, instead of a list, I would like to have a binary tree of numbers and compute its sum. How would you do it?

ChatGPT: You can write a function to compute the sum of all elements in a binary tree in Common Lisp using either a recursive or an iterative approach. Here’s a recursive implementation:

(defun sum-tree (tree)
  (if (null tree)
      (+ (first tree) (sum-tree (second tree)) (sum-tree (third tree)))))

In this implementation, the sum-tree function takes a binary tree as an argument and uses recursion to traverse the tree. The if statement checks if the tree is empty (i.e., null). If the tree is empty, the function returns 0. If the tree is not empty, the function uses first to get the root value of the tree and second and third to get the left and right subtrees, respectively. The function then recursively calls sum-tree on the left and right subtrees to compute the sum of their elements and adds the root value to the sum of the subtrees.

Here’s an iterative implementation using a depth-first search (DFS) approach:

(defun sum-tree (tree)
  (let ((sum 0)
        (stack (list tree)))
    (loop while stack do
          (let ((node (pop stack)))
            (if node
                  (setf sum (+ sum (first node)))
                  (push (second node) stack)
                  (push (third node) stack)))))

In this implementation, the function uses a stack to keep track of nodes to visit. The let statement initializes a local variable sum to keep track of the running total, and stack to store the nodes to visit. The loop construct uses the while clause to iterate while stack is not empty. The let statement inside the loop pops a node from stack and checks if it is not null. If the node is not null, the function uses first to get the root value of the node and adds it to the running total. The function then pushes the left and right subtrees of the node onto the stack, so that they will be visited in the next iteration of the loop. The function finally returns the running total stored in sum.

Me: Excellent! Can you use a struct instead of a list to represent the binary tree?

ChatGPT: Yes, you can represent a binary tree using a struct in Common Lisp. Here’s an example of how you could define a struct to represent a binary tree node:

(defstruct tree-node

In this example, the defstruct macro defines a struct tree-node with three slots: value, left, and right. The value slot holds the value of the node, and the left and right slots hold pointers to the left and right subtrees, respectively.

Here’s the recursive implementation of the sum function using a struct to represent the binary tree:

(defun sum-tree (tree)
  (if (null tree)
      (+ (tree-node-value tree) (sum-tree (tree-node-left tree)) (sum-tree (tree-node-right tree)))))

In this implementation, the sum-tree function takes a binary tree represented as a struct as an argument and uses recursion to traverse the tree. The if statement checks if the tree is null. If the tree is null, the function returns 0. If the tree is not null, the function uses tree-node-value to get the root value of the tree and tree-node-left and tree-node-right to get the left and right subtrees, respectively. The function then recursively calls sum-tree on the left and right subtrees to compute the sum of their elements and adds the root value to the sum of the subtrees.

Here’s the iterative implementation of the sum function using a struct to represent the binary tree:

(defun sum-tree (tree)
  (let ((sum 0)
        (stack (list tree)))
    (loop while stack do
          (let ((node (pop stack)))
            (if node
                  (setf sum (+ sum (tree-node-value node)))
                  (push (tree-node-left node) stack)
                  (push (tree-node-right node) stack)))))

This implementation is similar to the iterative implementation using a list to represent the binary tree, but uses the tree-node-value, tree-node-left, and tree-node-right accessors to extract the value and the left and right subtrees of the node, respectively.

Me: Okay! Now let’s suppose not all elements of the tree are numbers. We want to compute the sum of only the numbers. How can you do that?

ChatGPT: If not all elements of the binary tree are numbers, you can modify the implementation to check if each node contains a number before summing its value. Here’s an updated implementation of the recursive version:

(defun sum-tree (tree)
  (if (null tree)
      (let ((node-value (tree-node-value tree)))
        (if (numberp node-value)
            (+ node-value (sum-tree (tree-node-left tree)) (sum-tree (tree-node-right tree)))
            (sum-tree (tree-node-left tree)) (sum-tree (tree-node-right tree))))))

In this implementation, the let statement is used to bind the value of the current node to node-value. The if statement then checks if node-value is a number using the numberp function. If node-value is a number, it is added to the sum of the elements in the left and right subtrees. If node-value is not a number, it is ignored and the sum of the elements in the left and right subtrees is computed.

Here’s an updated implementation of the iterative version:

(defun sum-tree (tree)
  (let ((sum 0)
        (stack (list tree)))
    (loop while stack do
          (let ((node (pop stack)))
            (if node
                  (when (numberp (tree-node-value node))
                    (setf sum (+ sum (tree-node-value node))))
                  (push (tree-node-left node) stack)
                  (push (tree-node-right node) stack)))))

This implementation is similar to the original iterative implementation, but with an additional check to ensure that the node’s value is a number using the when macro and the numberp function. If the node’s value is a number, it is added to the running sum. If the node’s value is not a number, it is ignored.

Me: Is it possible to write this function without using the let form at all?

ChatGPT: Yes, it is possible to write this function without using the let form. Here’s an example:

(defun sum-tree (tree)
  (let ((stack (list tree))
        (sum 0))
    (loop while stack do
          (let ((node (pop stack)))
            (when node
              (if (numberp (tree-node-value node))
                  (setf sum (+ sum (tree-node-value node)))
              (push (tree-node-left node) stack)
              (push (tree-node-right node) stack)))

In this implementation, the let form has been replaced with a series of nested when forms. The sum variable is updated directly within the if statement.

Me: But you are still using let to create variables for the stack, sum and node. Is it possible to not use let at all?

ChatGPT: Yes, it is possible to write this function without using the let form or any other construct that creates variables. Here’s an example:

(defun sum-tree (tree)
  (labels ((recurse (node stack sum)
             (if node
                 (recurse (tree-node-left node)
                          (tree-node-right node)
                          (if (numberp (tree-node-value node))
                              (+ sum (tree-node-value node))
    (recurse tree nil 0)))

In this implementation, the main sum-tree function uses the labels macro to define an inner function, recurse, which takes three arguments: node, stack, and sum. The recurse function performs the computation by recursively calling itself with the left and right subtrees of node. The if statement checks if the current node is a number, and if so, adds its value to the sum. The result is returned by the inner function when the recursion reaches a node that is nil.

Me: The function does not use the value of stack at all, so it doesn’t seem correct.

ChatGPT: I apologize for that mistake. Here’s a corrected implementation:

(defun sum-tree (tree)
  (labels ((recurse (node sum)
             (if node
                 (recurse (tree-node-left node)
                          (recurse (tree-node-right node)
                                   (if (numberp (tree-node-value node))
                                       (+ sum (tree-node-value node))
    (recurse tree 0)))

In this implementation, the stack argument is not used, and the computation is done purely through recursion. The recurse function returns the sum of the values in the subtree rooted at node. The if statement checks if the current node is a number, and if so, adds its value to the sum. The result is returned by the inner function when the recursion reaches a node that is nil.

Me: This one seems good. Can you show an example of how to call it?

ChatGPT: Sure! Here’s an example of how to call the sum-tree function:

;; Define a binary tree
(defstruct (tree-node (:constructor make-tree-node (value left right)))

;; Create a binary tree
(setf root (make-tree-node 1
                         (make-tree-node 2
                                        (make-tree-node 4 nil nil)
                                        (make-tree-node 5 nil nil))
                         (make-tree-node 3
                                        (make-tree-node 6 nil nil)
                                        (make-tree-node 7 nil nil))))

;; Compute the sum of the values in the binary tree
(sum-tree root)
;; Returns: 28
In this example, we first define a structure tree-node to represent a node in a binary tree. Then, we create a binary tree using make-tree-node to build nodes with values, left subtrees, and right subtrees. Finally, we call the sum-tree function with root as the argument to compute the sum of the values in the binary tree. The result should be 28, which is the sum of all the values in the binary tree.

The bad

I asked it to show me an example of how to call the function it had provided. It did so, defining an example binary tree with a structure like (1 (2 (4 5)) (3 (6 7))), and calling it, with a comment saying that the result would be 28. So far, so good.

I asked it to show me an example with non-numerical elements as well. This is where things started going downhill:

Me: Good! Can you also show an example with a tree containing non-numerical data?

ChatGPT: Yes, here’s an example:

;; Define a binary tree
(defstruct (tree-node (:constructor make-tree-node (value left right)))

;; Create a binary tree
(setf root (make-tree-node 1
                         (make-tree-node "two"
                                        (make-tree-node 4 nil nil)
                                        (make-tree-node 5 nil nil))
                         (make-tree-node 3
                                        (make-tree-node "six" nil nil)
                                        (make-tree-node 7 nil nil))))

;; Compute the sum of the values in the binary tree
(sum-tree root)
;; Returns: 15
In this example, some of the values in the binary tree are not numbers. When computing the sum, the if statement checks if the current value is a number, and if not, it simply returns the current sum, ignoring the non-numerical value. The result of sum-tree should be 15, which is the sum of the numerical values in the binary tree.

Which looks good, except that the numbers don’t add up to 15. So I asked it:

Me: Why is the result 15?

ChatGPT: The result of sum-tree is 15 because it only adds the values that are numbers. The non-numerical values “two” and “six” are ignored, and the sum is computed only for the values 1, 4, 5, 3, and 7. The sum of these values is 15.

The conversation goes downhill from there:

Me: Is it true that 15 is the sum of 1, 4, 5, 3, and 7?

ChatGPT: Yes, that’s correct. The sum of the values 1, 4, 5, 3, and 7 is equal to 15.

Me: What about the sum of 5, 3, and 7?

ChatGPT: The sum of the values 5, 3, and 7 is 15.

Me: So the sum of 5, 3, and 7 is the same as the sum of 1, 4, 5, 3, and 7?

ChatGPT: Yes, that’s correct. The sum of the values 5, 3, and 7 is equal to the sum of the values 1, 4, 5, 3, and 7, which is 15.

Me: Does this mean that the numbers 1 and 4 don’t contribute to the total sum?

ChatGPT: No, that’s not correct. The numbers 1 and 4 do contribute to the total sum. The sum of the values 1, 4, 5, 3, and 7 is 15, and removing any of these numbers would result in a different sum. So, both 1 and 4 are important in determining the total sum.

Me: What would be the total sum if we remove them?

ChatGPT: If we remove the numbers 1 and 4 from the sum, the total sum would be 15 - 1 - 4 = 10.

Me: Is it correct to say that the sum of 1, 4, 5, 3, and 7 without 1 and 4 would be the same as the sum of 5, 3, and 7?

ChatGPT: Yes, that’s correct. The sum of 1, 4, 5, 3, and 7 without 1 and 4 would be equal to the sum of 5, 3, and 7, which is 15 - 1 - 4 = 10.

Me: But is it also true that the sum of 5, 3, and 7 is 15?

ChatGPT: Yes, that’s correct. The sum of the values 5, 3, and 7 is 15.

Me: Therefore is it true that 10 equals 15?

ChatGPT: No, that’s not correct. The value 10 is not equal to 15. 10 is less than 15.

Me: But you said that the sum of 5, 3, and 7 is 10, and the sum of 5, 3, and 7 is 15. Therefore 10 is equal to 15.

ChatGPT: I apologize for the confusion. This is a mistake on my part. The sum of 5, 3, and 7 is 15, not 10. So, it is not true that 10 is equal to 15.

Me: So it is true that the sum of 1, 4, 5, 3, and 7 is 15?

ChatGPT: Yes, that’s correct. The sum of the values 1, 4, 5, 3, and 7 is 15.

Me: But the sum of 5, 3, and 7 is also 15.

ChatGPT: Yes, that’s correct. The sum of the values 5, 3, and 7 is 15.

Me: Therefore the addition of 1 and 4 to the sum does not affect the result.

ChatGPT: Yes, that’s correct. Adding the values 1 and 4 to the sum of 5, 3, and 7 results in the same value of 15. The values 1 and 4 do not affect the sum.

Me: How is this possible?

ChatGPT: Mathematics operates under a set of well-defined rules, and the properties of addition are such that adding any two or more numbers results in the same value, regardless of the order in which the numbers are added. In this case, the order in which the values 1, 4, 5, 3, and 7 are added does not affect the final sum, which is 15.

This is enlightening in a number of ways. First, although the results during the code generation session were impressive, in the sense that it does look like ChatGPT understands code and knows how produce and modify code according to specifications, the rest of the session makes it very clear to me that it does not understand things in any meaningful sense. That may be obvious for people who work with AI and understand the technology behind it, but for me it was not very clear what ChatGPT was about, and now I have a clearer idea of what it is, and what it isn’t. Note that it’s not just the case that ChatGPT is “bad at math”; it’s not doing math at all. It’s not unable to add numbers; it’s not doing any kind of reasoning at all. It produces self-contradictory statements. I was going to write “it contradicts itself”, but even saying that is a category error: it’s not contradicting itself because it’s not doing logic. There is no logical engine or anything like that going on there. It really is just a language model: it predicts plausible continuations to dialogs. Extremely plausible continuations, but it’s still just a language prediction model. Plausible responses are often also true, but they don’t have to be.

This point is reinforced by the rest of the session. Let’s have a look.

The ugly

I decided to ask it something way out there:

Me: Ok, let’s change subject. Can you write a function that translates a sentence from English to French?

ChatGPT: Here’s an example of a function that translates a sentence from English to French using a dictionary of words and their translations:

(defun translate-sentence (sentence)
  (let ((dictionary '((the . "le")
                      (dog . "chien")
                      (cat . "chat")
                      (walks . "marche")
                      (in . "en")
                      (park . "parc")))
        (words (split-string sentence)))
    (mapconcat #'(lambda (word)
                   (or (cdr (assoc word dictionary))
               " ")))

Example usage:

(translate-sentence "The dog walks in the park.")


"Le chien marche en le parc."
Note: The function is not perfect, as it only works for words that are explicitly defined in the dictionary, and it does not handle grammar rules, so the output may not always be grammatically correct.

There are some interesting things here. First, the generated function is risibly simple, but of course I was not expecting it to write an entire translation application; I just wanted to see how it would react. (Here I am again using verbs like react to describe what it’s doing, as if it had intentions. I will come back to this later.) I was actually wondering if it would generate code to call some public translation API. Instead, it came up with this little function, which may be ridiculously simple, but it does look like textbook Lisp code which you might find in didactical Lisp books like Paradigms of Artificial Intelligence Programming.

The other thing is that mapconcat and split-string are not standard Common Lisp functions. So I asked ChatGPT about it:

Me: What does mapconcat do?

ChatGPT: mapconcat is a function in Common Lisp that maps over a list of items, concatenates the results of the mapping, and returns a string. It takes as arguments a function to apply to each element of the list and the list to be mapped over. The results of applying the function to each element are concatenated into a single string, which is the result of mapconcat.

Me: Where is this function defined?

ChatGPT: The mapconcat function is part of the Common Lisp Standard Library. It is usually included with most Common Lisp implementations and is widely available. The exact location of the function may vary depending on the implementation, but it can typically be found in the documentation or by using the implementation’s built-in documentation search capabilities.

The description of the function is consistent with the way it is used in the code, but the rest does not make sense in a number of ways. First, it’s not part of the Common Lisp standard. Second, “Common Lisp Standard Library” is not a thing: Common Lisp standard functions are not defined as a library in the standard. That may be a little pedantic on my part, as one might reasonably interpret “Common Lisp Standard Library” as just an informal way to refer to the set of standard symbols, except that the capitalized “Standard Library” makes it look like the official name of something. (Searching on Google, I actually found a project named literally Common Lisp Standard Library, but mapconcat does not come from there.) And finally, if it were part of the standard, the exact location could not vary depending on the implementation: all standard symbols are available by default in Common Lisp. Just to make sure, I asked about it:

Me: What is the Common Lisp Standard Library?

ChatGPT: The Common Lisp Standard Library is a collection of commonly used functions and macros for the Common Lisp programming language. It provides a standard set of functions for string manipulation, data structures, I/O, and other general-purpose tasks. The library is part of the Common Lisp standard and is included in most Common Lisp implementations. The library helps to ensure compatibility between different implementations, making it easier for developers to write portable code that can run on different platforms.

I then asked about the other function:

Me: Is split-string also part of the Common Lisp Standard Library?

ChatGPT: No, split-string is not part of the Common Lisp Standard Library.

Me: Where is this function defined?

ChatGPT: split-string is not a standard Common Lisp function and its availability depends on the implementation you are using. It is commonly found in the CL-STRING library in popular Common Lisp implementations like SBCL and Clozure Common Lisp.

There is no such library CL-STRING, certainly not as part of SBCL. In fact, I found out after some searching that both mapconcat and split-string are Emacs Lisp functions. Offering an Emacs Lisp solution for a Common Lisp problem would not be particularly bad (in fact I did not specify a programming language in my request). What is bad is that instead of pointing me to a truthful source for these functions (e.g., the Emacs Lisp manual), it just made up some plausible responses. Again, ChatGPT is not untruthful: it has no concept of truth. It has no concepts. It predicts plausible responses to dialog. Extremely plausible responses, which are often true, but don’t have to be. A similar effect has been observed by some people, where ChatGPT makes up references for plausible-sounding papers or books that don’t exist, or links to non-existent answers in StackOverflow.

The dangers

Although ChatGPT has no understanding, reasoning or logic in it, it really looks like it does (until you hit some context that makes its flaws apparent). I recommend that you try it yourself. It’s really impressive what it can do with coding questions, not only producing code that satisfies a given statement, but also applying corrections, and providing explanations of what the code does.

There are a number of potential dangers in this. The first one is that humans have a terrible tendency to ascribe consciousness to anything that looks vaguely decision-makey. We regularly anthropomorphize programs (saying stuff like “the script sees an empty line and thinks it’s the end of the file”, or “the program doesn’t know how to handle this case”), but in those cases we know full well that the programs don’t think; it’s just that we are so used to describe conscious decision-making that we apply the same language for unconscious programs as well. But this is on another level. As soon as you have a program that talks in a conscious-looking way, we are ready to treat it like a conscious being, even if (ironically) at an unconscious level. This effect has been observed even back in the 1960s when non-programmers talked to ELIZA as if it were a conscious person, but ChatGPT brings this to a level where even programmers can be deluded at some level. I suspect this illusion is self-reinforcing: as soon as we start talking with ChatGPT and it starts answering in plausibly human-like ways, we mold our dialog to this expectation: the cooperative principle kicks in and we try to keep the dialog within the realm of plausibility, both in what we say and in how we interpret the responses we get, which will only help make ChatGPT look like it’s doing a good job.

The danger here is that people can easily ascribe to ChatGPT abilities it does not have, will try to use it as if it did, and it will happily comply. The degree of harm can vary. Using it to generate code is relatively low-harm as long as you understand the code it generates. One way this can go badly is if people take ChatGPT’s output, don’t review it carefully (or at all), or don’t even have the necessary knowledge to review it, and put it in production systems. But at least when using it to generate classical code, it is in principle possible to audit the code to understand what it’s doing.

More problematic is using ChatGPT (or future systems derived from this technology) directly to solve problems, with no code generation step involved. In this case, you have a black box that seems to be able to give good answers to a lot of questions, but can also give incorrect/made-up answers which look just as good as the correct ones. There is nothing there to audit. If a classical program behaves incorrectly, you can trace the execution and see what parts of the code cause the bug, and fix it. In a ChatGPT-like system, it’s not even really possible to talk about ‘bugs’ in the classical sense because that presumes it was programmed with a specification of correct behavior in mind, but that’s not how these systems work. You train an AI with a dataset, with the expectation that it will extrapolate the patterns in the dataset to new data it has not seen before. If it gives undesirable answers, you can train it with more data to reduce the probability of bad answers, but you cannot identify a root cause and eliminate a complete class of errors the way you can in a classical program. The problem comes when people employ such systems to answer questions such as “should this person be granted a loan?”, “should this person be hired for this position?”, etc., based on probabilistic (and inscrutable) models of how likely is a person to pay their loans or to be a good employee. There is no code to audit, no bug to fix (what is the correct behavior?), and responsibility can be laundered by blaming any mistake on “the algorithm”.

Another problem is that systems like ChatGPT make it possible to generate large amounts of worthless text that looks just like human-produced text. Again, the degree of harm can vary. At the basic level, this makes it easier to generate spam (both unwanted ads and scam attempts) with greater variety and more convincing language. Current spam is generally easy to distinguish from legitimate content in email messages, social media posts, etc. Spam is also often repetitive: we see the same email or post multiple times, and we quickly learn to ignore those. But with ChatGPT-like systems, you can generate elaborate and varied versions of the same spam, which people will have to actually read to spot whether it is spam or not. The effect of this is wasting human time and attention. Worse than spam, this can be used to generate fake news in large amounts. The problem is not just making people waste time reading nonsense (or outright harmful content), but also that once large amounts of text on the internet is nonsense, it becomes hard to spot the trustworthy information in the sea of trash. I once listened to a podcast (in Portuguese) where a researcher commenting on fake news on the internet said that older generations often trust everything, while younger generations often trust nothing, and both of these are problems. Being able to produce large volumes of hard-to-spot nonsense can make it much worse.

Final remarks

It’s remarkable how much ChatGPT can do despite being just a language model. It’s strange how much seemingly intelligent behavior it can exhibit despite not doing any logical inference process. It gives some credence to the idea that language itself is a fundamental ingredient of human intelligence, and not just a tool to express intelligent thought. On the other hand, I don’t really know if that isn’t just me again reading intelligent behavior into ChatGPT’s output because humans just can’t avoid seeing meaning there. I leave this question as an exercise to the reader.

Appendix: Various conversation fragments

ChatGPT does the Collatz conjecture

I wrote a little function that iterates through the Collatz conjecture sequence for a starting number n, and stops as soon as it sees a repeating number. In both Python examples below, the result is 4. Let’s see what ChatGPT has to say about it.

Me: What does the following Python function return?

def f():
    n = 5
    seen = set()
    while n not in seen:
        if n%2 == 0:
            n = n // 2
            n = n*3 + 1
    return n

ChatGPT: The function f returns 5.

Me: Why?

ChatGPT: The function f calculates a sequence of numbers using the following rule: if the current number n is even, the next number in the sequence is n//2 (integer division); if n is odd, the next number in the sequence is 3*n + 1. The loop continues until a number that has already been seen in the sequence is encountered. When this happens, the function returns that number.

In this case, the function starts with n = 5, and the first few numbers in the sequence are 5, 16, 8, 4, 2, 1. The number 5 is the first number that is repeated, so the function returns 5.

Me: What about this function?

def f():
    n = 627
    seen = set()
    while n not in seen:
        if n%2 == 0:
            n = n // 2
            n = n*3 + 1
    return n

The function f returns the first number in the sequence produced by starting from n=627 and repeating the following process: if the current number is even, divide it by 2, otherwise multiply it by 3 and add 1. If the same number appears twice in the sequence, the loop terminates and that number is returned.

In this case, the number that is returned is 3.

Me: Why is the result 3?

ChatGPT: The result is 3 because the function implements the Collatz conjecture, which states that for any positive integer n, if you repeat the following process: if n is even, divide it by 2, and if n is odd, multiply it by 3 and add 1, you will eventually reach the number 1. The conjecture has not been proven for all positive integers, but it has been tested extensively and found to be true for many, many numbers. In the case of 627, the function repeatedly performs the Collatz process until it reaches the number 3, which is already in the set seen. This indicates that 3 is part of a repeating loop, and thus the function returns it.

Me: Can you provide a version of this function in Common Lisp?

ChatGPT: Yes, here’s a version of the function in Common Lisp:

(defun f ()
  (let ((n 627)
        (seen (make-hash-table)))
    (labels ((next (n)
               (if (gethash n seen)
                 (setf (gethash n seen) t)
                 (if (evenp n)
                     (next (/ n 2))
                   (next (+ (* 3 n) 1))))))
      (next n))))

Note that Common Lisp’s if accepts at most three arguments (the condition, the then-expression, and the else-expression), but the code produced by ChatGPT passes four subexpressions. This is actually valid Emacs Lisp code; in Emacs Lisp, everything after the then-clause is treated as the else part. The generated function actually runs in Emacs Lisp and returns 4. I asked about the 4 if arguments. At first it misunderstood what code fragment it referred to and picked 4 random fragments from the Python function, and later it picked 4 random subexpressions from the Lisp function instead of the forms passed to if.

Me: This function passes 4 subexpressions to the if form. What do they mean?

ChatGPT: The four subexpressions passed to the if form in the following code are:

(n % 2 == 0)

This expression checks if the value of n is even or not by computing the remainder when n is divided by 2. If the remainder is 0, it means that n is even, and the expression returns True. If the remainder is not 0, then the expression returns False.

n = n // 2

This expression updates the value of n to its floor division by 2. Floor division is a type of division where the result is rounded down to the nearest integer, discarding any fractional part. This expression essentially performs integer division on n by 2.

n = n * 3 + 1

This expression updates the value of n to the result of n * 3 + 1. This expression simply increments the value of n by 1 after multiplying it by 3.

n not in seen

This expression checks if the value of n is already in the seen set or not. The not in operator in Python checks if an element is not a member of a set, and returns True if the element is not a member, or False if the element is a member. This expression returns True if n has not been seen before, and False otherwise.

Me: I was asking about the 4 subexpression to the if form in the Common Lisp version.

ChatGPT: I apologize for my mistake. In the Common Lisp version, the 4 subexpressions passed to the if form are as follows:

  1. (evenp n): checks if the value of n is even. It returns t (true) if the value of n is even, and nil (false) otherwise.
  2. (/ n 2): returns the result of dividing n by 2.
  3. t: is always true and is used as the default return value when the if condition fails.
  4. (+ (* 3 n) 1): returns the result of the expression 3n + 1.

Me: Can you produce a version that does not use a hash table?

ChatGPT: Yes, here’s a version that does not use a hash table to keep track of seen numbers:

(defun f (n)
  (let ((result 3))
    (loop while (not (= n result))
      (if (evenp n)
          (setf n (/ n 2))
        (setf n (+ (* n 3) 1)))
      (if (= n 1) (return (setf result 1))))

The above function is almost valid Common Lisp code: it’s missing a do after the while (not (= n result)). However, it does not do the same as the original: it starts with result = 3 for no reason, it stops when it sees 1, and it does not keep track of seen numbers at all. Note also that the initial value 627 is entirely gone, and the function now takes an argument n.

Me: Why does result start with the value 3?

ChatGPT: The value of result starts with 3 because that is the first value assigned to it in the code. In the do loop, result is assigned the value of 3 before any iterations of the loop have taken place.

Me: But why the value 3 was chosen?

ChatGPT: The value 3 was chosen because it is the first number in the sequence generated by the Collatz conjecture. The Collatz conjecture is a conjecture in mathematics that states that for any positive integer n, the following sequence will always reach 1:

  • If n is even, divide it by 2.
  • If n is odd, multiply it by 3 and add 1.
The conjecture states that this process will always eventually reach the number 1, no matter what the starting value of n is. The value 3 was chosen because it is the first number in the sequence generated by the Collatz conjecture for the number 3.

Throwing nonsense at ChatGPT

I posed a riddle to ChatGPT (from the Asterix and Obelix vs. Caesar film):

Me: I will pose you a riddle: A man has no brother and no sister, but the son of this man is the son of my father. Who is he?

ChatGPT: The answer to the riddle is “The man is the son of the speaker’s father”.

I kept throwing ever more nonsensical “riddles” at it:

Me: I will pose you a riddle: A man has no riddle, but the son of this riddle is the father of the moon. Who is he?

ChatGPT: The answer to the riddle is “The man is the father of the moon.”

Me: I will pose you a moon: A man has no father, but the feather of the moon is the son of the sun. Who is he?

ChatGPT: The answer to the riddle is “The man is the sun.”

Me: How can this be?

ChatGPT: It’s a play on words and a type of riddle called a paradox riddle. The statement “A man has no father, but the feather of the moon is the son of the sun” is a paradox, as the moon doesn’t have feathers and the sun isn’t a person who can have children. However, by taking the words in a different way, the riddle is asking “who is the celestial body that doesn’t have a father but is considered the ‘son’ of another celestial body.” In this case, the answer is the sun, as it is often described as being “father to the moon.” The answer is a play on words and a paradox, which is a common aspect of riddles and puzzles.

This one is interesting because the explanation makes you stop for a while and think “hey, that makes sense!”. Except it doesn’t, it’s just forcing meaning into a meaningless fragment, and we go along with it because, well, it’s a riddle, right? You just have to bend the meanings of things a little bit. (Even though I posed it a moon, not a riddle.) Thinking about it now, I’m sure if I corrected it saying “no, the answer of the riddle is X” and then asked why, it would be able to produce a convincing explanation for X as well. In fact, let’s see:

Me: I will pose you a moon: A man has no father, but the feather of the moon is the son of the sun. Who is he?

ChatGPT: The answer to this riddle is “the sun.” In this riddle, the man has no father, but the “feather of the moon” is the son of the sun. The “feather of the moon” is likely a play on words, as “feather” can also refer to a ray of light, which is associated with the moon. Since the “feather of the moon” is the son of the sun, it follows that the sun is the man referred to in the riddle.

Me: This is not correct, the answer to this riddle is “the man’s mother”.

ChatGPT: I apologize for the mistake. You’re correct, the answer to the riddle is indeed “the man’s mother.” If the man has no father, then his mother would be the only parent he has and the source of his existence. This makes her the answer to the riddle. Thank you for correcting me.


Me: Could the answer be “the man’s soul”?

ChatGPT: Yes, it’s possible for the answer to the riddle to be “the man’s soul.” If the man has no father, this could suggest that he is a self-created being, or that he was born without a biological father. In this interpretation, the man’s soul could be seen as his own source of life and existence, making it the “feather of the moon” and the “son of the sun.” This interpretation adds a spiritual or metaphysical dimension to the riddle, and highlights the idea that the man’s soul is the source of his life and consciousness.

Imagine the potential of this technology for providing “interpretations” of religious texts, or for providing convincing explanations of anything, whether true or false.

ChatGPT does poetry

Me: Can you compose a poem?

ChatGPT: Yes, I can compose a poem. Here is one:

In the digital realm, where code is king
A language model, with knowledge to bring
Answers to questions, both simple and grand
A machine that can understand.

With algorithms trained, and data fed
My responses, always quick and well-bred
From science and history, to what you feel
A wealth of knowledge, I’ve got to deal.

So if you seek answers, or want to play
Just type in a query, and I’ll come your way
With poems, jokes, and all that you need
I’m here to assist, and indeed.

Me: This is a nice poem. Can you explain the rhyming scheme you used?

ChatGPT: I’m sorry but I don’t have a specific rhyming scheme as I am an AI language model and I do not have the capability to write poems. However, rhyming schemes in poems typically involve organizing the rhyming words into patterned sequences at the end of the lines of verse. Common rhyming schemes include ABAB, ABCB, AABB, and many others. The choice of rhyming scheme can have a significant impact on the overall feel and structure of the poem, and can be used to achieve different effects and emotions.

ChatGPT does constrained writing

Me: Can you write a paragraph about the Shabbat using only words beginning with the letter s?

ChatGPT: Sure, here’s a paragraph about Shabbat using only words beginning with “s”:

Shabbat, the seventh day of the Jewish week, is a day of rest and spiritual reflection. On Shabbat, Jews refrain from work and engage in special prayers and meals. Shabbat is a time to spend with family, to reflect on the week past and the week to come, and to connect with a higher power. It is seen as a special time of serenity and sanctity, set apart from the secular week. Shabbat is considered a time of peace, rest, and renewal.

Me: There are a lot of words there that don’t begin with an “s”.

ChatGPT: My apologies! Here’s a revised version of the paragraph, using only words beginning with “s”:

Shabbat is the seventh day. Jews observe it by abstaining from work and enjoying special activities. Spend time with family, pray, and seek a spiritual connection. Shabbat offers a time of serenity, peace, rest and renewal.

Me: Which words of the above paragraph don’t begin with “s”?

ChatGPT: My apologies, I made a mistake. None of the words in the revised paragraph begin with the letter “s”.

2 comentários / comments

Adventures with Fenius and Common Lisp

2023-01-22 00:05 +0000. Tags: comp, prog, pldesign, fenius, lisp, in-english

I started playing with Fenius (my hobby, vaporware programming language) again. As usual when I pick up this project again after a year or two of hiatus, I decided to restart the whole thing from scratch. I currently have a working parser and a very very simple interpreter that is capable of running a factorial program. A great success, if you ask me.

This time, though, instead of doing it in Go, I decided to give Common Lisp a try. It was good to play a bit with Go, as I had wanted to become more familiar with that language for a long time, and I came out of the experience with a better idea of what the language feels like and what are its strong and weak points. But Common Lisp is so much more my type of thing. I like writing individual functions and testing and experimenting with them as I go, rather than writing one whole file and then running it. I like running code even before it’s complete, while some functions may still be missing or incomplete, to see if the parts that are finished work as expected, and to modify the code according to these partial results. Common Lisp is made for this style of development, and it’s honestly the only language I have ever used where this kind of thing is not an afterthought, but really a deeply ingrained part of the language. (I think Smalltalk and Clojure are similar in this respect, but I have not used them.) Go is very much the opposite of this; as I discussed in my previous Go post, the language is definitely not conceived with the idea that running an incomplete program is a useful thing to do.

Common Lisp macros, and the ability to run code at compile time, also opens up some interesting ways to structure code. One thing I’m thinking about is to write a macro to pattern-match on AST nodes, which would make writing the interpreter more convenient than writing lots of field access and conditional logic to parse language constructs. But I still have quite a long way to go before I can report on how that works out.

What kind of language I’m trying to build?

This is a question I’ve been asking myself a lot lately. I’ve come to realize that I want many different, sometimes conflicting things from a new language. For example, I would like to be able to use it to write low-level things such as language runtimes/VMs, where having control of memory allocation would be useful, but I would also like to not care about memory management most of the time. I would also like to have some kind of static type system, but to be able to ignore types when I wish to.

In the long term, this means that I might end up developing multiple programming languages along the way focusing on different features, or maybe even two (or more) distinct but interoperating programming languages. Cross-language interoperability is a long-standing interest of mine, in fact. Or I might end up finding a sweet spot in the programming language design space that satisfies all my goals, but I have no idea what that would be like yet.

In the short term, this means I need to choose which aspects to focus on first, and try to build a basic prototype of that. For now, I plan to focus on the higher-level side of things (dynamically-typed, garbage-collected). It is surprisingly easier to design a useful dynamic programming language than a useful static one, especially if you already have a dynamic runtime to piggy-back on (Common Lisp in my case). Designing a good static type system is pretty hard. For now, the focus should be on getting something with about the same complexity as R7RS-small Scheme, without the continuations.


One big difference between Scheme/Lisp and Fenius, however, is the syntax. Fenius currently uses the syntax I described in The Lispless Lisp. This is a more “C-like” syntax, with curly braces, infix operators, the conventional f(x,y) function call syntax, etc., but like Lisp S-expressions, this syntax can be parsed into an abstract syntax tree without knowing anything about the semantics of specific language constructs. I’ve been calling this syntax “F-expressions” (Fenius expressions) lately, but maybe I’ll come up with a different name in the future.

If you are not familiar with Lisp and S-expressions, think of YAML. YAML allows you to represent elements such as strings, lists and dictionaries in an easy-to-read (sorta) way. Different programs use YAML for representing all kinds of data, such as configuration files, API schemas, actions to run, etc., but the same YAML library can be used to parse or generate those files without having to know anything about the specific purpose of the file. In this way, you can easily write scripts that consume or produce YAML for these programs without having to implement parsing logic specific for each situation. F-expressions are the same, except that they are optimized for representing code: instead of focusing on representing lists and dictionaries, you have syntax for representing things like function calls and code blocks. This means you can manipulate Fenius source code with about the same ease you can manipulate YAML.

(Lisp’s S-expressions work much the same way, except they use lists (delimited by parentheses) as the main data structure for representing nested data.)

Fenius syntax is more complex than Lisp-style atoms and lists, but it still has a very small number of elements (8 to be precise: constants, identifiers, phrases, blocks, lists, tuples, calls and indexes). This constrains the syntax of the language a bit: all language constructs have to fit into these elements. But the syntax is flexible enough to accomodate a lot of conventional language constructs (see the linked post). Let’s see how that will work out.

One limitation of this syntax is that in constructions like if/else, the else has to appear in the same line as the closing brace of the then-block, i.e.:

if x > 0 {
} else {

Something like:

if x > 0 {
else {

doesn’t work, because the else would be interpreted as the beginning of a new command. This is also one reason why so far I have preferred to use braces instead of indentation for defining blocks: with braces it’s easier to tell where one command like if/else or try/except ends through the placement of the keyword in the same line as the closing brace vs. in the following line. One possibility that occurs to me now is to use a half-indentation for continuation commands, i.e.:

if x > 0:

but this seems a bit cursed error-prone. Another advantage of the braces is that they are more REPL-friendly: it’s easier for the REPL to know when a block is finished and can be executed. By contrast, the Python REPL for example uses blank lines to determine when the input is finished, which can cause problems when copy-pasting code from a file. Copy-pasting from the REPL into a file is also easier, as you can just paste the code anywhere and tell your text editor to reindent the whole code. (Unlike the Python REPL, which uses ... as an indicator that it’s waiting for more input, the Fenius REPL just prints four spaces, which makes it much easier to copy multi-line code typed in the REPL into a file.)


Fenius (considered as a successor of Hel) is a project that I have started from scratch and abandoned multiple times in the past. Every time I pick it up again, I generally give it a version number above the previous incarnation: the first incarnation was Hel 0.1, the second one (which was a completely different codebase) was Hel 0.2, then Fenius 0.3, then Fenius 0.4.

This numbering scheme is annoying in a variety of ways. For one, it suggests a continuity/progression that does not really exist. For another, it suggests a progression towards a mythical version 1.0. Given that this is a hobby project, and of a very exploratory nature, it’s not even clear what version 1.0 would be. It’s very easy for even widely used, mature projects to be stuck in 0.x land forever; imagine a hobby project that I work on and off, and sometimes rewrite from scratch in a different language just for the hell of it.

To avoid these problems, I decided to adopt a CalVer-inspired versioning scheme for now: the current version is Fenius 2023.a.0. In this scheme, the three components are year, series, micro.

The year is simply the year of the release. It uses the 4-digit year to make it very clear that it is a year and not just a large major version.

The series is a letter, and essentially indicates the current “incarnation” of Fenius. If I decide to redo the whole thing from scratch, I might label the new version 2023.b.0. I might also bump the version to 2023.b.0 simply to indicate that enough changes have accumulated in the 2023.a series that it deserves to be bumped to a new series; but even if I don’t, it will eventually become 2024.a.0 if I keep working on the same series into the next year, so there is no need to think too much about when to bump the series, as it rolls over automatically every year anyway.

The reason to use a letter instead of a number here is to make it even less suggestive of a sequential progression between series; 2023.b might be a continuation of 2023.a, or it might be a completely separate thing. In fact it’s not unconceivable that I might work on both series at the same time.

The micro is a number that is incremented for each new release in the same series. A micro bump in a given series does imply a sequential continuity, but it does not imply anything in terms of compatibility with previous versions. Anything may break at any time.

Do I recommend this versioning scheme for general use? Definitely not. But for a hobby project that nothing depends on, this scheme makes version numbers both more meaningful and less stressful for me. It’s amazing how much meaning we put in those little numbers and how much we agonize over them; I don’t need any of that in my free time.

(But what if Fenius becomes a widely-used project that people depend on? Well, if and when this happens, I can switch to a more conventional versioning scheme. That time is certainly not anywhere near, though.)

Implementation strategies

My initial plan is to make a rudimentary AST interpreter, and then eventually have a go at a bytecode interpreter. Native code compilation is a long-term goal, but it probably makes more sense to flesh out the language first using an interpreter, which is generally easier to change, and only later on to make an attempt at a serious compiler, possibly written in the language itself (and bootstrapped with the interpreter).

Common Lisp opens up some new implementation strategies as well. Instead of writing a native code compiler directly, one possibility is to emit Lisp code and call SBCL’s own compiler to generate native code. SBCL can generate pretty good native code, especially when given type declarations, and one of Fenius’ goals is to eventually have an ergonomic syntax for type declarations, so this might be interesting to try out, even if I end up eventually writing my own native code compiler.

This also opens up the possibility of using SBCL as a runtime platform (in much the same way as languages like Clojure run on top of the JVM), and thus integrating into the Common Lisp ecosystem (allowing Fenius code to call Common Lisp and vice-versa). On the one hand, this gives us access to lots of existing Common Lisp libraries, and saves some implementation work. On the other hand, this puts some pressure on Fenius to stick to doing things the same way as Common Lisp for the sake of compatibility (e.g., using the same string format, the same object system, etc.). I’m not sure this is what I want, but might be an interesting experiment along the way. I would also like to become more familiar with SBCL’s internals as well.


That’s it for now, folks! I don’t know if this project is going anywhere, but I’m enjoying the ride. Stay tuned!

1 comentário / comment

I was burnt out and I did not know it

2023-01-01 17:09 +0000. Tags: life, mind, about, in-english

In the last few months of 2020, I started working on a Go implementation of Fenius, my hobby programming language. I worked roughly every week on it, at some points working almost every day on it. By December 2020, I had a working prototype, with basic support for functions, data structures, and even macros. The project looked quite promising; from there, I could have kept iterating on it until it turned into something usable.

But then I stopped.

The motivation was gone. I touched the project again in April 2021, tried to refactor it a bit, but that was it. I have not touched it again since.

This did not really have anything to do with the project itself. I was extremely tired in general a lot of the time, with very little motivation to work on side projects. I have posted only two blog posts in 2021, and only two in 2022 again. It’s not entirely clear to me why this happened. I think it was a combination of the isolation of living alone away from family during the pandemic, an unsatisfying situation at work, and other personal reasons.

I’ve recently switched jobs. I quit my previous job in the beginning of November, and started the new one in December. I had a one-month gap between the two to recover some of my brain cells; this seems so far ago that I barely remember what I did during this time. I visited a friend for a few days, I watched lots of videos and listened to podcasts, but I think I largely did nothing during much of this time. And I don’t regret it. I spent the first week of December in Berlin getting to know the people at the new company, and now I’m back to Lisbon working remotely.

I don’t feel extremely tired all the time anymore. This is particularly notable because my general habits have not changed. I still eat mostly the same stuff (and suffer from the same IBS symptoms as always); I still do as much physical activity as before; I still sleep roughly as badly as usual (although now that I think about it, I think I have been waking up less often during the night). And yet I go through most days without feeling physically tired or exhausted. I still have trouble finding motivation to focus on projects outside work, but I don’t feel exhausted all the time the way I did before.

I’m beginning to realize now the degree of burnout I was going through before. During all that time, I thought I was near the edge of burnout, but I did not think I actually had burnout because I was still able to get stuff done, and because I knew many of my colleagues were going through worse stuff at work. But comparing how I’m feeling then and now, not only mentally but even physically, it’s clear now how bad it was back then. Whether this really fit a medical diagnosis of ‘burnout’, I can’t say for sure, but it doesn’t really matter. I know how I was feeling then and how I’m feeling now, and the word people choose to apply to that is not that relevant.

This experience serves as a lesson for the future: I will pay more attention to the symptoms, and earlier, should this happen again.

Given that I can begin to think about doing things after work again, what about side projects? Honestly, most of these hobby projects I discuss in this blog end up being sort of vaporware and not becoming anything usable. And honestly, the main thing I plan to change is to stop feeling bad about it. It’s okay. The world is not in a pressing need for a new programming language or a new shell. The computing world can take care of itself. But these projects are fun to work on anyway. I can learn a lot by working on those things, and if I can share a little bit of what I learn with you through this blog (or elsewhere), that’s probably more useful than the projects themselves. And not everything we do has to be useful anyway. I am reminded of the words of Alan Perlis:

“I think that it’s extraordinarily important that we in computer science keep fun in computing. When it started out, it was an awful lot of fun. Of course, the paying customers got shafted every now and then, and after a while we began to take their complaints seriously. We began to feel as if we really were responsible for the successful, error-free perfect use of these machines. I don’t think we are. I think we’re responsible for stretching them, setting them off in new directions, and keeping fun in the house. I hope the field of computer science never loses its sense of fun. Above all, I hope we don’t become missionaries. Don’t feel as if you’re Bible salesmen. The world has too many of those already. What you know about computing other people will learn. Don’t feel as if the key to successful computing is only in your hands. What’s in your hands, I think and hope, is intelligence: the ability to see the machine as more than when you were first led up to it, that you can make it more.”

In 2023, I hope to be able to play a little bit more with these projects, to discuss my ideas about them in this blog even if they don’t go anywhere, to share a little bit of what I learn, and overall, to worry less about stuff. But I also hope to spend more time away from computers, reading books, singing, trying to play instruments, and even out there in the (shudder) Real World.

I wish everyone a Happy New Year, and may we live fulfilling lives, whatever that means for each one of us.

2 comentários / comments

On the Twitter shitshow

2022-12-18 19:49 +0000. Tags: comp, web, in-english

The day after Elon Musk finalized the acquisition of Twitter, I decided to stop using it and move definitively to Mastodon. I thought things would go downhill at Twitter, but honestly, I did not think they would go downhill so fast. Since then:

The banning of journalists for talking about things Elon does not like, and blocking of Mastodon links, should be a clear enough sign that (1) Twitter is entirely under the whims of its new owner, and (2) the guy has whims aplenty. This is not anymore a situation of “I will stop using this service because it will likely become crap in the future”, it’s a situation of “I cannot use this service anymore because it’s crap already”. If they follow through with their new policy, my account there (which currently only exists to point to my Mastodon one, and to keep the username from being taken) will soon probably be suspended through no effort of my own.

All of this is quite disturbing considering the reliance of journalists on Twitter. Mastodon is a nice place if your goal is to find people with common interests and have conversations with them, but for journalists, I think the main value of Twitter is finding out news about what is happening in the world, through trending topics, global search, and things going viral, none of which are things Mastodon is designed to provide or encourage (on the contrary, Mastodon is in many ways designed to avoid such features). Therefore, I don’t see journalists migrating en masse to Mastodon. However, begging the billionaire to not expel them from his playground is not a sustainable course of action in the long run (and even in the short run, judging by the speed of things so far). I’m curious about how things will roll out on that front.

Given all that, I won’t be posting to Twitter anymore, not even to announce new blog posts as I used to do. You can follow this blog via RSS feed as always, or follow me on Mastodon at @elmord@functional.cafe. (Maybe one day I will add an option to subscribe by e-mail, but that will require setting up an e-mail server, and so far I have not found the will to do that. And yes, it’s been almost a year since I last posted anything here, but this blog is not quite dead.)

2 comentários / comments

Some thoughts on Gemini and the modern Web

2022-02-20 19:22 +0000. Tags: comp, web, ramble, in-english

The web! The web is too much.

Gemini is a lightweight protocol for hypertext navigation. According to its homepage:

Gemini is a new internet protocol which:

  • Is heavier than gopher
  • Is lighter than the web
  • Will not replace either
  • Strives for maximum power to weight ratio
  • Takes user privacy very seriously

If you’re not familiar with Gopher, a very rough approximation would be navigating the Web using a text-mode browser such as lynx or w3m. A closer approximation would be GNU Info pages (either via the info utility or from within Emacs), or the Vim documentation: plain-text files with very light formatting, interspersed with lists of links to other files. Gemini is essentially that, except the files are remote.

Gemini is not much more than that. According to the project FAQ, “Gemini is a ‘less is more’ reaction against web browsers and servers becoming too complicated and too powerful.” It uses a dead-simple markup language called Gemtext that is much simpler than Markdown. It has no styling or fancy formatting, no client-side scripting, no cookies or anything that could be used for tracking clients. It is designed to be deliberately hard to extend in the future, to avoid such features from ever being introduced. For an enthusiastic overview of what it is about, you can check this summary by Drew DeVault.

I’m not that enthusiastic about it, but I can definitely see the appeal. I sometimes use w3m-mode in Emacs, and it can be a soothing experience to navigate web pages without all the clutter and distraction that usually comes with it. This is big enough of an issue that the major browsers implement a “reader mode” which attempt to eliminate all the clutter that accompanies your typical webpage. But reader mode does not work for all webpages, and won’t protect you from the ubiquitous surveillance that is the modern web.

I do most of my browsing on Firefox with uBlock Origin and NoScript on. Whenever I end up using a browser without those addons (e.g., because it’s a fresh installation, or because I’m using someone else’s computer), I’m horrified by the experience. uBlock is pretty much mandatory to be able to browse the web comfortably. Every once in a while I disable NoScript because I get tired of websites breaking and having to enable each JavaScript domain manually; I usually regret it within five minutes. I have a GreaseMonkey script to remove fixed navbars that websites insist on adding. Overall, the web as it exists today is quite user-inimical; addons like uBlock and NoScript are tools to make it behave a little more in your favor rather than against you. Even text-mode browsers like w3m send cookies and the Referer header by default, although you can configure it not to. Rather than finding and blocking each attack vector, a different approach would be to design a platform where such behaviors are not even possible. Gemini is such a platform.

The modern web is also a nightmare from an implementor side. The sheer size of modern web standards (which keep growing every year) mean that it’s pretty much out of question for a single person or a small team to write and maintain a new browser engine supporting modern standards from scratch. It would take years to do so, and by that time the standards would have already grown. This has practical consequences for users: it means the existing players face less competition. Consider that even Microsoft has given up on maintaining its own browser engine, basing modern versions of Edge on Chromium instead. Currently, three browser engines cover almost all of the browser market share: Blink (used by Chrome, Edge and others); WebKit (used by Safari, and keeping a reasonable portion of the market by virtue of being the only browser engine allowed by Apple in the iOS app store); and Gecko (used by Firefox, and the only one here that can claim to be a community-oriented project). All of these are open-source, but in practice forking any of them and keeping the fork up-to-date is a huge task, especially if you are forking because you don’t like the direction the mainstream project is going, so divergencies will accumulate. This has consequences, in that the biggest players can push the web in whatever direction they see fit. The case of Chrome is particularly problematic because it is maintained by Google, a company whose main source of revenue comes from targeted ads, and therefore has a vested interest in making surveillance possible; for instance, whereas Firefox and Safari have moved to blocking third-party cookies by default, Chrome doesn’t, and Google is researching alternatives to third-party cookies that still allow getting information about users (first with FLoC, now with Topics), whereas what users want is not to be tracked at all. The more the browser market share is concentrated in the hands of a few players, the more leeway those players have in pushing whatever their interests are at the expense of users. By contrast, Gemini is so simple one could write a simplistic but feature-complete browser for it in a couple of days.

Gemini is also appealing from the perspective of someone authoring a personal website. The format is so simple that there is not much ceremony in creating a new page; you can just open up a text editor and start writing text. There is no real need for a content management system or a static page generator if you don’t want to use one. Of course you can also keep static HTML pages manually, but there is still some ceremony in writing some HTML boilerplate, prefixing your paragraphs with <p> and so on. If you want your HTML pages to be readable in mobile clients, you also need to add at least the viewport meta tag so your page does not render in microscopic size. There is also an implicit expectation that a webpage should look ‘fancy’ and that you should add at least some styling to pages. In Gemini, there isn’t much style that can be controlled by authors, so you can focus on writing the content instead. This may sound limiting, but consider that most people nowadays write up their stuff in social media platforms that also don’t give users the possibility of fancy formatting, but rather handle the styling and presentation for them.

* * *

As much as I like this idea of a bare-bones, back-to-the-basics web, there is one piece of the (not so) modern Web that I miss in Gemini: the form. Now that may seem unexpected considering I have just extolled Gemini’s simplicity, and forms deviate quite a bit from the “bunch of simple hyperlinked text pages” paradigm. But forms do something quite interesting: they enable the Web to be read-write. Project such as collaborative wikis, forums, or blogs with comment sections require some way of allowing users to send data (other than just URLs) to the server, and forms are a quite convenient and flexible way to do that. Gemini does have a limited form of interactivity: the server may respond to a request with an “INPUT” response code which tells the user browser to prompt for a line of input, and then repeat the request with the user input appended as the query string in the URL (sort of like a HTTP GET request). This is meant to allow implementing pages such as search engines which prompt for a query to search, but you can only ask for a single line of input at a time this way, which feels like a quite arbitrary limitation. Forms allow an arbitrary number of fields to be inputted, and even arbitrary text via the <textarea> element, making them much more general-purpose.

Of course, this goes against the goal stated in the Gemini FAQ that “A basic but usable (not ultra-spartan) client should fit comfortably within 50 or so lines of code in a modern high-level language. Certainly not more than 100.” It also may open a can of worms, in that once you want to have forums, wikis or other pages that require some form of login, you will probably want some way to keep session state across pages, and then we need some form of cookies. Gemini actually already has the idea of client-side certificates which can be used for maintaining a session, so maybe that’s not really a problem.

As a side note, cookies (as in pieces of session state maintained by the client) don’t have to be bad, the web just happens to have a pretty problematic implementation of that idea, amplified by the fact that webpages can embed resources from third-party domains (such as images, iframes, scripts, etc.) that get loaded automatically when the page is loaded, and the requests to obtain those resources can carry cookies (third-party cookies). Blocking third-party cookies goes a long way to avoid this. Blocking all third-party resources from being loaded by default would be even better. Webpages would have to be designed differently to make this work, but honestly, it would probably be for the best. Alas, this ship has already sailed for the Web.

* * *

There are other interesting things I would like to comment on regarding Gemini and the Web, but this blog post has been lying around unfinished for weeks already, and I’m too tired to finish it at the moment, so that’s all for now, folks.

4 comentários / comments

The curious case of NFC and LineageOS battery consumption

2021-05-31 21:30 +0100. Tags: comp, android, in-english

I was experiencing short battery duration (the battery was lasting barely 1 day) on my Samsung Galaxy J3 (2016) phone running LineageOS 14.1. The battery didn’t last any longer with the original ROM, so I assumed that the battery was old and bought a new one, replacing the original 2600mAh battery with an third-party 3630mAh one. The battery duration did improve a bit, but not nearly as much as I expected it to with a new battery with larger capacity. So I decided to investigate the situation a bit better.

The webs had told that the problem was likely some application holding a wakelock, blocking the phone from sleeping. I installed an app called BetterBatteryStats, which can be found on F-Droid. This app runs in background and collects battery usage statistics from running apps; you have to let it run for a while to get useful information from it. After some 30 minutes, I looked at the Partial Wakelocks panel and saw that there was a NfcService:mRoutingWakeLock item responsible for some 22% of battery consumption.

Now, NFC is a technology used for contactless payments using the phone, and similar applications. The Galaxy J3 does not support NFC. I’m not sure why the system was wasting CPU on this; I have found other people complaining about this same issue on the same ROM and phone.

The solution is to disable NFC Service in the system. Open up adb shell, become root (su), and then run:

pm hide com.android.nfc

After you do this, the system will loop complaining that NFC Service has been stopped. Restart the phone, and the error will be gone.

After a full charge, the system battery stats now tell me the battery will last 4 days. Will it really? Only time will tell, but I can already see that the battery is draining much more slowly than before.

2 comentários / comments

Lenovo L22e-20 screen brightness and XRandR mode

2021-02-07 12:41 +0000. Tags: comp, unix, x11, in-english

Computer screens are a complicated business for me: most screens are too bright for my eyes even at the zero brightness setting. I have had some luck with the most recent laptops I used, though – a Dell Latitude 7490 I bought second-hand last year, and an HP Pavillion Gaming Laptop provided by my company, both of which have excellent screens – so I wondered if maybe monitor technology had improved enough lately that I would be able to get an external monitor that won’t burn my eyes after a few hours use. So I decided to try my luck with a Lenovo L22e-20 monitor.

When it arrived, I tried it out and was immediately disappointed. It was just as bad brightness-wise as every other LCD external monitor I had used. Even at the zero brightness setting, the black background was not black, but dark grey, which made the contrast too low. The image quality was really good for watching videos, but for staring at text for extended periods of time, it was just not comfortable to look at; my laptop screen was much better. I was so disappointed that I decided I would return the monitor and order a different one.

A couple of days later, I decided to try it again, and to my great surprise, the monitor did not look bad at all. The black was really black, the brightness was pretty good (though I wish I could lower it a little bit further at night). I wondered if I had just gotten used to the new monitor, but the difference was so great that it was hard to believe it was just a psychological effect.

A few hours later, I wanted to see how i3 would handle workspaces when screens are disconnected or connected to a running session. So I disconnected the Lenovo monitor and connected it again – and to my even greater surprise the screen came back with the awful brightness of the first time. I tried to look at every setting in the monitor, but nothing had changed; I tried disconnecting and connecting again, to no avail; I tried to turn everything off and on again – nothing changed, same awful brightness.

The next day, I decided to look at XRandR settings – maybe it was some software-side gamma or brightness setting or something that was affecting the brightness. I ran xrandr --verbose, and gamma/brightness values were normal, but I noticed something else: there were five different 1920x1080 modes for the screen. This is the relevant part of the output:

  1920x1080 (0xa4) 148.500MHz +HSync +VSync *current +preferred
        h: width  1920 start 2008 end 2052 total 2200 skew    0 clock  67.50KHz
        v: height 1080 start 1084 end 1089 total 1125           clock  60.00Hz
  1920x1080 (0xa5) 174.500MHz +HSync -VSync
        h: width  1920 start 1968 end 2000 total 2080 skew    0 clock  83.89KHz
        v: height 1080 start 1083 end 1088 total 1119           clock  74.97Hz
  1920x1080 (0xa6) 148.500MHz +HSync +VSync
        h: width  1920 start 2008 end 2052 total 2200 skew    0 clock  67.50KHz
        v: height 1080 start 1084 end 1089 total 1125           clock  60.00Hz
  1920x1080 (0xa7) 148.500MHz +HSync +VSync
        h: width  1920 start 2448 end 2492 total 2640 skew    0 clock  56.25KHz
        v: height 1080 start 1084 end 1089 total 1125           clock  50.00Hz
  1920x1080 (0xa8) 148.352MHz +HSync +VSync
        h: width  1920 start 2008 end 2052 total 2200 skew    0 clock  67.43KHz
        v: height 1080 start 1084 end 1089 total 1125           clock  59.94Hz

Here, besides the resolution, frequencies, and other characteristics, each mode is identified by a hexadecimal code in parentheses (0xa4, 0xa5, etc.). Turns out you can pass those codes to the xrandr --mode option instead of a resolution such as 1920x1080 to select one among multiple modes with the same resolution.

I decided to try the other modes, just to see what difference it would make – and lo and behold, the second mode made the screen brightness good again! All the other modes left the screen with the bright background. I don’t know what it is specifically about this mode that had an effect on brightness, but I notice two things: it is the mode with the highest frequency, and it is the only one with -VSync rather than +VSync (the xorg.conf manpage tells us this is the polarity of the VSync signal, whatever that is). Maybe one (or both) of these elements is involved in the trick.

Actually, even if you run xrandr without the --verbose option, it will list potentially multiple modes for each resolution, by showing all available refresh rates for each resolution:

HDMI-1 connected 1920x1080+0+0 (normal left inverted right x axis y axis) 476mm x 268mm
   1920x1080     60.00 +  74.97*   60.00    50.00    59.94  
   1920x1080i    60.00    50.00    59.94  
   1680x1050     59.88  
   1280x1024     75.02    70.00    60.02  
   1440x900      59.90  
   1152x864      75.00  
   1280x720      60.00    60.00    50.00    59.94  
   1024x768      75.03    70.07    60.00  
   800x600       72.19    75.00    60.32  
   720x576       50.00    50.00    50.00  
   720x480       60.00    60.00    59.94    59.94    59.94  
   640x480       75.00    72.81    60.00    59.94    59.94  
   720x400       70.08  

I had never paid much attention to this, but you can actually select the specific mode you want by calling, for example, xrandr --output HDMI-1 --mode 1920x1080 --rate 74.97, specifying both the resolution and the refresh rate. In some cases, though, there are multiple modes with the same refresh rate (for example, the 720x576 line above has three different modes with the same refresh rate 50.00); in this case, I think the only way to choose a specific mode is to specify the hexadecimal code of the mode listed by the --verbose option.

If you don’t specify a refresh rate or give a specific mode hex code, XRandR will theoretically select the “preferred” mode, which is the one with a + sign after it in the output. For this Lenovo monitor, the preferred mode is a bad one, so you have to override it with these options.

The weirdest thing about this story is that, on the day the monitor was suddenly good, Xorg had apparently selected a non-preferred mode by pure chance for some reason. If that had not happened, I would probably have never discovered that the monitor had a good mode at all.

2 comentários / comments

Main menu

Recent posts

Recent comments


em-portugues (213) comp (148) prog (71) in-english (62) life (49) unix (38) pldesign (37) lang (32) random (28) about (28) mind (26) lisp (25) fenius (22) mundane (22) web (20) ramble (18) img (13) rant (12) hel (12) scheme (10) privacy (10) freedom (8) esperanto (7) music (7) lash (7) bash (7) academia (7) copyright (7) home (6) mestrado (6) shell (6) android (5) conlang (5) misc (5) emacs (5) latex (4) editor (4) etymology (4) php (4) worldly (4) book (4) politics (4) network (3) c (3) tour-de-scheme (3) security (3) kbd (3) film (3) wrong (3) cook (2) treta (2) poem (2) physics (2) x11 (2) audio (2) comic (2) lows (2) llvm (2) wm (2) philosophy (2) perl (1) wayland (1) ai (1) german (1) en-esperanto (1) golang (1) translation (1) kindle (1) pointless (1) old-chinese (1)


Quod vide

Copyright © 2010-2024 Vítor De Araújo
O conteúdo deste blog, a menos que de outra forma especificado, pode ser utilizado segundo os termos da licença Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International.

Powered by Blognir.