My first Clojure program (and blog post about it)

384 views
Skip to first unread message

kirby urner

unread,
Jul 30, 2015, 3:46:40 PM7/30/15
to Clojure
Greetings all. 

I'm new to Clojure (but not to programming) and wanted to document a first effort.
The blog post:  http://controlroom.blogspot.com/2015/07/ramping-up.html

===

(ns test-project.synmods)

(defn add-open
[edges]
(let [[a b c d e f] edges
[a2 b2 c2 d2 e2 f2] (map (fn [x] (* x x)) edges )]
(do
(reduce + [
(reduce * [f2 a2 b2])
(reduce * [d2 a2 c2])
(reduce * [a2 b2 e2])
(reduce * [c2 b2 d2])
(reduce * [e2 c2 a2])
(reduce * [f2 c2 b2])
(reduce * [e2 d2 a2])
(reduce * [b2 d2 f2])
(reduce * [b2 e2 f2])
(reduce * [d2 e2 c2])
(reduce * [a2 f2 e2])
(reduce * [d2 f2 c2])])
)))

(defn add-closed
[edges]
(let [[a b c d e f] edges
[a2 b2 c2 d2 e2 f2] (map (fn [x] (* x x)) edges )]
(do
(reduce + [
(reduce * [a2 b2 d2])
(reduce * [d2 e2 f2])
(reduce * [b2 c2 e2])
(reduce * [a2 c2 f2])])
)))

(defn add-opposite
[edges]
(let [[a b c d e f] edges
[a2 b2 c2 d2 e2 f2] (map (fn [x] (* x x)) edges )]
(do
(reduce + [
(reduce * [a2 e2 (+ a2 e2)])
(reduce * [b2 f2 (+ b2 f2)])
(reduce * [c2 d2 (+ c2 d2)])])
)))

(defn Volume
[edges]
(let [ open ( add-open edges)
closed ( add-closed edges)
opposite ( add-opposite edges)]
(Math/sqrt (* (- (- open closed) opposite) 0.5))))

(println (format "All edges D=1, Volume: %s" (Volume [1.0 1.0 1.0 1.0 1.0 1.0]) ))

; A Module
(def a 1.0)
(def EF (* a (/ (Math/sqrt 6.0) 12.0 )))
(def EC (* a (/ (Math/sqrt 6.0) 4.0 )))
(def ED (* a (/ (Math/sqrt 2.0) 4.0 )))
(def FC (* a (/ (Math/sqrt 3.0) 3.0 )))
(def CD (/ a 2.0) )
(def DF (* a (/ (Math/sqrt 3.0) 6.0 )))

(def Avol (Volume [EF EC ED FC CD DF]))
(println (format "Amod volume: %s" Avol))

; E Module
; Fig. 986.411A T & E Module
; http://www.rwgrayprojects.com/synergetics/s09/figs/f86411a.html

(def D 1.0)
(def R (/ D 2.0))
(def h R)

(def OC h)
(def OA (* h (Math/sqrt (/ (- 5.0 (Math/sqrt 5.0)) 2.0))) )
(def OB (* h (Math/sqrt (/ (- 9.0 (* 3 (Math/sqrt 5.0))) 2.0))))
(def CA (* (/ h 2.0) (- (Math/sqrt 5.0) 1.0)))
(def AB (* h (Math/sqrt (- 5.0 (* 2.0 (Math/sqrt 5.0))))))
(def BC (* (/ h 2.0) (- 3.0 (Math/sqrt 5.0))))

(def Evol (Volume [OC OA OB CA AB BC]))
(println (format "Emod volume: %s" Evol))

; S Module
; Fig. 988.13A S Quanta Module Edge Lengths
; http://www.rwgrayprojects.com/synergetics/s09/figs/f8813a.html

(def a D)
(def FG (* (/ a 2.0) (* (Math/sqrt 3.0) (Math/sqrt (- 7.0 (* 3.0 ( Math/sqrt 5.0))))) ) )
(def FE (* a (Math/sqrt (- 7.0 (* 3.0 (Math/sqrt 5))))))
(def FH (* (/ a 2.0) (- (Math/sqrt 5.0) 1.0)))
(def GE (* (/ a 2.0) (Math/sqrt (- 7.0 (* 3.0 ( Math/sqrt 5))))))
(def EH (* (/ a 2.0) (- 3.0 (Math/sqrt 5.0))))
(def HG GE)

(def Svol (Volume [FG FE FH GE EH HG]))
(println (format "Smod volume: %s" Svol))

(println (format "sFactor: %s" (/ Svol Evol)))

James Reeves

unread,
Jul 30, 2015, 5:51:12 PM7/30/15
to clo...@googlegroups.com
One quick suggestion is that arithmetic operations in Clojure frequently take multiple arguments. So:

    (reduce + [1 2 3])

Is equivalent to:

    (+ 1 2 3)

In terms of style, variables are typically lower-case in Clojure except when referring to a class, interface or protocol.

- James

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Mark Engelberg

unread,
Jul 30, 2015, 6:17:13 PM7/30/15
to clojure
The do's are unnecessary.

Mark Engelberg

unread,
Jul 30, 2015, 6:21:41 PM7/30/15
to clojure
(println (format ...)) can be rewritten as (printf ...) if you add a \n to your string.

A large chunk of your computations after the definitions appear to be global definitions and print messages to achieve some sort of unit testing.  I encourage you to refactor this using clojure.test so you can get a feel for how unit testing is generally done in Clojure.  Those definitions could then be made local, and the printing is probably unnecessary.

Mark Engelberg

unread,
Jul 30, 2015, 6:54:02 PM7/30/15
to clojure
Given that your functions expect a list of exactly 6 inputs, I'd be inclined to write these as functions that take 6 inputs, rather than a list.  That way you get a meaningful error if they pass the wrong number of inputs.

If you do prefer to keep them as-is, you can also shorten the code by a line by destructuring directly in the input:
(defn f [[a b c d e f :as edges]] ...)

kirby urner

unread,
Jul 30, 2015, 7:36:57 PM7/30/15
to Clojure, mark.en...@gmail.com

Excellent feedback so far, I thank experienced Clojure programmers for giving me tips. 

I may post a next version after incorporating some of this advice.

Yes, I have much to learn!

Kirby


kirby urner

unread,
Jul 30, 2015, 7:59:27 PM7/30/15
to Clojure, kirby...@gmail.com

Thanks to excellent feedback, I now realize my code was overly verbose, a common phenomenon among beginners in many a computer language.

As an example, the newer version replaces this:


===

(ns test-project.synmods)

(defn add-open
[edges]
(let [[a b c d e f] edges
[a2 b2 c2 d2 e2 f2] (map (fn [x] (* x x)) edges )]
(do
(reduce + [
(reduce * [f2 a2 b2])
(reduce * [d2 a2 c2])
(reduce * [a2 b2 e2])
(reduce * [c2 b2 d2])
(reduce * [e2 c2 a2])
(reduce * [f2 c2 b2])
(reduce * [e2 d2 a2])
(reduce * [b2 d2 f2])
(reduce * [b2 e2 f2])
(reduce * [d2 e2 c2])
(reduce * [a2 f2 e2])
(reduce * [d2 f2 c2])])
)))



... with this:



(defn add-open
[edges]
(let [[a b c d e f] edges
[a2 b2 c2 d2 e2 f2] (map (fn [x] (* x x)) edges )]
    (+ (* f2 a2 b2)
(* d2 a2 c2)
(* a2 b2 e2)
(* c2 b2 d2)
(* e2 c2 a2)
(* f2 c2 b2)
(* e2 d2 a2)
(* b2 d2 f2)
(* b2 e2 f2)
(* d2 e2 c2)
(* a2 f2 e2)
(* d2 f2 c2))
))

Much simpler! Thank you. I'll look into testing features.

In my Python version of the above, I do in fact, invoke the unittest framework.

Kirby


Leif

unread,
Jul 30, 2015, 9:14:05 PM7/30/15
to Clojure, kirby...@gmail.com
This still seems very verbose to me.  I think it is because the definition of "open," "opposite," and "closed" are implicit in the great big blocks of arithmetic you are doing.  I think a useful exercise would be to define edges in terms of points, and maybe faces in terms of edges and an `face?` function.  Then define different properties like `open?` in terms of functions on edges.

