Inspiriert durch ein Video habe ich mich nun endlich einmal hingesetzt und mit Lisp beschäftigt.
Das Video, das mich inspiriert hat war dieses hier:
Als ich es gesehen habe, wollte ich unbedingt ausprobieren, ob es wirklich so einfach - und vor allem mit so wenig Code - möglich ist, für (fast) jede Funktion von einem Computer die Ableitung bilden und berechnen zu lassen. Es ist schon wirklich sehr lange her, dass ich mich mit funktionalen Sprachen beschäftigen musste - das war damals noch in meinem Diplom-Studium der Informatik. Und irgendwie hat mich die Beschäftigung mit Gofer (der Sprache, nicht dem Protokoll - sehr geprägt - und zwar so, dass ich sogar den Lambda-Ausdrücken in Programmiersprachen eher zurückhaltend gegenüberstehe...
Ich habe daher versucht, zunächst den reinen Lisp-Code aus dem Video abzutippen - das machte, dass ich mich wieder extrem jung fühlte: Mein letztes Listing, das ich abgetippt hatte, stammte aus einer Computerzeitschrift - das war irgendwann zwischen 1989 und 1992.
Ich wollte versuchen, dieses Skript mit einem der zahlreichen online verfügbaren Lisp-REPLs auszuprobieren, scheiterte aber damit in mehreren, so dass ich mir GNU clisp installierte. Das funktionierte besser, allerdings hatte ich das Problem, dass eines der komplexeren Beispiele am Ende des Videos den falschen Wert lieferte. Nachdem ich den Quelltext gefühlt 10 mal mit dem im Video angegebenen verglichen und keine Unterschiede entdeckt hatte, schrieb ich eine EMail an den Ersteller des Videos um zu fragen, ob es eventuell sein könnte, dass doch eine Funktion im Video vergessen wurde oder ein Kunstgriff nicht erwähnt wurde, der erfahrenen Lisp-Programmierern so sehr in Fleisch und Blut übergegangen ist, dass sie nicht mehr darüber nachdenken.
Ich war erfreut und überrascht, dass Professor Weitz mir schon wenige Stunden später nicht nur antwortete, sondern mir sogar den für das Video benutzten Code zur Verfügung stellte. Jedoch habe ich auf Anhieb nicht einmal mit maschineller Unterstützung durch Meld gesehen, worin der Unterschied lag: Ich schiebe es auf 32 Jahre mit Assembler, C++, C, Java und Python, dass ich den Unterschied zwischen 1 / 2 (ich) und / 1 2 (korrekt) nicht wahrnahm.
Nach dem Ausbessern dieses Fehlers ergab sich folgendes Skript:
;;clisp -repl ableiten.lisp
;;https://www.youtube.com/watch?v=EyhL1DNrSME
(defun variable-p (expr)
(and (symbolp expr) (not (eq expr 'd))
(let ((name (symbol-name expr)))
(and (= (length name) 1) (alpha-char-p (char name 0))))))
(defun match-variable (var input bindings)
(let ((binding (assoc var bindings)))
(cond ((null binding) (cons (cons var input) bindings))
((equal input (cdr binding)) bindings))))
(defun match* (pattern input &optional (bindings '((dummy . dummy))))
(cond ((null bindings) nil)
((variable-p pattern) (match-variable pattern input bindings))
((eql pattern input) bindings)
((and (consp pattern) (consp input))
(match* (rest pattern) (rest input)
(match* (first pattern) (first input) bindings)))))
(defun match (pattern input)
(let ((result (match* pattern input)))
(and result (or (butlast result) t))))
(defun apply-rule (input rules)
(loop for (pattern replacement) in rules
for bindings = (match* pattern input)
thereis (and bindings (sublis bindings replacement))))
;;(defparameter *rules* nil)
(defun transform (expr &optional (*rules* *rules*)) (transform* expr))
(defun transform* (expr)
(if (atom expr) expr (transform-expr (mapcar #'transform* expr))))
(defun transform-expr (expr)
(cond ((transform* (apply-rule expr *rules*)))
((evaluable expr) (eval expr))
(t expr)))
(defun evaluable (expr)
(and (every #'numberp (rest expr))
(or (member (first expr) '(+ - * /))
(and (eq (first expr) 'expt) (integerp (third expr))))))
(defparameter *simple-diff-rules*
'(((D x x) 1)
((D (+ u v) x) (+ (D u x) (D v x)))
((D (* u v) x) (+ (* (D u x) v) (* u (D v x))))
((D (/ v) x) (- (/ (D v x) (* v v))))
((D u x) 0)
;;((D (sin u) x) (* (cos u) (D u x)))
;;((D (exp u) x) (* (exp u) (D u x)))
;;((D (log u) x) (* (/ u) (D u x)))
))
(defparameter *chain-diff-rules*
(loop for (in out) in '(((exp u) (exp u)) ((log u) (/ u))
((sin u) (cos u)) ((cos u) (- (sin u))))
collect `((D ,in x) (* ,out (D u x)))))
(defparameter *diff-rules*
(append (butlast *simple-diff-rules*)
*chain-diff-rules*
(last *simple-diff-rules*)))
(defparameter *input-rules*
'(((+ x y z . w) (+ x (+ y z . w)))
((* x y z . w) (* x (* y z . w)))
((- x y) (+ x (* -1 y)))
((/ x y) (* x (/ y)))
((^ x y) (expt x y))
((expt x y) (exp (* y (log x))))
((log a b) (/ (log a) (log b)))
((sqrt x) (^ x (/ 1 2)))
((tan x) (/ (sin x) (cos x)))))
(defparameter *simplification-rules*
'(((+ 0 x) x) ((+ x 0) x)
((* x 1) x) ((* 1 x) x)
((* 1 x . w) (* x . w))
((* x 0) 0) ((* 0 x) 0)
((* (/ x) x . w) (* 1 . w))
((* x (* y z . w)) (* x y z . w))
((exp (* a (log b))) (expt b a))))
(defun doit (expr)
(transform (transform (transform expr *input-rules*)
*diff-rules*) *simplification-rules*))
Nachdem damit zunächt das Video nachvollzogen war, wollte ich die Verbindung mit Java vollziehen: Das Ziel war, eine Implementierung zu erreichen, die es mir gestattete, für eine beliebige Funktion Werte ihrer ersten (und aller folgenden) Ableitungen zu berechnen.
Der folgende Code erreicht das unter Benutzung von Armed Bear Common Lisp (ABCL), einer Lisp-Variante, die JSR 223: Scripting for the JavaTM Platform umsetzt:
import javax.script.*;
import java.io.IOException;
import java.util.UUID;
class Function extends de.elbosso.util.lang.OneDFunction
{
private final ScriptEngine lispEngine =
new ScriptEngineManager().getEngineByExtension("lisp");
private final java.lang.String uuid;
private final java.lang.String variable;
Function(java.lang.String f, java.lang.String variable) throws
IOException, ScriptException
{
this.variable=variable;
java.io.FileInputStream fis=
new java.io.FileInputStream("ableiten.lisp");
java.io.InputStreamReader isr=new java.io.InputStreamReader(fis);
lispEngine.eval(isr);
isr.close();
fis.close();
uuid= UUID.randomUUID().toString();
System.out.println(lispEngine.eval(
"(setq "+uuid+" (doit '(D ("+f+") "+variable+")))"));
}
@Override
public double compute(double input)
{
try
{
double value = ((java.lang.Number) lispEngine.eval(
"(float (transform (subst " + input + " '" +
variable + " " + uuid + ") '(((* /) * /))))")).doubleValue();
return value;
}
catch(Exception exp)
{
throw new RuntimeException(exp);
}
}
}
Damit ist es möglich, bei der Instantiierung der Klasse die Funktion (in der erwarteten Form - siehe Video), sowie die Variable zu übergeben, nach der abgeleitet werden soll. Jeder Aufruf der Methode compute berechnet dann einen konkreten Wert der Ableitung dieser Funktion.
19.01.2025
Ich stieß neulich auf einen interessanten Artikel zum Thema reguläre Ausdrücke, der mich so faszinierte, dass ich zunächst einmal wieder mal in einen Kaninchenbau abstürzte und als ich wieder daraus emportauchte ernsthaft überlegte, einen weiteren Ausflug in Gebiete zu machen, die ich zuvor nie ernsthaft in Betracht gezogen hätte:
Ticketsysteme sind lebende Wesen
29.03.2020
Hier zunächst wieder eine Triggerwarnung: Dieser Artikel wird meine Meinung abbilden. es kann sein, dass sie dem einen oder anderen nicht gefällt - das ist mir aber egal. Und wenn hier irgendwelche Schneeflocken mitlesen, dann sind die selber schuld.
Weiterlesen...Android Basteln C und C++ Chaos Datenbanken Docker dWb+ ESP Wifi Garten Geo Go GUI Gui Hardware Java Jupyter JupyterBinder Komponenten Links Linux Markdown Markup Music Numerik OpenSource PKI-X.509-CA Präsentationen Python QBrowser Rants Raspi Revisited Security Software-Test sQLshell TeleGrafana Verschiedenes Video Virtualisierung Windows Upcoming...
Ich berichtete hier bereits über Experimente mit dem Clifford-Attractor, allerdings war ich noch Experimente unter geringfügig geänderten Parametern schuldig...
WeiterlesenEs wurde wieder einmal Zeit für ein neues Feature in meinem Static Site Generator mittels dessen ich ja auch meine Heimatseite im Zwischennetz gestalte und verwalte...
WeiterlesenEs kamen mehrere Faktoren zusammen: die Tatsache, dass ich nicht mehr ganz so kürzlich die 50 überschritten habe hatte ebenso darauf Einfluss wie das heutige trübe Wetter und auch der Fakt, dass ich bereits beinahe alle Wochenendpflichten erledigt habe. Der letzte Stein des Anstoßes war dann aber, dass sich heute zum 125. Mal der Geburtstag von Erich Fromm jährt.
WeiterlesenManche nennen es Blog, manche Web-Seite - ich schreibe hier hin und wieder über meine Erlebnisse, Rückschläge und Erleuchtungen bei meinen Hobbies.
Wer daran teilhaben und eventuell sogar davon profitieren möchte, muss damit leben, daß ich hin und wieder kleine Ausflüge in Bereiche mache, die nichts mit IT, Administration oder Softwareentwicklung zu tun haben.
Ich wünsche allen Lesern viel Spaß und hin und wieder einen kleinen AHA!-Effekt...
PS: Meine öffentlichen Codeberg-Repositories findet man hier.