A performance-oriented Lisp-like language where I can have my cake, and eat it (too)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4.7 KiB

#+TITLE:Fun with Cakelisp

Here are a list of things I did with Cakelisp which I thought showcase its niceties. Nothing is too small, because small things can make a big difference!

No more header files

They're such an annoyance to manage, which causes me to not want to create separate files. Cakelisp still retains the concept of declarations vs. definitions via local and other keywords. This gives you the control to expose limited parts of a module, without having to declare those parts in a separate file.

Code modification

I made AutoTest.cake, which automatically finds functions preceded by test-- and compiles them all into a single main function, calling them one by one. It's a dead-simple way to embed high-level tests.

The feel of scripts, the performance of C

cakelisp --execute makes it possible to run .cake files as if they were simple scripts. Cakelisp will handle all the building and caching for you. No more error-prone and tedious "compile, link, run, make change, compile…" which you get with C/C++ files.

With a simple "hello world" application, I found Cakelisp to be about half as fast as Python to build and run the program. You only pay this price when the code changes, however. If there are no changes, the Cakelisp version evaluates and executes an order of magnitude faster than Python (which isn't saying much, admittedly - Python is known for being slow).

I also check for "shebang" #! and ignore the line if found on the first line in a .cake file. This allows users to execute their scripts via e.g.:

chmod +x MyScript.cake

…when MyScript.cake is:

  #!/usr/bin/cakelisp --execute
  (c-import "<stdio.h>")

  (defun main (&return int)
    (printf "Hello, execute!\n")
    (return 0))

No more build system woes

I got rid of external build systems! I used to use Jam (which is still used to build Cakelisp itself). Now, Cakelisp can handle it all internally.

This has made it much more fun to program with, because all I need to do to use a new dependency like SDL is (import "SDL.cake"). It's the responsibility of the module to know how it needs to be built, which makes all that complexity stay there instead of spreading into every new project. I got this idea from Jai.

In GameLib, I have simple modules for Tracy, SDL, and Ogre already set up.

Build performance seems promising

I have not yet seen the need for partial builds. Partial builds make the Cakelisp internals much more complicated. So far, the high-level performance of Cakelisp has me optimistic that I will be able to continue fully parsing and evaluating the files without resorting to partial loading. Note that this does not include compiling and linking, which are handled by external processes, and dominate the total time when building clean.

Build configuration labels

Build configurations are constructed "lazily", meaning all you need to do to create a new configuration is make the necessary changes to the environment and add a unique label.

For example, a build configuration Debug-HotReloadable could be constructed via:

  • Overriding the build command via (set-cakelisp-option build-time-compile-arguments ...), adding debug flags. (add-build-config-label "Debug") and that's all needed to create the Debug configuration

  • Importing HotReloadingCodeModifier.cake, which adds (add-build-config-label "HotReloadable"). This is important because hot-reloadable builds are different from regular builds - they expect their variables to be initialized by the loader, and a dynamically linked library is created instead of a standalone executable

I like this solution because it gives the user the ability to make their configurations as complex as they want, without having to face any additional/introductory complexity. For example, we could add processor architecture, operating system, and C standard library selections to our configurations, if necessary. A/B comparisons between runtime performance could also be done easily, just by adding a label to the alternate.

If you are just writing a quick one-off script, you need not worry about configurations at all.

Because all options must be provided in Cakelisp files, it encourages composable configurations. For example, we could take the Debug configuration from above and put it in Config_Debug.cake, then import it and build the program via cakelisp Config_Debug.cake MyProgram.cake.