Fork me on GitHub
#beginners
<
2024-05-21
>
Melanie02:05:09

Hi! I'm still in the process of learning clojure, using euler problems so far. The image represents my progress so far. I'm doing euler https://projecteuler.net/problem=12, and in order to do that, I thought that would be a good idea to finally get better understanding of lazy-seq I get that I can probably generate them by simply using reductions with something like

(defn triangular-numbers (reductions + (iterate inc 1)))
However, i'm trying to get a hang of lazy-seq The following seems to work, but I feel I have it more complicated that it should be.
(defn triangular-numbers
  ([] (triangular-numbers 1 0))
  ([curr prev] (cons (+ curr prev)
                         (lazy-seq (triangular-numbers (inc curr) (+ curr prev))))))

Melanie02:05:14

I should obviously be using a let for (+ curr prev)

Melanie02:05:46

a bit more satisfied with my next version:

(def triangular-numbers
  ((fn t-n [curr previous]
     (let [next (+ curr previous)]
       (lazy-seq (cons next (t-n (inc curr) next))))) 1 0))

Bob B03:05:32

In that situation, I might letfn the function and then call it because sometimes the double parens read a little strange (to me). But that's just my preference. I might also name the variables a bit differently to distinguish between the 'counter' and the 'total'. In general, this looks to line up nicely with the recipe at <https://clojure.org/reference/lazy>.

thanks2 1
💜 1
Melanie03:05:06

TY so much Bob! This is super helpful!

Melanie03:05:43

I guess I can rename next to total, but the rest is just the previous and current value, as I didn't find a way to avoid that, I feel those are named ok.

Melanie03:05:04

so you meant something more like this?

(def triangular-numbers
  (letfn [(t-n [curr previous]
     (let [total (+ curr previous)]
       (lazy-seq (cons total (t-n (inc curr) total)))))]
    (t-n 1 0)))

Melanie03:05:21

I can probably rename the inner function too, t-n is not super explicit.

Melanie03:05:26

but thanks so much!

Melanie03:05:06

(def triangular-numbers
  (letfn [(triangular-n [counter previousTotal]
     (let [total (+ counter previousTotal)]
       (lazy-seq (cons total (triangular-n (inc counter) total)))))]
    (triangular-n 1 0)))

👍 1
craftybones06:05:22

I find the extra letfn and fn bindings unnecessary. I think your original solution with a simple let works well.

(defn triangular 
  ([] (triangular 1 0))
  ([curr prev]
   (let [n (+ curr prev)]
     (lazy-seq (cons n (triangular (inc curr) n))))))

💜 1
thanks2 1
craftybones06:05:02

For one, the letfn obfuscates matters and hides the recursive nature of the call.

craftybones06:05:58

Is there a reason you abandoned the defn and went with the def ?

craftybones06:05:05

If you are worried about having a signature that allows consumers to invoke triangular with arguments and you only want to expose the sequence, then you’re better off making this private and declaring a public facing var with the sequence defined.

Melanie01:05:44

well I was worried about few things: • def vs. defn was for the caller to not have it to look like (take 5 (triangular-numbers)) vs (take 5 triangular-numbers)... which i tend to prefer (for that use case) • then yes I was worried about invoker able to call it with arguments when i didn't want.

Melanie01:05:50

though considering it's all just personal euler project to practice, there's no actual worry. and I do like the use of private (I guess using defn- with a public var which both makes everything cleaner and does fit my first aesthetic preference on point one)

Melanie01:05:12

so thanks for that suggestion, I just didn't even think about it.

Melanie01:05:59

and definitely dropping the -number is worth it, I do like how the code you posted looks like. I guess my main worry initially was rather or not I was using lazy-seq correctly as it seemed that having access to the previous value to calculate the next one would be so common that I was thinking I was doing something wrong with having a two-args signature...

Hoon Wee06:05:29

