|
| 1 | +^{:kindly/hide-code true |
| 2 | + :clay {:title "Emmy, the Algebra System: Infix Notation" |
| 3 | + :quarto {:author :kloimhardt |
| 4 | + :type :draft |
| 5 | + :date "2025-11-15" |
| 6 | + :category :libs |
| 7 | + :tags [:emmy :infix :yamlscript]}}} |
| 8 | + |
| 9 | +(ns mentat-collective.emmy.fdg-ch01-ys |
| 10 | + (:refer-clojure :exclude [+ - * / zero? compare divide numerator denominator |
| 11 | + time infinite? abs ref partial =]) |
| 12 | + (:require [emmy.env :refer :all :exclude [D F->C]] |
| 13 | + [yamlscript.compiler :as ys] |
| 14 | + [scicloj.kindly.v4.api :as kindly] |
| 15 | + [scicloj.kindly.v4.kind :as kind])) |
| 16 | + |
| 17 | +^:kindly/hide-code |
| 18 | +(def +++ identity) |
| 19 | + |
| 20 | +^:kindly/hide-code |
| 21 | +(def mul+ *) |
| 22 | + |
| 23 | +^:kindly/hide-code |
| 24 | +(def add+ +) |
| 25 | + |
| 26 | +^:kindly/hide-code |
| 27 | +(defn ysc [s] |
| 28 | + (ys/compile (str "!ys-0\n" s))) |
| 29 | + |
| 30 | +^:kindly/hide-code |
| 31 | +(defmacro ys [s] |
| 32 | + (list 'kindly/hide-code |
| 33 | + [(list 'kind/code s) |
| 34 | + (read-string (ysc s))])) |
| 35 | + |
| 36 | +^:kindly/hide-code |
| 37 | +(kind/hiccup |
| 38 | + [:div |
| 39 | + [:script {:src "https://cdn.jsdelivr.net/npm/scittle-kitchen/dist/scittle.js"}] |
| 40 | + [:script {:src "https://cdn.jsdelivr.net/npm/scittle-kitchen/dist/scittle.emmy.js"}] |
| 41 | + [:script {:src "https://cdn.jsdelivr.net/npm/scittle-kitchen/dist/scittle.cljs-ajax.js"}] |
| 42 | + [:script {:src "https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js", :crossorigin ""}] |
| 43 | + [:script {:src "https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.production.min.js", :crossorigin ""}] |
| 44 | + [:script {:src "https://cdn.jsdelivr.net/npm/scittle-kitchen/dist/scittle.reagent.js"}] |
| 45 | + [:script {:type "application/x-scittle" :src "scheme.cljc"}]]) |
| 46 | + |
| 47 | + |
| 48 | +^:kindly/hide-code |
| 49 | +(kind/scittle |
| 50 | + '(require '[emmy.env :refer :all :exclude [D F->C]])) |
| 51 | + |
| 52 | +^:kindly/hide-code |
| 53 | +(def time first) |
| 54 | + |
| 55 | +^:kindly/hide-code |
| 56 | +(kind/scittle |
| 57 | + '(def time first)) |
| 58 | + |
| 59 | +^:kindly/hide-code |
| 60 | +(kind/scittle |
| 61 | + '(def D partial)) |
| 62 | + |
| 63 | +;; The Clojure code below is taken from the snippets of [a previous entry](https://clojurecivitas.github.io/mentat_collective/emmy/fdg_ch01.html). Please make pull requests to [the source file of this notebook](https://github.com/kloimhardt/clojurecivitas.github.io/blob/infix/src/mentat_collective/emmy/fdg_ch01_ys.clj). It is not necessary to understand what the Clojure code does. As this semantics does not matter here, it is rather the syntax, the notation, that should be compared and critiqued vis-a-vis the proposed infix notation. |
| 64 | + |
| 65 | +;; ## First Example |
| 66 | + |
| 67 | +(kind/scittle |
| 68 | + '(defn Lfree [mass] |
| 69 | + (fn [[_ _ v]] (* 1/2 mass (square v))))) |
| 70 | + |
| 71 | +;; The following infix notation indeed compiles to Clojure code and is equivalent to the above. |
| 72 | + |
| 73 | +(ys " |
| 74 | +defn LFree(mass): |
| 75 | + fn([_ _ v]): mass * 1/2 * square(v) |
| 76 | +") |
| 77 | + |
| 78 | +;; ## Another one |
| 79 | + |
| 80 | +;; I proceed to the next infix snippet |
| 81 | + |
| 82 | +(ys " |
| 83 | +defn sphere-to-R3(R): |
| 84 | + fn([_ [theta phi]]): |
| 85 | + up: |
| 86 | + =>: R * sin(theta) * cos(phi) |
| 87 | + =>: R * sin(theta) * sin(phi) |
| 88 | + =>: R * cos(theta) |
| 89 | +") |
| 90 | + |
| 91 | +;; which is the following in Clojure |
| 92 | + |
| 93 | +(kind/scittle |
| 94 | + '(defn sphere->R3 [R] |
| 95 | + (fn [[_ [theta phi]]] |
| 96 | + (up (* R (sin theta) (cos phi)) |
| 97 | + (* R (sin theta) (sin phi)) |
| 98 | + (* R (cos theta)))))) |
| 99 | + |
| 100 | +;; ## Higher order functions |
| 101 | + |
| 102 | +;; For the next infix, I introduce `call` (with obvious behaviour) and two aliases. |
| 103 | + |
| 104 | +(do |
| 105 | + (defn call [f x] (f x)) |
| 106 | + (def of call) |
| 107 | + (def at call)) |
| 108 | + |
| 109 | +(def D partial) |
| 110 | + |
| 111 | +(ys " |
| 112 | +defn F-to-C(F): |
| 113 | + fn(state): |
| 114 | + up: |
| 115 | + time: state |
| 116 | + F: state |
| 117 | + =>: D(0).of(F).at(state) + ( D(1).of(F).at(state) * velocity(state) ) |
| 118 | +") |
| 119 | + |
| 120 | +;; With `of` and `at` like that, the above `D(0).of(F).at(state)` (which means "take the zeroth derivative of the function F at point state") translates into what are higher order functions in the Clojure version. |
| 121 | + |
| 122 | +(kind/scittle |
| 123 | + '(defn F->C [F] |
| 124 | + (fn [state] |
| 125 | + (up (time state) |
| 126 | + (F state) |
| 127 | + (+ (((D 0) F) state) |
| 128 | + (* (((D 1) F) state) |
| 129 | + (velocity state))))))) |
| 130 | + |
| 131 | +;; ## Another one |
| 132 | + |
| 133 | +(ys " |
| 134 | +defn Lsphere(m R): |
| 135 | + compose: |
| 136 | + LFree: m |
| 137 | + F-to-C: sphere-to-R3(R) |
| 138 | +") |
| 139 | + |
| 140 | +(kind/scittle |
| 141 | + '(defn Lsphere [m R] |
| 142 | + (compose (Lfree m) (F->C (sphere->R3 R))))) |
| 143 | + |
| 144 | +;; ## The proof is in the pudding |
| 145 | + |
| 146 | +;; In order to quote symbols, I introduce a macro called `q` |
| 147 | + |
| 148 | +(defmacro q [f] (list 'quote f)) |
| 149 | + |
| 150 | +(ys " |
| 151 | +simplify: |
| 152 | + Lsphere(m:q R:q): |
| 153 | + up: |
| 154 | + =>: t:q |
| 155 | + up: theta:q phi:q |
| 156 | + up: thetadot:q phidot:q |
| 157 | +") |
| 158 | + |
| 159 | +(kind/reagent |
| 160 | + (vector :tt |
| 161 | + '(simplify |
| 162 | + ((Lsphere 'm 'R) |
| 163 | + (up 't (up 'theta 'phi) (up 'thetadot 'phidot)))))) |
| 164 | + |
| 165 | +;; Indeed the results are the same which proves the infix technically works. |
| 166 | + |
| 167 | +;; ## Intermission |
| 168 | + |
| 169 | +;; The infix notation I use here is `YS` (say "wise"), also called [YAMLScript](https://yamlscript.org/). I have read one opinion that says "I do not want my program to run through a YAML parser". This is a vantage point which is not applicable to Clay notebooks. A Clay notebook is not about maintaining a codebase, it is about conveying ideas and concepts. It is not about the writing of code, it is about the audience which reads the code. |
| 170 | + |
| 171 | +;; And concerning reading code, YS has a lot to offer for the Clojure community. Namely that with YS we can communicate the idea of functional+immutable to that particular audience which is not even willing to read code that has the "parens in the wrong place". |
| 172 | + |
| 173 | +;; I'd like to put into light a related project of mine. With [LisRoot](https://github.com/kloimhardt/LisRoot) I try to lure away the HEP-community from Python towards Clojure/jank. And here YS is the single best way to show code snippets that are functional+immutable. Those people who like that concept will eventually learn to install a code editor for slurp/barfing and then also cherish the nrepl as we all do. |
| 174 | + |
| 175 | +;; I did not find the appropriate YS-compiler on Clojars (for Clay I do not want a dependency on the shared compiled library) , so to deps.edn, I added the following git-sha: |
| 176 | + |
| 177 | +^:kindly/hide-code |
| 178 | +(kind/code " |
| 179 | +yamlscript/core {:git/url \"https://github.com/yaml/yamlscript\" |
| 180 | + :git/sha \"ed7adfbf90a39f379d5a7193bb2e4bdd7f0eecf8\" |
| 181 | + :deps/root \"core\"} |
| 182 | +") |
| 183 | + |
| 184 | +;; ### Some more examples |
| 185 | + |
| 186 | +;; ## 1 |
| 187 | +(ys " |
| 188 | +defn L2(mass metric): |
| 189 | + fn(place velocity): mass * 1/2 * metric(velocity velocity).at(place) |
| 190 | +") |
| 191 | +(kind/scittle |
| 192 | + '(defn L2 [mass metric] |
| 193 | + (fn [place velocity] |
| 194 | + (* 1/2 mass ((metric velocity velocity) place))))) |
| 195 | + |
| 196 | + |
| 197 | +;; ## 2 |
| 198 | + |
| 199 | +^:kindly/hide-code |
| 200 | +(def coordinate-system-to-vector-basis coordinate-system->vector-basis) |
| 201 | + |
| 202 | +(ys " |
| 203 | +defn Lc(mass metric coordsys): |
| 204 | + e =: coordinate-system-to-vector-basis(coordsys) |
| 205 | + fn([_ x v]): |
| 206 | + L2(mass metric): point(coordsys).at(x) (e * v) |
| 207 | +") |
| 208 | + |
| 209 | +(kind/scittle |
| 210 | + '(defn Lc [mass metric coordsys] |
| 211 | + (let [e (coordinate-system->vector-basis coordsys)] |
| 212 | + (fn [[_ x v]] |
| 213 | + ((L2 mass metric) ((point coordsys) x) (* e v)))))) |
| 214 | + |
| 215 | +;; ## 3 |
| 216 | + |
| 217 | +(ys " |
| 218 | +the-metric =: literal-metric(g:q R2-rect) |
| 219 | +") |
| 220 | + |
| 221 | +(kind/scittle |
| 222 | + '(def the-metric (literal-metric 'g R2-rect))) |
| 223 | + |
| 224 | + |
| 225 | +;; ## 4 |
| 226 | +(ys " |
| 227 | +L =: Lc(m:q the-metric R2-rect) |
| 228 | +") |
| 229 | + |
| 230 | +(kind/scittle |
| 231 | + '(def L (Lc 'm the-metric R2-rect))) |
| 232 | + |
| 233 | + |
| 234 | +;; ## and the pudding |
| 235 | + |
| 236 | +(ys " |
| 237 | +simplify: |
| 238 | + L: |
| 239 | + up: |
| 240 | + =>: t:q |
| 241 | + up: x:q y:q |
| 242 | + up: vx:q vy:q |
| 243 | +") |
| 244 | + |
| 245 | +(kind/reagent |
| 246 | + (vector :tt |
| 247 | + '(simplify |
| 248 | + (L (up 't (up 'x 'y) (up 'vx 'vy)))))) |
0 commit comments