Newbie Questions on Clojure


#1

How do I use vectors as both inputs and outputs for functions? The following script gives me the error message:

“exception in thread “main” clojure.lang.ArityException: Wrong number of args (0) passed to: PersistentVector, compiling:(/tmp/form-65768568449210.clj:1:73)”

and then at butch of unidentifiable location (“at”) outputs. Heres the script being executed as “lein run 1” (btw, does the initially called function (e.g., main) need to have some input? Could I leave it blank somehow and just run “lein run”?).

(ns acyclicScript.core)

(defn foo
"I don’t do a whole lot."
[x]
(println x “Hello, World!”))
(defn acyclic
"I don’t do a whole lot."
[listInput a b]
(loop [i 0 resultVector []]
(when (>= i (count(listInput)))
resultVector
)
(recur (+ i 1) resultVector)
)
)

(defn -main
"ye"
[yeezy]
(let [listInput [] listInput (conj listInput [{:x 3.4, :y 2.9, :z 0}, {:x 4.2, :y 3.5, :z 0}, {:x 3.3, :y 4.7, :z 0}, {:x 1.2, :y 1.9, :z 0}])] (print (acyclic listInput 1 2)))
)


#2

Hi Alex!

Great question. I decided to answer your question as a video!

https://purelyfunctional-mentoring.dpdcart.com/subscriber/post?id=1262

Rock on!
Eric


#3

Thank you, ericnormand, for making another great video!
I managed to get it working. You were right, the count function had a list as function which of course was an error. After fixing that error, debugging my code became much easier.
But although that error was fixed, I was still getting more. During my debugging process I noticed that both statements within and outside the consequent of my “when/if” were being executed, and thus resulting in an infinite loop due to the recur always being executed. To fix this I had to also wrap my “if/when” function around the recur function so that if the “if” condition held true, the recur would not be called. In most imperative programming languages (Java, C, etc…) the rest of the function wrapping the “return” would be skipped unlike Clojures approach were the rest of the algorithm within that function still gets executed.

Concerning being able to simply run “lein run” with no arguments, this was easily solved by removing the parameters in the mains input such as “[]”.

Here’s the fixed and cleaned up code. Enjoy!

(ns assignment2.core)

(defn foo
    "I don't do a whole lot."
    [x]
    (println x "Hello, World!")
 )
(defn acyclic
   "I don't do a whole lot."
   [listInput a b]
   (loop [i 0 resultVector []]
      (if (>= i (count listInput))
         (do (println "foo") resultVector
          )                      
         (do (println i) (recur (+ i 1) resultVector)
          )
       )
     )
   )


