Getting started with nREPL server and REPL-y client

nREPL

If you are using Clojure, you might be interested in nREPL which lets you connect a REPL terminal to a running Clojure program. In the following I am setting up a small nREPL demo using the nREPL server and the REPL-y nREPL client.

First I set up aliases for the server and client in $HOME/.clojure/deps.edn as follows:

{:aliases {:nrepl {:extra-deps {nrepl/nrepl {:mvn/version "1.1.0"}}}
           :reply {:extra-deps {reply/reply {:mvn/version "0.5.1"}}
                   :main-opts ["-m" "reply.main" "--attach" "7888"]}}}

Now I need a small demo program to test things out. First I create $HOME/Documents/repltest/deps.edn which just specifies the Clojure version.

{:deps {org.clojure/clojure {:mvn/version "1.11.1"}}
 :paths ["src"]}

The following program then displays a counter which gets increased once per second. Furthermore it starts an nREPL server on port 7888. The program goes into the file $HOME/Documents/repltest/src/repltest/core.clj.

(ns repltest.core
  "nREPl demo"
  (:gen-class))

(require '[nrepl.server :refer [start-server stop-server]])
(defonce server (start-server :port 7888))

(def t (atom 0))

(defn -main
  "nREPL demo"
  [& _args]
  (while true
         (println (swap! t inc))
         (Thread/sleep 1000))
  (System/exit 0))

Now one can run the program using clj -M:nrepl -m repltest.core. The program will print out consecutive numbers as follows:

1
2
3
4
.
.
.

Now you need to open a second terminal for the nREPL client. You run the network client using clojure -M:reply. The important thing which took me some time to find out is that you need to then switch to your applications namespace as follows:

user=> (ns repltest.core)

Now you can easily access the variables of the main program:

repltest.core=> @t
42

You can also modify the value while the main program is still running:

repltest.core=> (swap! t - 10)
32

You should see the counter decrease in the application’s output. You can even redefine methods using the nREPL client. I.e. you can do interactive development.

See github.com/wedesoft/repltest for the demo code.

Enjoy!

Omikron Basic

The first program I wrote as a kid was an Omikron Basic program on an Atari 1040ST. I wrote a program to print something and later I tried out a for-loop. It took me a long time to figure out that there was an EDIT mode with automatic line numbering. Using Hatari I recreated a short video of this:

Here is the download link for the Omikron Basic STX disk image.

Flight Simulator 2 on Atari ST

As a kid I got the joy of playing Flight Simulator 2 on my dad’s Atari 1040ST. The Atari had a 640x400 monochrome monitor.

I noticed that Debian 12 comes with the Hatari Atari emulator. Furthermore one can download a disk image of Flight Simulator 2 at atarimania.com. Then it was just a matter of unzipping the file and specifying the disk image on the Hatari command line.

sudo apt install hatari
unzip flight_simulator_2.zip
hatari --mono --disk-a "Flight Simulator 2.stx"

I took a video of the initial part of the demo mode here:

Enjoy!

Update: You also need to download a TOS image for Atari ST and copy it to /usr/share/hatari/tos.img.

Specifying Clojure function schemas with Malli

Clojure is a dynamically typed language which means that functions can be called with arbitrary values. If the function cannot operate on the values (or if it calls a function which cannot operate on a value or part of a value) a runtime exception is thrown. Even worse, a function could exhibit undefined behaviour for the values provided.

Statically typed languages are often considered safer because the types of values are known at compile time so that the validity of values can be checked. However statically typed implementations tend to become inflexible. Type checking at compile time requires the types of the values held by variables to be defined at compile time. When trying to keep the system both modular and extensible, the developer is forced to come up with complicated type hierarchies and possibly even generic types.

There is a promising solution to this and this is using pre- and post-conditions (contracts) for functions. In particular there is Malli which allows you to specify schemas for each function using a concise syntax. Malli schemas can be defined, reused, and composed like types. Note that a value can fulfill multiple schemas (similar to Rust traits), however implementations and schemas exist independently from each other. This allows schemas to be more specific than static types will ever be (e.g. you can have a schema for lists with three or more elements).

Malli is optimized for good runtime performance. If performance is still a concern, one can leave schema validation disabled in production for some or all namespaces.

Here follows a short introduction to using Malli for function pre- and post-conditions:

You can use the Malli provider module to suggest possible schemas matching a list of values.

(require '[malli.provider :as mp])
(mp/provide [1])
; :int
(mp/provide [1 nil])
; [:maybe :int]
(mp/provide [[1 2 3]])
; [:vector :int]

The resulting schemas can be used to validate values.

(require '[malli.core :as m])
(m/validate [:vector :int] [1 2 3])
; true
(m/validate [:vector :int] [1 2 nil])
; false

Schemas are values which can be reused and composed.

(def N (m/schema [:int {:min 1}]))
(m/validate [:vector N] [0 1 2])
; false
(m/validate [:vector N] [1 2 3])
; true

It is possible to add predicate functions to schemas.

(def ordered-vector (m/schema [:and [:vector :int] [:fn (fn [v] (apply < v))]]))
(m/validate ordered-vector [2 5 3])
; false
(m/validate ordered-vector [2 3 5])
; true

Most importantly one can add pre- and post-conditions (function schemas) to functions.

(defn sqr
  {:malli/schema [:=> [:cat :double] :double]}
  [x]
  (* x x))

By default function schemas are ignored until the schemas are collected and the functions are instrumented. Note that here I am replacing the default exception thrower with a more human-readable one.

(require '[malli.instrument :as mi])
(require '[malli.dev.pretty :as pretty])
(mi/collect!)
(mi/instrument! {:report (pretty/thrower)})
(sqr 3.0)
; 9.0
(sqr "text")
; -- Schema Error ---------------------------------------------------------------- NO_SOURCE_FILE:1 --
;
; Invalid function arguments:
;
;   ["text"]
;
; Function Var:
;
;   user/sqr
;
; Input Schema:
;
;   [:cat :double]
;
; Errors:
;
;   {:in [0], :message "should be a double", :path [0], :schema :double, :value "text"}
;
; More information:
;
;   https://cljdoc.org/d/metosin/malli/CURRENT/doc/function-schemas
;
; ----------------------------------------------------------------------------------------------------

If you want to collect the schemas from a module (e.g. for testing purposes), you can specify the namespace as follows.

(mi/collect! {:ns ['the.module]})

You can also parse code to infer a general schema for a function header.

(require '[malli.destructure :as md])
(def infer (comp :schema md/parse))
(infer '[x])
; [:cat :any]

If you have a multiple arity function, you can use :function to specify the different alternatives.

(defn f
  {:malli/schema [:function [:=> [:cat :double] :double]
                            [:=> [:cat :double :double] :double]]}
  ([x] x)
  ([x y] (+ x y)))
(mi/collect!)
(mi/instrument! {:report (pretty/thrower)})
(f 2.0 3.0)
; 5.0
(f "test")
; ... error ...

There is also support for sequence schemas which allows a more compact schema in this case.

(defn f
  {:malli/schema [:=> [:cat :double [:? :double]] :double]}
  ([x] x)
  ([x y] (+ x y)))

Finally here is an example for a method accepting keyword arguments which took me some time to figure out.

(defn g
  {:malli/schema [:=> [:cat [:= :x] :double [:= :y] :double] :double]}
  [& {:keys [x y]}]
  (+ x y))
(mi/collect!)
(mi/instrument! {:report (pretty/thrower)})
(g :x 2.0 :y 3.0)
; 5.0
(g :x 2.0 :y "test")
; ... error ...

The only problem I encountered so far is that instrumentation clashes with Clojure type hints. I guess the solution is to instead provide the type hints at the call site instead where necessary for performance reasons.

Enjoy!

Updates:

You can also define recursive schemas by using a local registry:

(require '[malli.core :as m])
(def nested-vector
  (m/schema [:schema {:registry {::node [:or :int [:vector [:ref ::node]]]}}
                     [:ref ::node]]))
(m/validate nested-vector [[1 2] 3])
; true
(m/validate nested-vector [[1 :k] 3])
; false

One can use m/=> to add a Malli schema to a function. This comes in handy when you need to add a schema to a Clojure multimethod.

(require '[malli.core :as m])
(require '[malli.instrument :as mi])
(require '[malli.dev.pretty :as pretty])
(defmulti factorial identity)
(m/=> factorial [:=> [:cat integer?] integer?])
(defmethod factorial 0 [_]  1N)
(defmethod factorial :default [num]
   (* num (factorial (dec num))))
(mi/instrument! {:report (pretty/thrower)})
(factorial 10)
; 3628800N
(factorial 1.5)
; ... error ...

Create Blender bones and animate and import with Assimp

This article is about

  • creating objects and armatures consisting of bones
  • adding inverse kinematics
  • creating animations
  • exporting to glTF
  • importing the animations with Assimp

Here is the Youtube video showing the steps involved:

Here is a corresponding step-by-step procedure to create a proof-of-concept kinematic chain in Blender

  • create armature
    • first clear scene (in layout mode)
    • in Object mode add an armature which is going to be named “Armature”
    • rotate the armature around the y-axis by 90 degrees so that the bone points in the direction of the x-axis
    • in Edit mode move the tip of the bone to enlarge it
    • press E and extrude the bone to add a second bone to the chain
    • in Edit mode add an additional bone to the armature by pressing Shift-A and move it to the end of the previous chain
    • in the tree view rename the bones to “upper bone”, “lower bone”, and “stay IK”
  • set up inverse kinematic constraint
    • in Pose mode select the stay IK bone first (!) and then shift-click on the lower bone.
    • go to bone constraints (not constraints) and add an Inverse Kinematics constraint
    • set target to “Armature” and bone to “stay IK”
    • set the chain length to 2
    • note that the bone constraint should appear under the lower bone in the tree view
    • moving the stay IK bone in Pose mode should now update the kinematic chain of upper and lower bone
  • add objects and connect them to armature
    • in Object mode select the armature, go to Object properties -> Viewport display, and check name and in front
    • in Object mode create a cuboid and move it to the position of the upper bone
    • click on the cube and shift-click on the armature
    • switch to Pose mode, click on the cube, and shift-click on the upper bone
    • press Ctrl-P and set the parent to bone
    • moving the stay IK bone should now move the cuboid
    • add another cube and set the parent to the lower bone (similar to the last 5 steps but for the lower bone)
    • add a small sphere and move it to the stay IK bone position
    • select the armature in Object mode
    • switch to Pose mode and select the stay IK bone
    • add the “Child Of” bone constraint and set the target to the sphere
    • moving the sphere in Object mode should move the cuboids now
  • add top-level armatures
    • add another armature in Object mode and set the name to “Control” in the tree view
    • click the upper arm bone and shift click the control armature, use Ctrl-P and set it as parent object
    • set the parent object of the sphere to the control armature as well (object, keep transform)
    • moving the control bone now should move everything else
    • create a root armature
    • shift drag and drop control armature under root armature (an extra node in the tree is useful if you want to overwrite the top-level transform programmatically)
  • create animations and export to glTF
    • go to the animation editor
    • add two keyframes for 90 degree rotation of the control bone around the y-axis
    • switch from Dope sheet to Nonlinear animation editor and push down the action
    • select the control bone in the tree view
    • go to Edit -> Bake action and bake only the selected bones and Object data
    • delete the original action, rename the current action to “ControlAction”, and push it down
    • uncheck the control action
    • add two keyframes moving the sphere
    • select the armature and the sphere and bake another action
    • push down the generated actions and the sphere action and rename to the same name (e.g. “CubeAction”) so that they will later become one animation in glTF
    • check the control action back on so that all actions are checked now
    • export the glTF with the animations (no need to check bake all actions)

You can use the LWJGL Assimp Java library to import the animations into Clojure as follows:

(import '[org.lwjgl.assimp Assimp AIAnimation AINodeAnim])
(def scene (Assimp/aiImportFile "test.gltf" (bit-or Assimp/aiProcess_Triangulate Assimp/aiProcess_CalcTangentSpace)))
(.mNumAnimations scene)
; 2
(def animation (AIAnimation/create ^long (.get (.mAnimations scene) 1)))
(/ (.mDuration animation) (.mTicksPerSecond animation))
; 10.4166669921875
(.mNumChannels animation)
; 5
(map #(.dataString (.mNodeName (AINodeAnim/create ^long (.get (.mChannels animation) %)))) (range (.mNumChannels animation)))
; ("Sphere" "Armature" "Stay IK" "Lowe Bone" "Upper Bone")
(def node-anim (AINodeAnim/create ^long (.get (.mChannels animation) 4)))
(.dataString (.mNodeName node-anim))
; "Upper Bone"
(.mNumPositionKeys node-anim)
; 2
(.mNumRotationKeys node-anim)
; 250
(.mNumScalingKeys node-anim)
; 2
(def quaternion (.mValue (.get (.mRotationKeys node-anim) 249)))
[(.w quaternion) (.x quaternion) (.y quaternion) (.z quaternion)]
; [0.99631643 -1.20924955E-17 -1.5678631E-17 0.08575298]
(/ (.mTime (.get (.mRotationKeys node-anim) 249)) (.mTicksPerSecond animation))
; 10.4166669921875

I used tmux, Clojure with rebel-readline, and vim with vim-slime to do the coding.

For an in-depth introduction to rigging I can recommend Mark Alloway’s video on how to rig and animate a landing gear.

Enjoy!