Search code examples
clojurequil

How to bind functions in let evaluating them later


I'm learning Clojure to use it with Quil to make generative art and I would like to try to understand a bit more about let function and reader macro probably.

I tried reading about reader macros from the docs but I couldn't find a clear answer to it.

I currently have this piece of code:

(let [dabs (take 10 (repeatedly #(make-watercolor 
                                   (mkpt 
                                     (randomNormal 0.1 0.9) 
                                     (randomNormal 0.1 0.9))
                                   (w (randomNormal 0.4 0.7)))))]
    (draw-multiple-watercolor dabs 3))

Which is pretty ugly and not really readable. I would like to slim down the repeated function splitting it into smaller pieces, but since those pieces will be evaluated multiple times and has random inside them I cannot store their result inside a variable and use that, instead I need to evaluate those when needed.

My question is: is there a way to do something like this

(let [randCoord (randomNormal 0.1 0.9) ;This..
      randPoint (mkpt randCoord randCoord) ;..and this doesn't should be evaluated here, but on the repeatedly function calls
      dabs (take 10 (repeatedly #(make-watercolor 
                                   randPoint ;Evaluation here
                                   (w (randomNormal 0.4 0.7)))))]
    (draw-multiple-watercolor dabs 3))

Solution

  • One option is to use the same approach you've used with the function you pass to repeatedly: just wrap the expressions in functions that take no arguments (or only the args you want to change), then they'll be evaluated each time you invoke the function.

    (let [rand-coord (fn [] (randomNormal 0.1 0.9)) ;; or #(randomNormal 0.1 0.9)
          make-dab #(make-watercolor (mkpt (rand-coord) (rand-coord))
                                     (w (randomNormal 0.4 0.7)))
          dabs (take 10 (repeatedly make-dab))]
      (draw-multiple-watercolor dabs 3))
    

    Also take a look at letfn for defining functions in a non-top-level/namespace scope.

    You might also find threading macros like ->, ->>, as->, some->, cond->, etc. can make some code more readable. For example, if you changed draw-multiple-watercolor to take its sequence of dabs last (which is fairly common practice for functions that operate on sequences in Clojure) then you could do something like this:

    (let [rand-coord (fn [] (randomNormal 0.1 0.9)) ;; or #(randomNormal 0.1 0.9)
          make-dab #(make-watercolor (mkpt (rand-coord) (rand-coord))
                                     (w (randomNormal 0.4 0.7)))]
      (->> make-dab
           (repeatedly 10) ;; note: repeatedly can take a # of iterations
           (draw-multiple-watercolor 3)))