beginners

Getting started with Clojure/ClojureScript? Welcome! Also try: https://ask.clojure.org. Check out resources at https://gist.github.com/yogthos/be323be0361c589570a6da4ccc85f58f.
2021-06-24T09:01:53.483900Z

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?

2021-06-24T09:08:10.484Z

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

borkdude 2021-06-24T09:16:13.484300Z

you could also call vec on it

bnstvn 2021-06-24T12:36:48.485800Z

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))

pavlosmelissinos 2021-06-24T13:08:22.485900Z

cond->

2021-06-24T13:09:40.486100Z

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).

1πŸ‘
bnstvn 2021-06-24T13:14:22.486300Z

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

pavlosmelissinos 2021-06-24T13:20:34.486600Z

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...

2021-06-24T13:21:07.486800Z

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

2
2021-06-24T13:22:29.487100Z

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

2021-06-24T13:23:34.487300Z

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

2021-06-24T13:23:42.487500Z

Or something, I'm on my phone

2021-06-24T13:24:27.487700Z

Are the calls different?

2021-06-24T13:26:29.487900Z

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.

2πŸ‘
2021-06-24T13:27:57.488100Z

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?”

bnstvn 2021-06-24T13:32:15.488500Z

yes, this let looks better, making the pattern obvious

bnstvn 2021-06-24T13:33:13.488700Z

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

2021-06-24T13:40:19.489Z

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.

2021-06-24T13:42:58.489300Z

Actually, use reduce

2021-06-24T13:43:07.489500Z

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

pavlosmelissinos 2021-06-24T13:52:20.489800Z

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

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

1πŸ‘
bnstvn 2021-06-24T13:56:54.490Z

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])

2021-06-24T13:58:49.490200Z

yea

2021-06-24T13:59:16.490400Z

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"

1πŸ‘
2021-06-24T14:02:36.490700Z

why avoid reduce? @pavlos

pavlosmelissinos 2021-06-24T14:18:27.491100Z

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

2021-06-24T14:20:09.491400Z

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

2021-06-24T14:20:45.491600Z

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.

tws 2021-06-24T15:16:02.492400Z

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

2021-06-24T15:49:19.492700Z

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

2021-06-24T15:50:49.492900Z

(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)"

2021-06-24T15:54:44.493300Z

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

1πŸ‘
2021-06-24T15:56:10.493500Z

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

2021-06-24T16:00:42.493800Z

I do it like this:

(defn exercise-case
  [x]
  (is (= 1 x)
    (str "is" x "= 1")))

(deftest exercise-1
  (exercise-case 1))

(deftest exercise-2
  (exercise-case 2))

(deftest exercise-3
  (exercise-case 3))
note that the second arg to is is printed on test failure, just like testing but it can be calculated on demand in its scope

seancorfield 2021-06-24T16:02:34.494300Z

I don't understand what you're asking here, sorry.

bnstvn 2021-06-24T17:03:57.494800Z

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?

2021-06-24T17:06:32.495200Z

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

2021-06-24T17:07:04.495400Z

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

bnstvn 2021-06-24T17:26:08.496400Z

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?))

2021-06-24T17:43:23.496700Z

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

2021-06-24T17:46:08.496900Z

;; 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)

2πŸ‘
Ryan Tate 2021-06-24T18:37:05.497400Z

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.

2021-06-24T18:38:37.498Z

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

2021-06-24T18:39:17.498500Z

the corrected version hangs just like you initial example did:

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

Ryan Tate 2021-06-24T18:40:11.499Z

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

2021-06-24T18:50:25Z

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

2021-06-24T18:51:06.000800Z

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

Ryan Tate 2021-06-24T18:53:40.002600Z

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.

2021-06-24T18:54:27.003100Z

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

2021-06-24T18:55:39.004400Z

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

1πŸ™Œ
2021-06-24T18:55:58.005200Z

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

Ryan Tate 2021-06-24T18:56:15.005900Z

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

2021-06-24T18:56:17.006100Z

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

Ryan Tate 2021-06-24T18:56:54.006700Z

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

2021-06-24T18:59:08.007700Z

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

1πŸ‘
2021-06-24T18:59:27.007900Z

it's a specialized tool

Karo 2021-06-24T20:08:12.013600Z

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"))

alexmiller 2021-06-24T20:09:40.014200Z

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

Karo 2021-06-24T20:11:34.014300Z

great thank you so much.

2021-06-24T20:23:20.015200Z

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

2021-06-24T20:24:33.015800Z

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

phronmophobic 2021-06-24T20:25:18.015900Z

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

2021-06-24T20:25:47.016100Z

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