Fork me on GitHub
#beginners
<
2021-06-24
>
Krishan V09:06:53

Hey everyone, I am trying spit the output of a distinct call which returns a lazy sequence. Thing is, it writes clojure.lang.LazySeq@4e9d9e in the text file instead of a list that I want. How do I force the lazy sequence to be computed so that I can write it into a file?

Sam H09:06:10

it depends on how you are outputting the sequence to the text file as you could use doall or see the answer here which uses pr-str on the sequence https://stackoverflow.com/questions/23405852/clojure-printing-lazy-sequence

borkdude09:06:13

you could also call vec on it

noisesmith15:06:19

fun fact: the entire lazy sequence is realized in order to calculate the hash for the sequence that's part of the string, it's just that the toString method for clojure.lang.LazySeq doesn't show the sequence contents doall is irrelevant here

noisesmith15:06:49

(cmd)user=> (def s (distinct (map #(doto % println) [1 1 1 2 3 2 3 4 5 4 3 2])))
#'user/s
(cmd)user=> (str s)
1
1
1
2
3
2
3
4
5
4
3
2
"clojure.lang.LazySeq@1c3e4a2"
(cmd)user=> (doall s)
(1 2 3 4 5)
(cmd)user=> (str s)
"clojure.lang.LazySeq@1c3e4a2"
(cmd)user=> (pr-str s)
"(1 2 3 4 5)"

Krishan V04:06:18

Thanks for the help everyone!

bnstvn12:06:48

what would make a call chain like this clean? (resembling some-> with a custom predicate?)

(let [context (call1 context)]
  (if (continue? context)
    (let [context (call2 context)]
      (if (continue? context)
        (call3 context)
        context))
    context))

manutter5113:06:40

That was my first thought too, but I don't think it'll work quite right if you want to keep calling (continue? context) on the updated value of context (assuming context is an immutable value).

👍 2
bnstvn13:06:22

yes, not sure how to make cond-> work with the “new” context values

pavlosmelissinos13:06:34

Oh that's right, the predicates are for the original input (I always assume they get threaded as well). I suppose you could repeat the actual code as part of the predicates but it wouldn't be pretty...

Drew Verlee13:06:07

The new context is the result of the last computation. In the case below, the context after would be 2 and false? Would be the continue. (cond-> 1 ; we start with 1 true inc ; the condition is true so (inc 1) => 2 false (* 42) ; the condition is false so the operation is skipped (= 2 2) ( 3)) ; (= 2 2) is true so ( 2 3) => 6 ;;=> 6

4
Drew Verlee13:06:29

So it would work right? But it looks like it also could just be a recursive call.

Drew Verlee13:06:34

(loop [c context] (if (c? (Recure (call c)) c)

Drew Verlee13:06:42

Or something, I'm on my phone

Drew Verlee13:06:27

Are the calls different?

manutter5113:06:29

Personally, I would probably do something like

(let [context (call1 context)
      context (if (continue? context) (call2 context) context)
      context (if (continue? context) (call3 context) context)
      ...
      ]
  context)
just because I know future me will be able to read that code and know exactly what’s going on.

👍 4
manutter5113:06:57

It’s a bit boiler-plate-y, but you avoid nesting x levels deep, and the repetition makes it obvious that it’s just repeated iterations of “call a fn and check for continue?”

bnstvn13:06:15

yes, this let looks better, making the pattern obvious

bnstvn13:06:13

> Are the calls different? yes, they are, just working on the same/similar shape

Drew Verlee13:06:19

Well the repeating part is the specific predicate and the call so (Loop [calls [a b c] context] (let [call (first calls)] (if (and call (C? Context)) (recure (rest calls) (call context) context) or something, lisp isn't easy to your type on a phone.

Drew Verlee13:06:58

Actually, use reduce

Drew Verlee13:06:07

So you don't have to check to calls are gone

pavlosmelissinos13:06:20

I would avoid reduce and recursion for something like this. I think I'd rather do it like @U06CM8C3V said or even with as-> :

(as-> (call1 context) context
  (if (continue? context) (call2 context) context)
  (if (continue? context) (call3 context) context)
  ...)

👍 2
bnstvn13:06:54

you meant something like this with reduce?

(reduce (fn [context f]
          (let [new-context (f context)]
            (if (continue? new-context)
              new-context
              (reduced new-context))))
        context
        [call1 call2 call3])

Drew Verlee13:06:16

this is what i wrote as an example:

(reduce
    (fn [n f]
      (if (int? n)
        (f n)
        (reduced n)))
    0
    [inc inc str inc]);; => "2"

👍 2
pavlosmelissinos14:06:27

It's harder to read, you have to put some effort to understand what's going on. Smarter is not always better. 🙂 In that sense, my solution using as-> is also less readable than just using a let and ifs

Drew Verlee14:06:09

maybe. How hard something is to read is rather subjective.

Drew Verlee14:06:45

When i look at the let binding solution i have to scan each word twice to see if things are reused, and whats really changing, so to me, that's harder to read.

tschady15:06:02

if you want to handle the elses then this looks like Railway Oriented Programming

noisesmith15:06:44

@UEQPKG7HQ reduce is only harder to read if you aren't used to it, IMHO it's worth getting used to

👍 2
noisesmith15:06:10

it's simpler to me than deep nesting of conditionals and here it conveys two important pieces of information that ease the reading of the code: there's a series of inputs that are always handled the same way, and any one of them could be a short circuit condition

bnstvn17:06:57

i didnt find reduce easier to read either, but thats why im asking in beginners.. it might make sense to actually def and name the paramterized reduce function - but what would be a nice name for that?

noisesmith17:06:32

something like stop-when-not-int (or some more succinct version of that) could work I find that the patterns of reduce functions are so predictable I rarely need names for clarity, it's always a function of two parameters, the first arg is always the init or return value of the last call, the second arg is always an item from the collection being traversed

noisesmith17:06:04

those properties might seem arbitrary but they never change so you only need to figure them out once

bnstvn17:06:08

this came to me multiple times, thats why i thought there might be an idiom for this (or a threading where some-> is a special case like (def some-> (partial something-> nil?))

noisesmith17:06:23

but that doesn't account for the repetition of applying some function to a previous value, then halting if a condition is met

noisesmith17:06:08

;; and reduce does capture that pattern easily                                                                                                                 
(reduce (fn [state f]                                                                                              
          (let [next-state (f state)]                                                                  
            (if (pred? next-state)                                               
             next-state
             (reduced state)))) 
        initial-state           
        functions)

👍 5
Ryan Tate18:06:05

core.async question: Why does this fail (hangs indefinitely in repl): (<!! (go (>! (chan) (+ 41 1)))) But this works: (let [c (chan)] (<!! (go >! c (+ 41 1)))) (Update: nope, left off a paren!) In other words, making a new channel inside a go block seems to be a no-no, the channel has to  be made outside the go block even though the go block returns its channel... Thanks for any answers, mostly just trying to wrap my head around this conceptually.

noisesmith18:06:37

in the second one you are not calling >!, it's simply referenced and discarded

noisesmith18:06:17

the corrected version hangs just like you initial example did:

user=> (let [c (chan)] (<!! (go (>! c (+ 41 1)))))
...

Ryan Tate18:06:11

oof thanks. paren error sorry. now to figure out why it's hanging....

hiredman18:06:25

because the go block is blocked waiting for someone to consume from the channel

hiredman18:06:06

an unbuffered channel is a synchronous exchange point, a reader and writer need to meet

Ryan Tate18:06:40

Thanks, ya I must be misunderstanding the async/go docs, this works fine: (let [c (chan)] (go (>! c (+ 41 1))) (<!! c)) But I thought go returns its own channel you could wait on? Per the https://clojure.github.io/core.async/#clojure.core.async/go

Returns a channel which will receive the result of the body when
completed
I thought this meant I could feed that channel directly to <!!. But I'm clearly misunderstanding that line or how it fits into async.

hiredman18:06:27

you can, I am not referring to the channel returned by the go block

hiredman18:06:39

you are are writing to a channel in the go block, but nothing is reading from the channel, so the go block waits for a reader, so the go block doesn't complete

🙌 2
hiredman18:06:58

so the channel returned from the go block never has anything written to it and is never closed

Ryan Tate18:06:15

Ahhhh thanks, it's a whole other channel, so this works: (<!! (go (+ 41 1)))

hiredman18:06:17

so taking from the channel returned from the go block also never completes

Ryan Tate18:06:54

right ok. thanks!! sorry am fairly dense on this.

noisesmith18:06:08

yeah, core.async is often counterintuitive, it's just more intuitive than the alternatives in its best use cases

👍 2
noisesmith18:06:27

it's a specialized tool

Karo20:06:12

Hi team, in an if statement if condition is true I must return "return true" value if false I need to call a function (in an example (+ 4 5)) before returning "return false" string (value of "x" does not related to the value that must be returned when the statement is false) I found a way of implementing this but do not think this is a good approach because I do not use "x" here. What is the idiomatic way of implementing described functionality?

(if false
  "return true"
  (let [x (+ 4 5)]
    "return false"))

Alex Miller (Clojure team)20:06:40

(if true
  "return true"
  (do
    (+ 4 5)
    "return false"))

Karo20:06:34

great thank you so much.

hiredman20:06:20

(or (and true "return true") (and (+ 4 5) "return false"))

phronmophobic20:06:18

this fails for examples where (+ 4 5) is something like (println "hello")

hiredman20:06:47

if that is a concern then I have an expression for you

hiredman20:06:33

(or (and true "return true") ((constantly "return false") (+ 4 5)))