As I posted on Reddit: It might also be good to mention Injest https://github.com/johnmn3/injest Which makes transducers more ergonomic to use if you are like me and use threading macros everywhere Would be curious to hear how others feel about it
Critique of Lazy Sequences in Clojure
41–50 of 52 posts
Re: Critique of Lazy Sequences in Clojure
#42Earlier quoted context omitted.
It's not really Rust's compiler that 'optimises lazy sequences'. It's LLVM, which notices that the code emitted happens to be able to be optimised down, if you run it for a really, really long time with some very strong optimisations. GHC would be a better example, I think. It performs stream fusion. This means it can turn 'map f (map g xs)' into 'map (f . g) xs', and of course it gets more complex than that, but tha…
> GHC would be a better example, I think. It performs stream fusion. This means it can turn 'map f (map g xs)' into 'map (f . g) xs', and of course it gets more complex than that, but that's the basics. It directly optimises lists (which, this being Haskell, are lazy sequences). Is it for built-in map or it would work in a general way for, say, `myMap f (myMap g xs)` ?
Just the stdlib `map`, but it's all ordinary library code. You can easily add your own rewrite rules.
Re: Critique of Lazy Sequences in Clojure
#43Earlier quoted context omitted.
You won't start anything. I dare you. Haha. You can't learn Clojure and you know it.
I hope you find something better to do with your day than putting people down.
Re: Critique of Lazy Sequences in Clojure
#44I've been circling around lisp for a couple of years. I'm starting in a month, I'll spend several hours a day. I still don't know what language I want to learn. I was drawn to Clojure because it looked like a lisp for getting stuff done. But a few things put me off. This article puts me off more. I want to get the semantics down before I have to think about what's going on under the hood.
Is this just a personal goal you’re setting? I’m curious because I’m in a similar position, so I’d love to hear your plan!
Re: Critique of Lazy Sequences in Clojure
#45Re: Critique of Lazy Sequences in Clojure
#46> The good parts of laziness: Avoiding unnecessary work Actually be very careful with side effects. Some functions like `map` and `for` take things in chunks, typically in steps of 32 as most underlying structures are in log-32 leaves. ``` (let [printing-range (map (fn [i] (print "debug: " i) i) (range)) first-10 (take 10 printing-range)] first-10) debug: 0 debug: 1 debug: 2 debug: 3 debug: 4 debug: 5 debug: 6 debug:…
Suppose we make a sequence of numbers which grows very rapidly, so that by the time we hit the 17th one, we have a bignum that is gigabytes wide.
You probably don't want this to be chunked in batches of 32.
Another situation might be if we have some side effect: the lazy sequence is connected to some external API somehow or foreign code. You might want it so that the observable behaviors happen only to the extent that the sequence is materialized.
The advice to be careful with side effects is good in general; not sure why you're downvoted.
Re: Critique of Lazy Sequences in Clojure
#47It’s too bad that transducers were created long after clojure’s inception. Can you always replace a lazy seq with a transducer? Could the language theoretically be redesigned to replace all default usages of lazy seqs with transducers, even if it were a major breaking change? And have lazy operations be very explicit?
Clojure is the only language where it is baked in that prominently though.
Re: Critique of Lazy Sequences in Clojure
#48 1> (len
(with-stream (s (open-file "/usr/share/dict/words"))
(get-lines s)))
** error reading #: file closed
** during evaluation of form (len (let ((s (open-file "/usr/share/dict/words")))
(unwind-protect
(get-lines s)
(close-stream s))))
** ... an expansion of (len (with-stream
(s (open-file "/usr/share/dict/words"))
(get-lines s)))
** which is located at expr-1:1
The built-in solution is that when you create a lazy list which reads lines from a stream, that lazy list takes care of closing the stream when it is done.If the lazy list isn't processed to the end, then the stream semantically leaks; it has to be cleaned up by the garbage collector when the lazy list becomes unreachable.
We can see with strace that the stream is closed:
$ strace txr -p '(flow "/usr/share/dict/words" open-file get-lines len)'
[...]read(3, "d\nwrapper\nwrapper's\nwrappers\nwra"..., 4096) = 4096
read(3, "zigzags\nzilch\nzilch's\nzillion\nzi"..., 4096) = 826
read(3, "", 4096) = 0
close(3) = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
write(1, "102305\n", 7102305
) = 7
exit_group(0) = ?
+++ exited with 0 +++
It is possible to address the error issue with reference counting. Suppose that we define a stream with a reference count, such that it has to be closed that many times before the underlying file descriptor is closed.I programmed a proof of concept of this today. (I ran into a small issue in the language run-time that I fixed; the close-stream function calls the underlying method and then caches the result, preventing the solution from working.)
(defstruct refcount-close stream-wrap
stream
(count 1)
(:method close (me throw-on-error-p)
(put-line `close called on @me`)
(when (plusp me.count)
(if (zerop (dec me.count))
(close-stream me.stream throw-on-error-p)))))
(flow
(with-stream (s (make-struct-delegate-stream
(new refcount-close
count 2
stream (open-file "/usr/share/dict/words"))))
(get-lines s))
len
prinl)
With my small fix in stream.c (already merged, going into Version 292), the output is: $ ./txr lazy2.tl
close called on #S(refcount-close stream # count 2)
close called on #S(refcount-close stream # count 1)
102305
One close comes from the with-stream macro, the other from the lazy list hitting EOF when its length is being calculated.Without the fix, I don't get the second call; the code works, but the descriptor isn't closed:
$ txr lazy2.tl
close called on #S(refcount-close stream # count 2)
102305
In the former we see the call to close in strace; in the latter we don't.Re: Critique of Lazy Sequences in Clojure
#49As I posted on Reddit: It might also be good to mention Injest https://github.com/johnmn3/injest Which makes transducers more ergonomic to use if you are like me and use threading macros everywhere Would be curious to hear how others feel about it
these look great to me. would there be a downside to adding them to core?