0
votes

How can I use something similiar to recurnot at tail position?

Take a look at my code:

(defn -main [& args]

  (println "Hi! Type a file name...")

  (defn readFile[])
    (let [fileName(read-line)]
    (let [rdr (reader fileName)]
      (if-not (.exists rdr) 
        ((println "Sorry, this file doesn't exists. Type a valid file name...")
         (recur)))
         (defn list '())
         (doseq [line (line-seq rdr)]
           (if-not (= "" line)
             (concat list '(line)))
             (list))))

(defn fileLinesList (readFile))
  ...
  ...)

I know I can't use recur here... But I neither know how can I make it in clojure.

I'm a newbie in Clojure and I'm coming from a OOP context. So...

Is there a way to use recursion in this case? What would be an alternative?

1
Is your goal to write a function reading lines of text from a file or existing function would be good enough for you?Piotrek Bzdyl
Although you have not written it like this the logic of your function seems to be 'if <file does not exist> then <recurse> else <do things assuming file exists>', in which case the function is tail-recursive. So I'd suggest rewriting it like that.user5920214
There are other mistakes that Piotrek didn't mention, but which his code avoids. Just to get you started in the right direction: (1) In Clojure, none of the everyday, common functions will actually modify a piece of data, as your line (concat list '(line)) is supposed to do, since it's inside of a doseq. concat cannot modify list; instead, it returns a new piece of data, which is the concatenation of its arguments.Mars
(2) '(line) will return a list containing a single element, the symbol line, but what you wanted was the contents of that symbol. What should have been there was line without the parentheses or the quote.Mars
(3) What (list) in the last line looks like it returns is the result of calling the function list on no arguments, returning the empty list. When there is an unquoted list, its first argument is normally treated as the name of a function, which is to be evaluated with the other elements of the list as its arguments (except in special cases, as when the list is quoted). However, in this case, you have redefined list, so that it no longer refers to a function. So (list) will produce an error rather than executing the function that list normally names.Mars

1 Answers

6
votes

First of all you should not nest your functions definitions in another defn (-main in this case). defn or def always defines symbol bindings at the top level of namespace and they don't nest. If you want to define a locally scoped function you need to use let and fn, e.g.

(let [my-fn (fn [a b] (+ a b))]
  (my-fn 1 2))

In your particular case I think it would be easier to split your code into multiple functions. This way it will be more readable.

Prompting for a file name is one piece of your logic.

(defn get-existing-filename []
  (let [filename (read-line)]
    (if (.exists (java.io.File. filename))
      filename
      (do
        (println "Sorry, this file doesn't exists. Type a valid file name...")
        (recur)))))

Then you can use it to read a file removing empty lines:

(with-open [input (clojure.java.io/reader (get-existing-filename))]
  (->> (line-seq input)
       (remove empty?)
       (doall)))

For a file with following content:

AAA

BBB
CCC

DDD

it will return

("AAA" "BBB" "CCC" "DDD")

If you really want it as a single function, the following will work:

(defn read-file []
  (let [filename (read-line)]
    (if (.exists (java.io.File. filename))
      (with-open [input (clojure.java.io/reader (get-existing-filename))]
        (->> (line-seq input)
             (remove empty?)
             (doall)))
      (do
        (println "Sorry, this file doesn't exists. Type a valid file name...")
        (recur)))))

Finally, this function can be called from -main.

I have also noticed another issue in your sample code:

((println "Sorry, this file doesn't exists. Type a valid file name...")
 (recur))

if and if-not require a single expression for their then and else branches. If you want to have multiple expressions you need to nest them in do:

(do
  (println "Sorry, this file doesn't exists. Type a valid file name...")
  (recur))

If you need if or if-not without the else branch then you can use when or when-not macros. Then you don't need to wrap multiple expressions because when/when-not will wrap them for your inside of do.

(when true
  (println 1)
  (println 2))

is equivalent to

(if true
  (do
    (println 1)
    (println 2)))