scicloj / wolframite

An interface between Clojure and Wolfram Language (the language of Mathematica)
Eclipse Public License 2.0
46 stars 2 forks source link

Make it possible to compose expressions from named parts (vars) and make +/-/... work just as well as any wolfram fn #45

Closed holyjak closed 3 days ago

holyjak commented 2 months ago

Intro

We want to break complex expressions into multiple parts, with (def subexpr1 ...) etc. and compose them back and evaluate later. Currently, that requires to quote the subexpressions so that they are not evaluated (such as (* 2 phi)) and possibly unquote to include a nested sub-expression. That is rather inconvenient, find a better way.

~May be perhaps also used to define custom wolfram fns, e.g. (**2 x) that would expand into (Power x 2).~ Already possible: use (wl/eval '(..= MyPower2 (fn [x] (Power x 2)))) to define a wolfram-side fn MyPower2, then use it as (wl/eval '(MyPower2 3)) ;=> 9

I guess this is also a problem with (wl/eval (* 2 (Power 2 2))) as Clojure will evaluate its core * (which is not our magical intern.wolfram-fn and does does not return itself as data).

Analysis

We have two needs:

  1. Save arbitrary Wolfram expression for later evaluation, possibly embedding in another W. expr.
  2. Not evaluating sub-expressions that are valid Clojure function calls (iff they contain Wolfram sub-sub-exoressions) - see below

1. Save arbitrary Wolfram expression for later

I realize now that this is actually the same as 2, as the following already works:

(def two (w/Plus 1 1))
(wl/eval (w/Plus two two))

The problem is with expressions that are Clojure fns (only???).

2. Not evaluating Clojuresque sub-expressions

NOTE: I assume (wl/load-all-symbols 'w)

This would be fine: (w/Plus 1 (+ 2 3)) - the Clojure + would run at once, and Wolfram would be asked to eval Plus[1,5]. This is fine and may be desirable, perhaps? But not necessary to support, I imagine. We could require Wolframite input to be valid Wolfram-only expressions, without any Clojure-only parts.

Notice that '(Plus 1 (+ 2 3)) would result in evaluating 'Plus[2,Plus[2,3]]' since we map the symbol + to the Wolfram fn Plus. This makes it easier to write expression, with the familiar operators, but may mess up the evaluation when we instead of passing an expression end up trying to eval it in clojure land.

This would fail: (w/Plus 1 (+ 2 (w/Plus 3 4))) b/c Clojure would try to sum 2 and a "function".

NOTE: We have another likely related problem: this should work (wl/eval (list '< 2 w/Pi)) but fails to do what's expected b/c it turns w/Pi into "wolframite.impl.intern$wolfram_fn$wolf_fn__2531@4271d821". (b/c wl/eval fails to notice this should have been turned back into a symbol). Namely, < isn't our magical "wolfram-fn" and thus it does not walk its children to turn them back into symbols, as e.g. w/Plus would

Possible solutions

  1. Use the proposed defer: (w/Plus 1 (wl/defer (+ 2 (w/Plus 3 4))))
  2. Don't use operators that are also Clojure fns - have our own ns, perhaps w, with custom versions of +, *, etc., which behave in the same way as loaded syms such as w/Plus. (Note: See also https://github.com/scicloj/wolframite/issues/33#issuecomment-1961315334)
  3. ???

Decision

Go with nr. 2,

  1. Don't use operators that are also Clojure fns - have our own ns, perhaps w, with custom versions of +, *, etc., which behave in the same way as loaded syms such as w/Plus (i.e. they are produced by wolframite.impl.intern/wolfram-fn)