Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Function not called in map

Tags:

clojure

I am trying to rename all files and subdirectories in a given directory. I create vectors of the old and new names, and map a renaming function over the two vectors. The vectors of file paths are created ok but the mapping (map #(re-name (as-file %1) (as-file %2)) flist new-flist))) does not seem to call the renaming function at all (I've put a print call in it to test). After much tweaking and searching I am still perplexed as to what I'm missing.

Also, I would like to know how I could obviate resorting to forums by debugging it by stepping through the code.

Code:

;;;; Use:  Replaces whitespace in names of all directories and files in the given directory with the given separator.

(ns file-renamer.core
  (:gen-class)
  (:require [clojure.string :as str]))

(use '[clojure.java.io])
(use '[clojure.pprint])

; Constants for testing -> params live
(def direc "/home/john/test/")
(def separator "-")
; ====================================

(defn traverse-dir 
    "Return a seq of pathnames of directories and files in the given directoryt"
    [dir-path]
    (map #(.getAbsolutePath %) (file-seq (file dir-path))))

(defn replace-spaces
    "Return a copy of string s1 with all sequences of spaces replaced with string s2"
    [s1 s2]
    (str/replace s1 #"\s+" s2))

(defn re-name
    "Rename a file"
    [old-file new-file]
    (pprint (str "Renaming:  " old-file " ==>> " new-file)) ; put here for debugging
    (.renameTo old-file new-file))

(defn -main
    "Map a fn to rename a file over all files and dirs in the given directory"
    [& args]
    (let [flist      (vec (traverse-dir direc))
          new-flist  (vec (map #(replace-spaces % separator) flist))]
      (pprint flist)
      (pprint new-flist)
      (map #(re-name (as-file %1) (as-file %2)) flist new-flist)))

Output:

17 Mar 20:53 /file-renamer ⋄ lein run
["/home/john/test"
 "/home/john/test/test 108"
 "/home/john/test/test 108/    baaaa   rrrr"
 "/home/john/test/test 108/    baaaa   rrrr/Open    Document Text  ....  odt"
 "/home/john/test/test 108/    baaaa   rrrr/ba   z  z  er   ."
 "/home/john/test/test 108/    baaaa   rrrr/ba   z  z  er   ./freed.frm"
 "/home/john/test/test 108/    baaaa   rrrr/ba   z  z  er   ./New Folder"
 "/home/john/test/test 108/    baaaa   rrrr/ba   z  z  er   ./New Folder/Plain Text.txt"
 "/home/john/test/test 108/    baaaa   rrrr/ba   z  z  er   ./fr   ed.txt"
 "/home/john/test/test 108/s p a c e s------S P A C E S "
 "/home/john/test/fox"
 "/home/john/test/foo"
 "/home/john/test/fog"]
["/home/john/test"
 "/home/john/test/test-108"
 "/home/john/test/test-108/-baaaa-rrrr"
 "/home/john/test/test-108/-baaaa-rrrr/Open-Document-Text-....-odt"
 "/home/john/test/test-108/-baaaa-rrrr/ba-z-z-er-."
 "/home/john/test/test-108/-baaaa-rrrr/ba-z-z-er-./freed.frm"
 "/home/john/test/test-108/-baaaa-rrrr/ba-z-z-er-./New-Folder"
 "/home/john/test/test-108/-baaaa-rrrr/ba-z-z-er-./New-Folder/Plain-Text.txt"
 "/home/john/test/test-108/-baaaa-rrrr/ba-z-z-er-./fr-ed.txt"
 "/home/john/test/test-108/s-p-a-c-e-s------S-P-A-C-E-S-"
 "/home/john/test/fox"
 "/home/john/test/foo"
 "/home/john/test/fog"]
17 Mar 20:53 /file-renamer ⋄ 
like image 765
John Avatar asked Jun 04 '26 21:06

John


2 Answers

There are a couple of problems here. They are not directly your fault, but it's more the REPL that is playing a trick on you and you not fully understanding (probably) the core concepts of Clojure and lazy operations/sequences.

The map function returns a lazy sequence, that need to be realized at some point in time, which you are not doing. On top of that, the re-name function is not a side-effect free function (pure function). The REPL is also playing a trick on you: If you call (-main) on the REPL, it will automatically realize those sequences and can cause a lot of confusion for someone new to Clojure.

The most straightforward solution would be to just use the doall function.

(doall (map #(re-name (as-file %1) (as-file %2)) flist new-flist))

But this is the quick and dirty way and I am going to quote Stuart Sierra here:

You might get the advice that you can “force” a lazy sequence to be evaluated with doall or dorun. There are also snippets floating around that purport to “unchunk” a sequence.

In my opinion, the presence of doall, dorun, or even “unchunk” is almost always a sign that something never should have been a lazy sequence in the first place.

A better solution in this case would be to use the doseq function and write something like this:

(defn -main
    "Map a fn to rename a file over all files and dirs in the given directory"
    [& args]
    (doseq [file-string (traverse-dir direc)]
      (let [input-file (as-file file-string)
            output-file (as-file (replace-spaces file-string separator))]
        (re-name input-file output-file))))

This could also be written way shorter.

A good reading that will help a lot in general is the blog post from Stuart Sierra: Clojure Don’ts: Lazy Effects.

like image 191
Galrog Avatar answered Jun 07 '26 23:06

Galrog


Check the docs on doall.

Here, renaming is a side-effect. The map returns a lazy sequence. You need to force mapping over the whole collections in order for the renaming to happen.

like image 38
Simon Brooke Avatar answered Jun 07 '26 22:06

Simon Brooke



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!