Hello, I am trying to implement authentication/authorization in Clojure backend project. Seems like there's a library called "Buddy" which has been popular yet not maintained anymore, but I also heard that many Clojure libraries reach their own maturity, and doesn't need to be updated anymore. I wonder if that's the case for Buddy also - or are there any better alternatives for implementing Auth?

dharrigan10:05:07

Buddy is still being maintained. Last commit was only 1 year ago.

dharrigan10:05:27

It's used in a lot of projects (we use it at work) - it functions perfectly.

Hoon Wee11:05:42

Great! thanks

dharrigan11:05:45

You're most welcome. Seriously, it's a great library and you won't have issues with it 🙂 It's tried and tested.

❤️ 3
Shawn Northrop17:05:51

Hi, how would I go about taking the first item, dropping n items, and then taking n items from a lazy seq? i.e. given the seq ~[1 2 3 4 5 6 7 8]~ (1 2 3 4 5 6 7 8) I want to take 1 drop 2 take 2 the result being ~[1 4 5]~ (1 4 5)

Shawn Northrop17:05:20

I am parsing a csv file and want the header, and then want to skip n rows and take n rows…

(->> (csv/read-csv reader)
         (drop 50000)
         (take 100)
         (csv/write-csv writer))))

hiredman17:05:27

[] is a vector, which is not a seq

Shawn Northrop17:05:28

this works except I don’t have the header

Shawn Northrop17:05:47

sorry bad example, it is a seq i am actually parsing

hiredman17:05:02

(concat (take 1 x) (take 2 (drop 2 x)))

❤️ 1
Shawn Northrop17:05:22

Thanks ill try that

hiredman17:05:47

except you need to drop more than 2, because you are dropping all the parts of x you don't want, which still includes the parts included in the first take

Shawn Northrop17:05:02

yea i get where you are going with that

Shawn Northrop17:05:16

i’m guessing ill have to wrap it in a fn

Shawn Northrop17:05:09

(->> (csv/read-csv reader)
         (#(concat (take1 %) (take 2 (drop 2 %)))
         (csv/write-csv writer))))

Bob B18:05:04

another option that does more or less the same thing, but might read a little bit more nicely in this particular scenario, would be destructuring to get the first (header) row:

(let [[header & rows] (csv/read-csv some-csv)]
  (concat [header] (take 5 (drop 2 rows))))

❤️ 2
Felix Dorner20:05:47

I have a lazy seq where each element takes a while to evaluate, e.g. a http request, here simulated with sleep:

(->> (for [x (range 50)]
       (do (Thread/sleep 200)
           (println "done with" x)
           x))
     (run! println))
Running this, as expected prints "done with ..." every second, but the println in the last line is not executed until 32 elements have been realized, so 32 lines are printed after 32*0.2 seconds, then again theres' a wait time and the remaining lines get printed again in a chunk. Why is that? does run/reduce realize ahead?

Felix Dorner21:05:08

And the followup question is, can I somehow steer that behavior to get "ticking" output?

hiredman21:05:45

it is a chunked sequence

hiredman21:05:36

lazy-seqs, despite the name, are based understood as "non-strict" seqs, they don't make guarantees about how lazy they are

hiredman21:05:26

there is an optimization that some seqs (like the ones produced by range) have where they produce laziness in chunks (of size 32)

hiredman21:05:00

many lazy seq operations (map, filter, for, etc) try to be transparent to chunking, in that if the input is chunked the output is chunked

phronmophobic21:05:59

My general advice is that if you care about when things happen, don't use lazy sequences. There are plenty of tools for specifying what operations should happen separately from when they should happen.

🎯 1
hiredman21:05:45

my recommendation would be something like (run! println (eduction (map (fn [x] ...)) (range 50))) (basically don't use lazy seqs for this kind of thing)

Felix Dorner21:05:05

Ok thanks! Will have a look at options.

didibus21:05:07

Ya, you should think of them more as batched. But even then, when will one or two batch get realized can be hard to determine, as some functions can peek a bit further and cause another chunk to realize. For now batches are normally 32 elements long. Transducers are a pretty good replacement for when timing matters.