I wish to emulate cp -R using clojure. Is there an idiomatic way of writing this function so that the entire contents of a directory is recursively copied?
(given there's still interest in this answer):
One can use java java.io.File with clojure's file-seq:
(require '[clojure.java.io :as io])
(->> "/path"
(io/file)
(file-seq)
(run! (your-fn-that-copies-files-and-creates-folders)))
file-seq will return a tree-like structure for your files/folders.
In order to keep a consistent directory structure from your root point onwards, we must take an absolute path and turn it into a relative path:
(require '[clojure.string :as str]
'[clojure.data :as data])
(defn- path->chunks [file] (str/split (.getAbsolutePath file) #"/"))
(defn path->relative [root file]
(str/join "/" (filter some? (second (data/diff (path->chunks root)
(path->chunks file))))))
You can combine all that to perform your recursive copy.
Related
What's the difference between use and :use in the ns macro?
Docs says the following:
Use :use in the ns macro in preference to calling this directly
It is a bit confusing.
In the ns form, you want to use the keyword version (:require ...) like:
(ns clj.core
(:require [tupelo.core :as t] ))
You should consider this the "normal" way of doing things.
The other version, without the colon, is a function of the same name that can be called at the REPL in case you can't or don't want to use the ns form. This would look like:
> lein repl
user=> (require '[tupelo.core :as t] )
nil
user=> (t/append [1 2 3] 4)
[1 2 3 4]
Please note: For the repl/function version, you must also quote the vector containing the namespace specs. Notice that we did not quote the spec in the ns version using :require.
As an alternative, if you are already in the editor and just want to test something quick at the REPL, you can cut/paste the whole ns form from the top of your file into the REPL:
> lein repl
user=> (ns clj.core
#_=> (:require [tupelo.core :as t] ))
user=>
which is easier and less error-prone than typing the function-version by hand.
For a good overview please see the recent blog posting here: https://stuartsierra.com/2016/clojure-how-to-ns.html
I am parsing a big csv file and I am using the first line of it as the keys for the records. So for a csv file like:
header1,header2
foo,bar
zoo,zip
I end up with a lazy seq like:
({:header1 "foo" :header2 "bar"},
{:header1 "zoo" :header2 "zip"})
The code working fine, but I am not sure if in the following function I am holding the head of "lines" or not.
(defn csv-as-seq [file]
(let [rdr (clojure.java.io/reader file)]
(let [lines (line-seq rdr)
headers (parse-headers (first lines))]
(map (row-mapper headers) (rest lines)))))
Can somebody please clarify?
Yes, this expression syntactically says to hold the head
(let [lines (line-seq rdr)
though in this case you should get away with it because their are no references to
lines and headers after the call to map and the Clojure compiler starting with 1.2.x includes a feature called locals clearing: it sets any locals not used after a function call to nil in the preamble to the function call. In this case it will set lines and headers to nil in the local context of the function and they will be GCd as used. This is one of the rare cases where clojure produces bytecode that cannot be expressed in java.
I'm learning Clojure and as an exercise I wanted to write something like the unix "comm" command.
To do this, I read the contents of each file into a set, then use difference/intersection to show exclusive/common files.
After a lot of repl-time I came up with something like this for the set creation part:
(def contents (ref #{}))
(doseq [line (read-lines "/tmp/a.txt")]
(dosync (ref-set contents (conj #contents line))))
(I'm using duck-streams/read-lines to seq the contents of the file).
This is my first stab at any kind of functional programming or lisp/Clojure. For instance, I couldn't understand why, when I did a conj on the set, the set was still empty. This lead me to learning about refs.
Is there a better Clojure/functional way to do this? By using ref-set, am I just twisting the code to a non-functional mindset or is my code along the lines of how it should be done?
Is there a a library that already does this? This seems like a relatively ordinary thing to want to do but I couldn't find anything like it.
Clojure 1.3:
user> (require '[clojure.java [io :as io]])
nil
user> (line-seq (io/reader "foo.txt"))
("foo" "bar" "baz")
user> (into #{} (line-seq (io/reader "foo.txt")))
#{"foo" "bar" "baz"}
line-seq gives you a lazy sequence where each item in the sequence is a line in the file.
into dumps it all into a set. To do what you were trying to do (add each item one by one into a set), rather than doseq and refs, you could do:
user> (reduce conj #{} (line-seq (io/reader "foo.txt")))
#{"foo" "bar" "baz"}
Note that the Unix comm compares two sorted files, which is likely a more efficient way to compare files than doing set intersection.
Edit: Dave Ray is right, to avoid leaking open file handles it's better to do this:
user> (with-open [f (io/reader "foo.txt")]
(into #{} (line-seq f)))
#{"foo" "bar" "baz"}
I always read with slurp and after that split with re-seq due to my needs.
What would be an ideomatic way in Clojure to get a lazy sequence over a file containing float values serialized from Java? (I've toyed with a with-open approach based on line-reading examples but cannot seem to connect the dots to process the stream as floats.)
Thanks.
(defn float-seqs [#^java.io.DataInputStream dis]
(lazy-seq
(try
(cons (.readFloat dis) (float-seqs dis))
(catch java.io.EOFException e
(.close dis)))))
(with-open [dis (-> file java.io.FileInputStream. java.io.DataInputStream.)]
(let [s (float-seqs dis)]
(doseq [f s]
(println f))))
You are not required to use with-open if you are sure you are going to consume the whole seq.
If you use with-open, double-check that you're not leaking the seq (or a derived seq) outside of its scope.
I am very very new to clojure. The zip utility looks interesting but I cant seem to use it.
I tried
;; ZIP
(:use "zip")
(def data '[[a * b] + [c * d]])
(def dz (zip/vector-zip data))
But I am getting
java.lang.Exception: No such namespace: zip
How do yo use external libraries?
You may be confusing two different ways to import code. You can do it this way:
user> (use 'clojure.zip)
Or while you're declaring a namespace in a source file:
(ns foo
(:use clojure.zip))
The second version is a macro that is expanded into the first.
Outside of (ns), doing (:use "zip") is going to treat :use as a function and call it with "zip" as its parameter (i.e. try to use the string "zip" as a collection and look up the key :use in it), which does nothing.
clojure.zip has some functions whose names clash with things in clojure.core though, so you either have to do something like this:
user> (use '(clojure [zip :rename {next next-zip replace replace-zip remove remove-zip}]))
Or preferably this:
user> (require '(clojure [zip :as zip]))
With the latter you can refer to functions like (zip/vector-zip data) as you wish.
See the documentation for require and refer and the page talking about libs.
I don't know much about clojure, but this little ditty seems to work:
(require '[clojure.zip :as zip])
(def t '(:a (:b :d) (:c :e :f)))
(def z (zip/zipper rest rest cons t))
(zip/node z)