Fork me on GitHub
Zhylka Kyrylo08:06:28

I have troubles trying to read a static file. I have a function that should take path to the file and read it:

(defn load-schema [file]
  (with-open [r (io/reader (io/resource file))]
      (edn/read r)))
My file structure looks like this:
├── deps.edn
├── resources/
│   └── schema.edn
└── src/
    ├── main.clj
    └── schema.clj
So, in the schema.clj when I run this function as
(load-schema "resources/schema.edn")
I just get an error like this:
; Execution error (IllegalArgumentException) at schema/load-schema (REPL:7).
; Cannot open <nil> as a Reader.
What's strange, is if I try to debug it with the next input:
(with-open [r (io/resource "main.clj")] (print r))
I get an error, that shows me the exact path to the main.clj file, but still cannot locate it:
; #object[ 0x3f601828 file:/C:/Users/Acer/Documents/Project/src/main.clj]; Execution error (IllegalArgumentException) at schema/eval11447 (REPL:13).
; No matching field found: close for class
I feel like this is quite beginner stuff, but I don't understand what is the problem here.


you're telling io/resource to find "resources/schema.edn" which makes it look for /resources/resources/schema.edn — try just "schema.edn"


problem 1 is that io/resource takes a classpath-relative name, not a filename. main.clj is on the classpath but resources/schema.edn probably isn't. Perhaps if you ask for just schema.edn (without the word resources) it will work better. problem 2, your repl experiment with with-open gave it the url and not the reader; hence the message about not finding a close method.

Zhylka Kyrylo09:06:54

With this code:

(defn load-schema1 [file]
  (with-open [r (io/reader (io/resource file))]
    (edn/read r)))

(load-schema1 "schema.edn")
I get this error:
; Execution error (ClassCastException) at schema/load-schema1 (REPL:22).
; class cannot be cast to class ( and are in module java.base of loader 'bootstrap')

Zhylka Kyrylo09:06:04

If I delete io/resource part, I get error that the system just cannot find the file specified, if after that I again pass resources/schema.edn, I get error identical to the one before


I recommend building up in the REPL from successful individual expressions instead of trying to write the whole thing and figure out what went wrong. In other words, drop the defn and from your source buffer evaluate just the expression (io/resource "schema.edn") . Then wrap that expression with, say, slurp. Consider also this example

Zhylka Kyrylo10:06:08

Thanks for the given example! Finally, this code worked as intendent:

(defn load-schema1 [file]
  (with-open [r (io/reader file)]
    (edn/read ( r))))

(load-schema1 "resources/schema.edn")

👍 1

noob question: I am really struggling to understand the benefits of lazy sequences. According to my book lazy sequences are a series of instructions to be executed only when needed. But from my experience everything is executed when it's needed, like if I do immediate sequencing in something like python I will also go query by query and stop when I find what I need - why would I buffer all the data in advance or anything like that?


lazy sequences allow you to decouple how elements of a sequence are computed, from how and when they are going to be consumed. Like you can have a piece of code calculating all the possible odd numbers :

(def odd-numbers (filter odd? (range)))
and it doesn't need to deal with how many someone is going to need. Then some other piece of code can use them :
(take 10 odd-numbers)
(1 3 5 7 9 11 13 15 17 19)


lazy sequences are like python generators


That's a great question! One technique for simplifying your code is to decouple "what" (the logical operations), "when" (the scheduling of the operations), and "how" (the particular implementation of the operations). Lazy sequences let you decouple what/how from when which has architectural benefits. > why would I buffer all the data in advance or anything like that? Typically, you use lazy sequences specifically to avoid buffering all the data. As an example:

(defn process-lines [lines]
  (->> lines
       (filter good-stuff?)
       (map my-transform)))

(with-open [rdr (io/reader "file.txt")
            writer (io/writer "other-file.txt")]
  (let [lines (line-seq rdr)
        processed-lines (->> (process-lines lines)
                             (take 10))]
    (run! #(doto writer
             (.write %)
I haven't tested this code, but the point is that you can separately specify when from what/how. Further, this code doesn't require buffering the full dataset in memory and can process larges files (assuming individual lines are reasonably sized). You could also simplify it further using libraries, but hopefully it illustrates the idea. In general, I would prefer using transducers over lazy sequences (which were designed before transducers were available).


@U0739PUFQ isn't that what all programming is? the implementation is always static. and the execution is primarily when needed. by the time you need the sequence you are essentially going over a list of things to do AFAIK which doesn't feel much different than any other generator. like what's the difference with doing a generator that does query by query and stops when it finds what it needs? I don't need to buffer the data with immediate sequencing either? @U0NCTKEV8 that's my feeling too. @U7RJTCH6J an interesting point. but can't you separate your logic with any functions not just ones that evaluate lazily?


which doesn't feel much different than any other generatorthey are generators. They also have a cache, so if you access the same elements multiple times they aren't going to be re-computed again, which makes them immutable


Lazy sequences aren’t the only technique for decoupling when from what/how. You could to this with python generators as well. However, if process-lines wasn’t lazy, it would require buffering the whole dataset and do a bunch of extra work since our example only wants the first 10 lines. There are other techniques that would look similar.


so basically clojure has to do it this way to be efficient if it also wants to avoid side effects?


any language or run time has to do something similar to decouple when from what/how.


You’ll find similar things in other languages (eg. Haskell, C# LINQ, python generators)


> I am really struggling to understand the benefits of lazy sequences so, if you understand the benefits of generators, then I see lazy sequences as a similar abstraction, but with immutability in mind


they give you a reference to something that has the same interface as a sequence (first and rest), which you can share, but all the elements will be calculated only once as they are needed


Yes, lazy sequences are effectively generators + caching


so each computation is cached and can be reused cheaply later without recomputation?


the main point is that generators are stateful in that once you produce an element you have forever "consumed" it and can't get it back, while it isn't the case with lazy seqs, where first and next on the same seq will always yield the same thing


like what's the difference with doing a generator that does query by query and stops when it finds what it needs? I don't need to buffer the data with immediate sequencing either?Some other differences with python generators: • python's generators introduce new syntax • while you could implement (take 5 xs) with python generators, I don't think it would be idiomatic (it's been a while since I've used python).

(->> xs
     (take 5))
In my opinion, the lazy sequence approach is more concise and you end up with more reusable pieces. My python is rusty though, so I might be comparing apples and oranges. • as others have pointed out, you can also reuse lazy sequences
(let [nums (range 1000)
      evens (filter even? nums)
      ;; reuse nums
      odds (filter odd? nums)
      both-sums (map + evens odds)
      odd-sums (map + odds odds)
      even-sums (map + evens evens)]
  {:both both-sums
   :odds odd-sums
   :evens even-sums})
This example is very contrived, but you can reuse lazy sequences in multiple pipelines. Another function could further process any of the returned sequences. I'm not sure you could do that with python generators?


It might also help to read about possible problems with lazy seqs: