Wednesday, 7 January 2015

Maya- A DSL for math and numerical work

I feel awkward writing mathematical functions as s-exps

(defn quadratic-1 [a b c]
  (let [d (* 4 a c),
        D (Math/sqrt (- (* b b) d)),
        t (* 2 a), -b (- b)]
    [(/ (+ -b D) t) 
     (/ (- -b D) t)]))

Clojure's thread-first macro eases the pain a bit..

(defn quadratic-2 [a b c]
  (let [d (* 4 a c),
        D (-> (* b b) (- d) Math/sqrt),
        t (* 2 a), -b (- b)]
    [(-> -b (+ D) (/ t)) 
     (-> -b (- D) (/ t))]))

..but it's still pretty loaded with parens, and not very clear.

We need a math DSL that looks infixy and uses fewer parens.
Luckily, we can hack one for ourselves in no time.

~*~

Step 1- Thread mathematical expressions

(defmacro math->
  "(math-> 1 + 5 * 2 / 3) ;=> (-> 1 (+ 5) (* 2) (/ 3)) ;=> 4" 
  [exp & f-x-pairs]
  (if (even? (count f-x-pairs))
    `(-> ~exp 
       ~@(for [[f x] (partition 2 f-x-pairs)]
           (list f x)))
    (throw (Exception. "f-x-pairs should be even."))))

Step 2- Allow temporary bindings

(defmacro maya
  "(maya 1 + 5 :as six, six * 2 :as twelve, twelve / 3 * 2) ;=> 8"
  [& exprs]
  (let [[exp [_ ?as & next-exprs :as E]] (split-with #(not= :as %) exprs)]
    (if (empty? E)
      (cons `math-> exp)
      `(let [~?as (math-> ~@exp)]
         (maya ~@next-exprs)))))

Step 3- Profit?

(defn quadratic [a b c]
  (maya 4 * a * c :as d,
        b * b - d -> Math/sqrt :as D,
        2 * a :as t, (- b) :as -b,
        -b + D / t :as x1,
        -b - D / t :as x2,
    [x1 x2]))

~*~

Edit: Here's the original gist.

No comments:

Post a Comment