(defn -main
   "ye"
   []
  (let [listInput [] 
        listInput (conj listInput [:x 3.4, :y 2.9, :z 0],
                                          [:x 4.2, :y 3.5, :z 0],
                                          [:x 3.3, :y 4.7, :z 0], 
                                          [:x 1.2, :y 1.9, :z 0])]
  (print (acyclic listInput 1 2)))

#4

Nice video Eric!

I was thinking what you said about clojure.lang.IFn. Is it correct to say that (some) data structures are functions?

user> (#{42} 42)
42
user> ({:a 42} :a)
42
user> ([42] 0)
42
user> ('(42) 0)
ClassCastException clojure.lang.PersistentList cannot be cast to clojure.lang.IFn  user/eval9954 (form-init9050658716230962590.clj:55)

I understand they implement implicitly a “getter” on the data. Do you know if it is design this way for simplicity?

user> (get #{42} 42)
42
user> (get {:a 42} :a)
42
user> (get [42] 0)
42
user> (get '(42) 0)
nil

clojure.lang.PersistentList seems to be an exception to all this.


#5

Yep!

> (supers (type {}))
#{... clojure.lang.IFn ...}

> (supers (type (list)))
#{...} ;; no clojure.lang.IFn here!

Intuition: tables

A table is one way to imagine a function. (A table from arguments to values.)

  • A map is a table, from keys to values.

  • A vector is like a map, where the keys are all integers.

  • A set is like a map, where the keys are things possibly in the set.

Why lists aren’t functions

Unlike maps/vectors/sets, a list doesn’t return values in basically constant-time — if you want the 100th thing in a list, you have to run through 100 elements to get it. So it’s not a natural datastructure to treat as a table.

(Clojure is opinionated, and subtly nudges users to use a more appropriate datastructure. Sure, you could use (nth (list 1 2 3) 0); it’s just a wee bit more effort.)

More powers

> ({:a 1, :b nil} :c :not-found)
:not-found

#6

Great question, @Lud, and great answer, @tjg!!

I would call it convenience. I don’t typically use it in the function position in an expression (I don’t write ([1 2 3] 2)), but when you need to pass something to map, it’s nice to be able to use a hashmap as the function.

(def id->username {123 "eric" 234 "john" 567 "jane"})
(map id->username ids)

That’s when I use it. If you didn’t have that feature, you’d have to write:

(map #(get id->username %) ids)

Slightly less convenient. When you multiply it by all the times you might use a hashmap and all the places to use it, it turns into quite a lot. Plus it makes semantic sense. The only downside I’ve run into is the difficult error messages and other difficulty understanding what’s going on. Sometimes you use a hashmap as a hashmap, not a function, but you’re getting errors about arguments. Very weird.

Rock on!
Eric


#7

Thank you to both of you, It’s more clear now.


#8

Hello, most of my code has been changed. I’ve taking a new approach to it. But I am having a problem with my new code. How do I change a value in a vector in which I know the key associated with that value?

What I have as a vector is:
[{:paths [[:node 1, :cost 1], [:node 1, :cost 2]]},
{:paths [[:node 2, :cost 2], [:node 2, :cost 4]]}
{:paths [[:node 0, :cost 9], [:node 0, :cost 8]]}
{:pathLength nil}]
And I want the change the value of :pathLength


#9

Hi @AlexCreamer

Thanks for the question.

Let me see if I understand your data structure. You’ve got a vector of maps. Each map is either a map containing the key :paths or the key :pathLength. You want to change the value associated with one or all :pathLengths.

(defn change-pathLength [vm val]
  (mapv (fn [mp]
          (if (contains? mp :pathLength)
            (assoc mp :pathLength val)
            mp))
    vm))

I think that would work. You could pull :pathLength out into an argument and you’d have something more general, but without more context, I don’t know if that’s what you’d want. It’s a possibility.

I admit that this is kind of awkward, but this is how I’d manipulate the data structure you showed.

It seems like you’re learning a lot. Keep up whatever you’re doing!

Eric


#10

@ericnormand Does the modify the data of the referenced map? I assume not since data in Clojure is both immutable and persistent. That being the case, it probably “returns” a new vector; and if I wanted to use that vector throughout my function, then I would probably have to instantiate a new object holding that return value. The approach to instantiate this object could probably either be to use “let” or a new function.


#11

You’re right it does not modify the data. It returns a new vector with the same structure except the map with the :pathLength key is different (also not modified, it’s a new one).

You could use a let, like this:

(let [a1 the-vector
      a2 (transform1 a1)
      a3 (transform2 a2) ... etc

Where the-vector is your data and transform.. are your functions to make a copy with changes.

If you make sure that all the transform functions take the argument in the first position, you could do a thread-first:

(-> the-vector  
    transform1  
    transform2  
    (transform 3 arg1))  

#12

Hi,

Good news, I’m almost finished the above little project of mine.

But I’m having the problem of not being able to count the amount of values in one of my maps; in this particular case, the values in (:paths inputVector).

 (ns assignment2.core)

 (defn nodeValidation
 ""
 [inputVector resultVector a b]
(if (not(contains? resultVector a))
(cond
  (= b a)
    2
  :else
    1
)
0
)
)

(defn acyclic
""
[inputVector a b resultVector]
(loop [i 0 tempVector [:pathLength Double/POSITIVE_INFINITY]]
 (println "Entering with loop")
 (println i)
 (println tempVector)
 (if(not(< i (count( :paths (nth inputVector a))))
   (do (print("ending loop"))
   (conj (assoc (resultVector :pathLength) (+(((nth inputVector ((((nth inputVector a) :paths) i) :node))):pathLength) resultVector :pathLength)) tempVector))
   (let [evaluatingNode (((nth inputVector a):paths)i)]
   (let [validationVal (nodeValidation inputVector resultVector (evaluatingNode :node) b (evaluatingNode :cost))]
     (do (print "finished both let functions")
    (cond
     (= validationVal 2)
      (if (< (evaluatingNode :cost)) (tempVector :pathLength)
        (recur (+ i 1) evaluatingNode)
        )

   
     (= validationVal 1)
      (if (< ((acyclic inputVector (evaluatingNode :node) b resultVector):pathLength) (tempVector :pathLength))
        (recur (+ i 1) (acyclic inputVector evaluatingNode)) 
        )
      :else 
      (recur (+ i 1) tempVector)
     )
  )
  )
  )
 ) 
 )   
 )  

(defn -main
    "ye" 
    []
(let [inputVector []
    inputVector (conj inputVector [{:paths [[:node 1, :cost 1], [:node 2, :cost 2]]},
                              {:paths [[:node 2, :cost 1], [:node 3, :cost 2]]},
                              {:paths [[:node 0 :cost 1], [:node 1, :cost 2]]}])]

 (do(println "The following vector from left to right shows the path taken for the acyclic path:")
    (println (acyclic inputVector 0 3 []))
  )
 )
 )

#13

Hi @AlexCreamer,

Is this the line that you’re having trouble with?

(count (:paths (nth inputVector a)))

It looks like it should get the first map out of the vector (a is 0), take the value at :paths (which is a vector), and count it. The count should be 2. What is it doing?

Thanks
Eric


#14

Ok, I’ve read a little more and I think I see the problem.

(let [inputVector []
    inputVector (conj inputVector [{:paths [[:node 1, :cost 1], [:node 2, :cost 2]]},
                              {:paths [[:node 2, :cost 1], [:node 3, :cost 2]]},
                              {:paths [[:node 0 :cost 1], [:node 1, :cost 2]]}])]

What is the value of inputVector after these two lines?

Eric


#15

Good news, @ericnormand @AlexCreamer, I finally completed this little project of mine. I will post code on Github soon and post link for anyone who has been following to see.


#16

Hi, forgive me for the delay on providing the link to the final source code. Here it is: https://github.com/AlexCreamer/comp456 . It would be much appreciate if someone can evaluate it, suggest better naming/coding conventions, and suggest algorithm improvements. Thanks :).


#17

Hello? :slight_smile:


#18

Hey Alex!

Great job!

Let’s go through one bit at a time:

(defn nodeValidation 
  ""
  [inputVector previousPath evaluatingNode endNode]
  (if (not(contains? (:paths (nth previousPath 0)) evaluatingNode))  
    (cond
      (= endNode evaluatingNode)
        2 
      :else
        1
    )
    0
   )
 )

Comments:

  • Empty docstring is not worth much. Either fill it in or remove it (they’re optional).
  • (nth x 0) is probably best written (first x)
  • What is previousPath? Is that the right name?
  • Nested if and cond is kind of weird. You can probably rewrite it easily as one cond. Hint: make 0 be the first case.
  • What are these numbers that are returned? Are they codes with meaning? If so, you should use keywords to give them descriptive names.
  • It’s customary to close parens on the end of the line

My take:

(defn node-validation 
  [previous-path evaluating-node end-node]
  (cond 
    (contains? (:paths (first previous-path)) evaluating-node)
    :already-visited
    (= end-node evaluating-node)
    :end
    :else
    :potential))

Next!

(defn returnResults
  ""
  [currentNode tempLength tempPath previousPath]

  (vector (assoc
    (assoc
      (nth previousPath 0)
        :pathLength
        tempLength)
      :paths 
       (conj tempPath currentNode)))
  ) 

Comments:

  • Again, docstring.
  • Indentation is a little weird.
  • Nested assoc is unnecessary:
(assoc (nth previousPath 0)
  :pathLength tempLength
  :paths (conj tempPath currentNode))
  • It’s more customary to use literal ([]) vector than to call vector.

My take:

(defn return-results
  [current-node temp-length temp-path previous-path]
  [(assoc (first previous-path)
     :path-length temp-length
     :paths (conj temp-path current-node))])

Next!

(defn acyclic
  ""
  [inputVector currentNode endNode previousPath]
   
  (loop [i 0 tempLength Double/POSITIVE_INFINITY tempPath []]

     (if (not (< i (count (:paths (nth inputVector currentNode))))) 
           (returnResults currentNode tempLength tempPath previousPath)
   
       (let [evaluatingConnection (nth (:paths (nth inputVector currentNode))i)] 
        (let [nextNodeValidation (nodeValidation inputVector previousPath (:node evaluatingConnection) endNode)] 

          (cond
           (= nextNodeValidation 2)  
             (if (< (:cost evaluatingConnection) tempLength)
                (recur (+ i 1) (:cost evaluatingConnection) [(:node evaluatingConnection)]) 
                (recur (+ i 1) tempLength tempPath)
              )
              
           (= nextNodeValidation 1)
            (let [returnVector (acyclic inputVector (:node evaluatingConnection) endNode [(assoc (nth previousPath 0) :paths (conj (:paths (nth previousPath 0)) currentNode))])] 

              (if (< (+(:pathLength (nth returnVector 0)) (:cost evaluatingConnection)) tempLength)           
                (recur (+ i 1) (+ (:pathLength (nth returnVector 0))(:cost evaluatingConnection)) (:paths(nth returnVector 0))) 
                (recur (+ i 1) tempLength tempPath)
               )
             ) 

            :else 
             (recur (+ i 1) tempLength tempPath)
           )

          ) 
         )
        )
      )   
   ) 

Comments:

  • Big function! Can you break it up?
  • Is tempLength necessary? It seems like you can always calculate the length of a path.
  • Nested lets are unnecessary.
  • Looping with an integer is a smell. I wonder if there isn’t a better way to iterate through this.
  • I have this feeling that there’s a simple function here trying to get out. There are six cases, but three of them are the same.

Ok!

Those are my comments. Let me know if this helps.

Rock on!
Eric