E.g. if you define a point as a keyword, and an edge as a set of 2 points:

(def o :O)
(def a :A)
(def oa #{o a})
;; ...etc...
(defn connected? [edge-1 edge-2]
  (= 1 (count (set/intersection edge-1 edge-1))))
(defn open? [edge-1 edge-2 edge-3] ...)

Then I think the below function will eventually become something like

(->> edges
     open-triads
     ;; could give inner fn a name, too
     (map (fn [triad] (product (map edge-length triad))))
     sum)


The code as a whole will be more verbose, but it will be a verbosity that has evident meaning.

Happy Clojuring,
Leif

Amith George

unread,
Jul 30, 2015, 9:33:07 PM7/30/15
to Clojure, kirby...@gmail.com

Hi,

From a cursory glance, I didn't really understand the domain, so the function names I used in my rewrite might seem silly. But I wanted to illustrate that there is a lot of repetition in your code. Also discrete functions (with proper names) can make the code easier to grok. 

https://gist.github.com/amithgeorge/15b1cb607c32d39b70c7

;; instead of relying on the caller to pass the edges in the right order,
;; we accept a map and process the keys in the correct order

(defn- squared-edge-values
 
[edges]
 
(->> [:oc :oa :ob :ca :ab :bc]
       
(map #(edges %1))
       
(map #(* %1 %1))))

;; all operations on the edges are performed on the squared values.
;; the squared values are guaranteed to be in the correct order.

;; this separates out the logic specific to opposite edges and can be tested separately
(defn- opposite-edge-values
 
[edges]
 
(let [[a2 b2 c2 d2 e2 f2] (squared-edge-values edges)]
   
[[a2 e2 (+ a2 e2)]

     
[b2 f2 (+ b2 f2)]

     
[c2 d2 (+ c2 d2)]]))

(defn- closed-edge-values
 
[edges]
 
(let [[a2 b2 c2 d2 e2 f2] (squared-edge-values edges)]
   
[[a2 b2 d2]
     
[d2 e2 f2]
     
[b2 c2 e2]
     
[a2 c2 f2]]))

(defn- open-edge-values
 
[edges]
 
;; implement later
 
[[0 0 0]])

(defn- compute-edge-values
 
[edge-fn edges]
 
(->> edges
       
(edge-fn)
       
(map (fn [[x y z]] (* x y z)))
       
(reduce +)))

(def ^:private add-opposite (partial compute-edge-values opposite-edge-values))
(def ^:private add-closed (partial compute-edge-values closed-edge-values))
(def ^:private add-open (partial compute-edge-values open-edge-values))

;; this is the public entry point.
;; the precondition ensures all the edges are present in the input

(defn volume
 
[{:keys [oa ob oc ab bc ca] :as edges}]
 
{:pre [(every? #(number? %1) [oa ob oc ab bc ca])]}
 
(let [opposite (add-opposite edges)
        closed
(add-closed edges)
        open
(add-open edges)]

   
(Math/sqrt (* (- (- open closed) opposite) 0.5))))


;; sample input
(defn a-mod-input
 
[]
 
(let [a 1.0]
   
{:oc (* a (/ (Math/sqrt 6.0) 12.0))
     
:oa (* a (/ (Math/sqrt 6.0) 4.0))
     
:ob (* a (/ (Math/sqrt 2.0) 4.0))
     
:ca (* a (/ (Math/sqrt 3.0) 3.0))
     
:ab (/ a 2.0)
     :bc (* a (/
(Math/sqrt 3.0) 6.0))}))

(defn e-mod-input
 
[]
 
(let [d 1.0
        r
(/ d 2.0)
        h r
]
   
{:oc h
     
:oa 1
     
:ob 1
     
:ca 1
     
:ab 1
     
:bc 1}))

(def a-mod-volume (volume (a-mod-input)))
(def e-mod-volume (volume (e-mod-input)))

Again, the idea is to have small functions that reflect the steps you would normally do, as required by the domain or algo you are implementing. 

kirby urner

unread,
Jul 30, 2015, 9:38:11 PM7/30/15
to Leif, Clojure
On Thu, Jul 30, 2015 at 6:14 PM, Leif <leif.p...@gmail.com> wrote:
This still seems very verbose to me.  I think it is because the definition of "open," "opposite," and "closed" are implicit in the great big blocks of arithmetic you are doing.  I think a useful exercise would be to define edges in terms of points, and maybe faces in terms of edges and an `face?` function.  Then define different properties like `open?` in terms of functions on edges.


Yes, in the ecosystem I'm coming from, edges are indeed defined by two points, points being likewise vectors (in the coordinate system sense). 

An Edge is defined by two Vectors e.g.  (def a (length (Edge v0 v1))).  For vectors, I sometimes use non-XYZ 4-tuples called Quadrays (check Wikipedia). 

A Polyhedron is defined as a set of faces, going around clockwise or counter, giving all the vectors to that face.  Vectors are "tail originating" by definition i.e. their tails are all anchored at the origin.

Here's my Clojure code after some recent refactoring, minus the redundant bits I've not changed yet. 

I was 2nd powering all the six edge lengths each time inside the three sub-functions (components of Volume) whereas really Volume should just do that once. 

That's all my let form does anymore:

(ns test-project.synmods)

(defn add-open

[a2 b2 c2 d2 e2 f2]
  (+ (* f2 a2 b2)
(* d2 a2 c2)
(* a2 b2 e2)
(* c2 b2 d2)
(* e2 c2 a2)
(* f2 c2 b2)
(* e2 d2 a2)
(* b2 d2 f2)
(* b2 e2 f2)
(* d2 e2 c2)
(* a2 f2 e2)
(* d2 f2 c2)))

(defn add-closed

[a2 b2 c2 d2 e2 f2]
  (+ (* a2 b2 d2)(* d2 e2 f2)(* b2 c2 e2)(* a2 c2 f2)))

(defn add-opposite

[a2 b2 c2 d2 e2 f2]
   (+ (* a2 e2 (+ a2 e2)) (* b2 f2 (+ b2 f2))(* c2 d2 (+ c2 d2))))

(defn Volume
[edges]
(let [[a2 b2 c2 d2 e2 f2] (map (fn [x] (* x x)) edges )]
(Math/sqrt (*
(-
(- (add-open a2 b2 c2 d2 e2 f2)(add-closed a2 b2 c2 d2 e2 f2) )
(add-opposite a2 b2 c2 d2 e2 f2))

0.5))))

(println (format "All edges D=1, Volume: %s" (Volume [1.0 1.0 1.0 1.0 1.0 1.0]) ))

; A Module

kirby urner

unread,
Jul 30, 2015, 9:45:18 PM7/30/15
to Amith George, Clojure
On Thu, Jul 30, 2015 at 6:33 PM, Amith George <strid...@gmail.com> wrote:

Hi,

From a cursory glance, I didn't really understand the domain, so the function names I used in my rewrite might seem silly. But I wanted to illustrate that there is a lot of repetition in your code. Also discrete functions (with proper names) can make the code easier to grok. 

https://gist.github.com/amithgeorge/15b1cb607c32d39b70c7


Excellent!  Lots to study here.  I'm still picking up some of the basic grammar.  Very helpful.

Thanks for taking the time to show me some ropes!

Kirby



kirby urner

unread,
Aug 6, 2015, 1:39:08 PM8/6/15
to Clojure


On Thursday, July 30, 2015 at 6:38:11 PM UTC-7, kirby urner wrote:


On Thu, Jul 30, 2015 at 6:14 PM, Leif wrote:
This still seems very verbose to me.  I think it is because the definition of "open," "opposite," and "closed" are implicit in the great big blocks of arithmetic you are doing.  I think a useful exercise would be to define edges in terms of points, and maybe faces in terms of edges and an `face?` function.  Then define different properties like `open?` in terms of functions on edges.


Yes, in the ecosystem I'm coming from, edges are indeed defined by two points, points being likewise vectors (in the coordinate system sense). 

An Edge is defined by two Vectors e.g.  (def a (length (Edge v0 v1))).  For vectors, I sometimes use non-XYZ 4-tuples called Quadrays (check Wikipedia). 


Since last week I've been studying defrecord and defprotocol.  

Again, I expect this is overly verbose, any feedback welcome.  

I posted a link to the Python version I'm working from at the end.

"""
(cl) K. Urner, MIT License 2015
Python -> Java -> Clojure curriculum
2D + 3D Graphics: Martian Math
Topic: Quadrays
http://www.grunch.net/synergetics/quadintro.html
https://en.wikipedia.org/wiki/Quadray_coordinates

Asynchronous Learning Engine (Open Source project)
http://controlroom.blogspot.com/2015/08/asynchronous-learning-engine-ale.html
"""

(ns test-project.quadrays)

(
defprotocol Ops
(
q-add [this other])
(
q-sub [this other])
(
q-len [this])
(
q-neg [this])
(
q-norm [this]))

(
defrecord Quadray [OA OB OC OD]
Ops
(
q-norm [this]
(
let [[a b c d] [(:OA this) (:OB this) (:OC this) (:OD this)]
[x] [(
min a b c d)]]
(
Quadray. (- a x) (- b x) (- c x) (- d x))))

(
q-neg [this] (q-norm (Quadray. (- 0 (:OA this))
(
- 0 (:OB this) )
(
- 0 (:OC this))
(
- 0 (:OD this)))))

(
q-len [this] (let [[a b c d] [(:OA this) (:OB this) (:OC this) (:OD this)]
[k] [(
/ (+ a b c d) 4)]
[t0 t1 t2 t3] [(
- a k) (- b k) (- c k) (- d k)]]
(
* (Math/sqrt 2.0)
(
Math/sqrt (+ (* t0 t0) (* t1 t1) (* t2 t2) (* t3 t3))))))

(
q-add [this other]
(
q-norm (Quadray. (+ (:OA this) (:OA other))
(
+ (:OB this) (:OB other))
(
+ (:OC this) (:OC other))
(
+ (:OD this) (:OD other)))))

(
q-sub [this other] (q-add this (q-neg other))))

(
def v0 (Quadray. 1 0 0 0))
(
def v1 (Quadray. 0 1 0 0))
(
println str (q-sub v0 v1))
(
println str (q-add v0 v1))
(
println str (q-neg v1))
(
println str (q-len v1))


Link to Python version:   https://mail.python.org/pipermail/edu-sig/2015-August/011291.html

Kirby


Mark Engelberg

unread,
Aug 6, 2015, 4:06:42 PM8/6/15
to clojure
On Thu, Aug 6, 2015 at 10:39 AM, kirby urner <kirby...@gmail.com> wrote:
(ns test-project.quadrays)

(
defprotocol Ops
(
q-add [this other])
(
q-sub [this other])
(
q-len [this])
(
q-neg [this])
(
q-norm [this]))

(
defrecord Quadray [OA OB OC OD]
Ops
(
q-norm [this]
(
let [[a b c d] [(:OA this) (:OB this) (:OC this) (:OD this)]
[x] [(
min a b c d)]]
(
Quadray. (- a x) (- b x) (- c x) (- d x))))

(
q-neg [this] (q-norm (Quadray. (- 0 (:OA this))
(
- 0 (:OB this) )
(
- 0 (:OC this))
(
- 0 (:OD this)))))

(
q-len [this] (let [[a b c d] [(:OA this) (:OB this) (:OC this) (:OD this)]
[k] [(
/ (+ a b c d) 4)]
[t0 t1 t2 t3] [(
- a k) (- b k) (- c k) (- d k)]]
(
* (Math/sqrt 2.0)
(
Math/sqrt (+ (* t0 t0) (* t1 t1) (* t2 t2) (* t3 t3))))))

Inside the implementation of a record definition, you can refer to all the parts of a record more simply.
(:OA this) can just be OA and so on.
That means the `let` is also unnecessary here.

Note that `Quadray.` could be replaced by `->Quadray`.  Both forms are acceptable, but the latter is a true function and can be passed to higher-order functions.

Also, note that `-` can be used as a unary function.  `(- 0 x)` can just be replaced with `(- x)`.

The larger question here is whether records/protocols are really the right tool for the job here.  Clojure gives you a lot of ways to do a given thing; newcomers are often attracted to records/protocols as one of the more familiar OO-like constructs, but that doesn't mean they are always the right fit.

Protocols are valuable for polymorphism, i.e., implementing the same functions over several different concrete representations.  So unless you plan to have multiple representations of Quadrays, there's no real reason to use protocols.  And based on your naming scheme, there's absolutely no indication that you have any intention of having these functions ever apply to other data structures.  Remember, Clojure eschews encapsulation (no reason for it, since everything is immutable), therefore, a function can get exactly the same access to the so-called "privileged bits".  All your protocol definitions as written above could simply be regular functions.

A related choice is whether to implement Quadray as a record, or just a function that builds a map, i.e.,
(defn quadray [oa ob oc od] {:oa oa :ob ob :oc oc :od od})
or if you need a "type" to contrast it with other structures
(defn quadray [oa ob oc od] {:tag :quadray :oa oa :ob ob :oc oc :od od})

The tradeoffs are subtle.  Records give you improved performance when accessing the "member variables", the ability to type hint the members (which you arguably want to do here, since all the members are doubles, and that typing the members could potentially improve your arithmetic performance).  But records currently have a "performance bug" in 1.7 that makes them slower than plain maps when used as keys in other maps or sets, because they don't cache their hash values the way regular maps do.  Records are almost interchangeable with maps, but there are a few ways they are not -- for example, records by default can't be used as a function of their keys the way maps can.  Using plain maps also gives you a simpler serialization story.

So, there's nothing really wrong with using records/protocols, but be aware that there are other choices with other strengths/weaknesses.

Also, don't forget to explore the test framework versus global defs and print statements.

kirby urner

unread,
Aug 6, 2015, 4:52:45 PM8/6/15
to Clojure

Also, don't forget to explore the test framework versus global defs and print statements.

--

 
Excellent feedback Mark, thank you so much!  

This is exactly what I was hoping for.  I will be simplifying said code accordingly and posting it back.  

A great way to learn!

I do think I might add the same protocol to a regular XYZ Vector defrecord type, but then that would mean changing the names maybe to v-add, v-sub etc. v for vector.  

I have intra-conversion twixt these exotic Quadray and XYZ coordinates, already in Python.

Plus with vectors ** we expect:  angles between any two; polar coords; scalar grow-shrink; something like dot and cross product

So I could flesh out the one protocol (inherits from Java interface I understand) and show how both vector notations share the same API.  That would seem an appropriate use of the syntax maybe?

Related literature:  http://www.dividedspheres.com/

Kirby


** in this implementation a "vector" is always tail-originating at some place we locally define as "the origin", so any to vectors will have the same origination point, meaning a "central angle" is implied.  For polyhedrons around the origin we build with Edges, each defined with two vectors; and with Faces (sets of Edges).  A similar notion of vector (vs. line segment) is found in 






You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/RvHQPfBKJuM/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.

Mark Engelberg

unread,
Aug 6, 2015, 5:36:02 PM8/6/15
to clojure
Right, so if you use protocols, you'd just have one name:
add, sub, norm, len, neg
and use for both vectors and quadrays, rather than a v-add / q-add, etc.

If there are things that apply only to vectors and not quadrays, you could make those their own protocol (or individual functions).

And of course, multimethods are yet another way to achieve polymorphism in Clojure.  Multimethods would really shine if you want to get into complicated mixtures of adding vectors and quadrays, etc., or dispatch on something other than the type (e.g., maybe pre-normalized quadrays dispatch to something more efficient).  Protocols (like traditional OO) can only dispatch on the type of the first input.

One other code simplification tip: you can destructure in the parameter, e.g., if you were to rewrite q-neg to be a plain function as opposed to a protocol, you could still get convenient access to the member variables by writing it like this:
(defn q-neg [{:keys [OA OB OC OD]}] (->Quadray (- OA)(- OB)(- OC) (- OD)))

http://www.john2x.com/blog/clojure-destructuring/



--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.

kirby urner

unread,
Aug 8, 2015, 3:39:22 PM8/8/15
to Clojure
On Thu, Aug 6, 2015 at 2:35 PM, Mark Engelberg <mark.en...@gmail.com> wrote:
Right, so if you use protocols, you'd just have one name:
add, sub, norm, len, neg
and use for both vectors and quadrays, rather than a v-add / q-add, etc.


I have done this vis-a-vis Quadrays and XYZrays as both are types of vector with essentially the same API.

(defprotocol VectorOps
  (add [this other])
  (sub [this other])
  (len [this])
  (neg [this])
  (norm [this]))


https://github.com/4dsolutions/synmods/blob/master/qrays.clj
 
If there are things that apply only to vectors and not quadrays, you could make those their own protocol (or individual functions).


However I didn't take norm out of the interface though, even though it really only applies to one of the types. 

I had XYZray just return "this".  Bad idea?

(defrecord XYZray [OX OY OZ]
   VectorOps
   (norm [this] (this))
   (neg [this](XYZray. (- OX) (- OY) (- OZ)))
   (len [this](Math/sqrt (+ (* OX OX)(* OY OY)(* OZ OZ))))
   (add [this other] (XYZray. (+ OX (:OX other))
                     (+ OY (:OY other))
                     (+ OZ (:OZ other))))
   (sub [this other](add this (neg other))))

 
And of course, multimethods are yet another way to achieve polymorphism in Clojure.  Multimethods would really shine if you want to get into complicated mixtures of adding vectors and quadrays, etc., or dispatch on something other than the type (e.g., maybe pre-normalized quadrays dispatch to something more efficient).  Protocols (like traditional OO) can only dispatch on the type of the first input.

That may be next for me. 

If I initialize some plain vanilla Vector with 3 coordinates, it could recognize that as XYZ and output a vector of that type, or of either type given they're inter-convertible.
 

One other code simplification tip: you can destructure in the parameter, e.g., if you were to rewrite q-neg to be a plain function as opposed to a protocol, you could still get convenient access to the member variables by writing it like this:
(defn q-neg [{:keys [OA OB OC OD]}] (->Quadray (- OA)(- OB)(- OC) (- OD)))

http://www.john2x.com/blog/clojure-destructuring/



Before:

(defn qray-to-xyzray
  [qray] 
  (let [[a] [(:OA qray)]
        [b] [(:OB qray)]
        [c] [(:OC qray)]
        [d] [(:OD qray)]
        [x] [(* (/ 1 (Math/sqrt 2))(+ (- (- a b) c) d))]
        [y] [(* (/ 1 (Math/sqrt 2))(- (+ (- a b) c) d))]
        [z] [(* (/ 1 (Math/sqrt 2))(- (- (+ a b) c) d))]]     
    (XYZray. x y z)))



After:

(I bet I could use destructuring to shorten the above quite a bit)
 


On Thu, Aug 6, 2015 at 1:52 PM, kirby urner <kirby...@gmail.com> wrote:

Also, don't forget to explore the test framework versus global defs and print statements.

--


Right, still in my queue to get to that.  Very helpful.

Kirby



Mark Engelberg

unread,
Aug 10, 2015, 4:11:53 PM8/10/15
to clojure
On Sat, Aug 8, 2015 at 12:39 PM, kirby urner <kirby...@gmail.com> wrote:

(defrecord XYZray [OX OY OZ]
   VectorOps
   (norm [this] (this))

Should be (norm [this] this) not (norm [this] (this)), because the latter will try to invoke `this` as a function.
 
(defn qray-to-xyzray
  [qray] 
  (let [[a] [(:OA qray)]
        [b] [(:OB qray)]
        [c] [(:OC qray)]
        [d] [(:OD qray)]
        [x] [(* (/ 1 (Math/sqrt 2))(+ (- (- a b) c) d))]
        [y] [(* (/ 1 (Math/sqrt 2))(- (+ (- a b) c) d))]
        [z] [(* (/ 1 (Math/sqrt 2))(- (- (+ a b) c) d))]]     
    (XYZray. x y z)))



You have excessive, unnecessary brackets in your let clause.  Should look like this:

(defn qray-to-xyzray
  [qray] 
  (let [a (:OA qray)
        b (:OB qray)
        etc....

Your way "works" because basically you are building two extra vectors which are implicitly getting destructured, but that's syntactically noisy and also a lot of extra computational work that is unneeded.

kirby urner

unread,
Aug 10, 2015, 4:27:29 PM8/10/15
to Clojure
Your way "works" because basically you are building two extra vectors which are implicitly getting destructured, but that's syntactically noisy and also a lot of extra computational work that is unneeded.

--

Thanks!

Kirby





Reply all
Reply to author
Forward
0 new messages