This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
when trying to debug some function, I'd often like to delcare that the return value must satisfy some predicate, so I can find where I'm accidentally returning the wrong kind of thing. Is there a idiomatic way of doing this? I.e., some sort of annotation on defn
or on fn
?
Postcondition perhaps? See https://clojure.org/reference/special_forms
ah yes, that will work for defn
.
how about for fn
?
or functions defined in letfn
?
the documentation of fn
and letfn
don't mention it. š
it is mentioned in the guide.
you can use macroexpand
to see if the form expands to fn
or not
Is the documentation for letfn
wrong?
https://clojuredocs.org/clojure.core/letfn
ā¢ The docstring says that that functions defined in letfn
DO NOT take prepost-map?
as in defn
. However, my experimentation shows that they do. Am I reading the docs wrong? or are they wrong?
likewise the docstring for fn
says that it does not take prepost-map?
, yet my experimentation shows otherwise.
I'm guessing the docstring is a historical holdover: https://clojure.org/reference/special_forms#fn explains that fn
(and macros that expand to fn
) allow for pre/post maps
this what one would guess, that fn
is the thing that implements the feature, not defn
the doc doesn't say it "do not take prepost-map?"
hmmm. OK, I was interpreting the message to mean fnspec must be one of the following, neither of which includes prepost-map.
How do you interpret the phrase? Is or
intended to be non-exclusive or
?
contrast this with defn
in which case it is unambiguous
exprs
expands to all tokens that fn
can accept according to the docstring. exprs => prepost-map? body
sorry, not sure what you are saying. are you telling me how letfn
works, or are you telling me what the documentation for leftn
states? Not trying to be a smart-ass, just trying to make sure I understand what you're saying.
in the docstring for letfn
:
fnspec
could be either (fname [params*] exprs) or (fname ([params*] exprs)+)
I was referring to exprs
mentioned there
ahhh, are you sure that the prepost-map is an expression? That would be surprising, I don't think it is evaluated? Is it?
now I don't understand what are you talking about
to me, expressions are forms in an evaluation position.
they express a value.
Anyway, I plan to put a comment in the docs showing an example of prepost-map
with letfn
and fn
, just to make it less ambiguous.
(defn foo [x]
{:pre [(pos? x)]}
(inc x))
;; expands to
(def foo
(fn*
([x]
(if (pos? x)
nil
(do
(throw
(new
java.lang.AssertionError
(str "Assert failed: " (pr-str '(pos? x)))))))
(inc x))))
what is not evaluated here?btw, :pre and :post are just a convenient way to avoid adding branches in your code. The same code you could write yourself with more precise logging
Hello all, I am trying to write to file using data that comes over a channel. The channel has been defined as following:
(def loglivedata_chan (chan 20))
My code looks like so:
(with-open [wrtr (clojure.java.io/writer "livedatalog.json")] ;; open the file handle
(go-loop [x (
when I put data into this channel on my repl, the following happens:
:> (>!! loglivedata_chan (json/json-str {:a 1 :b 2}))
true
:>
Log Live data listener is now dying...
Channel read as : {"a":1,"b":2}
nil
why is the go-loop exiting? I want it to wait for another item on the channel or quit only when the channel has been closed. Any suggestions?
I also get the following error dump:
; Exception in thread "async-dispatch-6" java.io.IOException: Stream closed
; at java.base/java.io.BufferedWriter.ensureOpen(BufferedWriter.java:107)
; at java.base/java.io.BufferedWriter.write(BufferedWriter.java:224)
; at java.base/java.io.Writer.write(Writer.java:249)
; at livedataproc.MultThread$eval17615$fn__17642$state_machine__12344__auto____17645$fn__17647.invoke(NO_SOURCE_FILE:32)
; at livedataproc.MultThread$eval17615$fn__17642$state_machine__12344__auto____17645.invoke(NO_SOURCE_FILE:32)
; at clojure.core.async.impl.ioc_macros$run_state_machine.invokeStatic(ioc_macros.clj:972)
; at clojure.core.async.impl.ioc_macros$run_state_machine.invoke(ioc_macros.clj:971)
; at clojure.core.async.impl.ioc_macros$run_state_machine_wrapped.invokeStatic(ioc_macros.clj:976)
; at clojure.core.async.impl.ioc_macros$run_state_machine_wrapped.invoke(ioc_macros.clj:974)
; at livedataproc.MultThread$eval17615$fn__17642.invoke(NO_SOURCE_FILE:32)
; at clojure.lang.AFn.run(AFn.java:22)
; at java.base/java.util.concurrent.Thre
Alternatively should I open the file handle inside the go-loop? What is the most idiomatic solution to this?I think it's not that the go loop is exiting, but rather that it's async, and thus parking and returning. That causes the println outside the go loop to run, and the with-open block to close, thus leading to the stream closed message. Note that the stream closed exception comes from an async thread. The file handle could be re-opened each time through the go loop, but I'd guess that it'd be more efficient to sort of "buffer" some writes. You can decide/test for how much buffering makes sense in your use case, but I'd maybe call a function that'll append 'stuff' to a file, buffer stuff in the go loop, and when the buffer is full, call the function with the buffer and clear it (or something like that).
You can assume when you create a go block (go or go-loop) it runs on another thread, so you are opening a file, launching another thread to do stuff with the file, and then without waiting for that launched thread to finish, closing the file
go slices your code up into callbacks, and those callbacks are run on a fixed size shared threadpool, so generally it is a bad idea to gum up the pool by doing blocking/potentially blocking operations like io or whatever (even just cpu heavy code on that pool can stop other callbacks from running in a timely manner)
ok. So I've refactored my code. I now have 4 threads 1. main thread/data generator 2. thread for reading data from thread 1 and putting into a buffer 3. thread that keeps and eye on the buffer size; if the buffer len is above a certain size, put it into a file
and this doesn't work for me
the error message is as follows:
; Exception in thread "async-dispatch-1" java.lang.IllegalArgumentException: No matching method write found taking 1 args for class java.io.BufferedWriter
; at clojure.lang.Reflector.invokeMatchingMethod(Reflector.java:154)
; at clojure.lang.Reflector.invokeInstanceMethod(Reflector.java:102)
; at livedataproc.MultThread$logfilewriter.invokeStatic(NO_SOURCE_FILE:62)
; at livedataproc.MultThread$logfilewriter.invoke(NO_SOURCE_FILE:60)
; at livedataproc.MultThread$fileiothreadhandler$fn__15599$state_machine__12344__auto____15604$fn__15606.invoke(NO_SOURCE_FILE:121)
; at livedataproc.MultThread$fileiothreadhandler$fn__15599$state_machine__12344__auto____15604.invoke(NO_SOURCE_FILE:108)
; at clojure.core.async.impl.ioc_macros$run_state_machine.invokeStatic(ioc_macros.clj:972)
; at clojure.core.async.impl.ioc_macros$run_state_machine.invoke(ioc_macros.clj:971)
; at clojure.core.async.impl.ioc_macros$run_state_machine_wrapped.invokeStatic(ioc_macros.clj:976)
; at clojure.core.async.impl.ioc_macros$run_state_machine_wr
In my newbie eyes it appears the file pointer doesn't work anymore after the first attempt. I get 146 data elements written to the file... then it crashes.any ideas what can be done?
> java.lang.IllegalArgumentException: No matching method write found taking 1 args for class java.io.BufferedWriter means that looking at the argument types clojure cannot find a method that matches on the target
a wild guess would be you are writing stuff you pull from a channel, and when that channel closes you get nil and try to write that and the reflector cannot find a .write method it can pass nil to
my code looks like following
(:require [ :as io])
(defn logfilewriter [data]
(with-open [wrtr (io/writer "livedatalog.json" :append true)] ;; open the file handle
(.write wrtr data)) ;; write to file
)
data is json entries
it comes from a webservice
I've run the code for logfilewriter with dummy json and it works
should I do something else?
besides if the data is changing then the error should be when I am agregating the buffer... no?
yes... the logfilewriter function is getting called from within a go-loop
I am assuming this is wrong
I've never worked with async... so please excuse my ignorance
it is wrong(you should not do io in a block as I have said), but it is not the cause of the stacktrace
ok.. so please can you clarify ... where should I do io instead?
the stracktrace doesn't show the call to logfilewriter at all, just go block -> .write method call
this other thread is a go block as well?
or what?
either use clojure.core.async/thread or something similar on a threadpool you control or something (make sure to wait for the result)
so not go block but a thread will do
thank you
let me try that instead of wasting your time
I'll also try to find .write in the rest of my code
would you be kind enough to explain to me how you are walking thru the stack trace?
BTW is future a thread too? or is it too close to go and not tobe used for io?
frames like clojure.core.async.impl.ioc_macros$run_state_machine.invokeStatic(ioc_macros.clj:972)
are running a go block
so I can do io in a future block.. right?
let me try that and see what happens
thanks for your help!
I hope I won't have to bother you again
thanks again
oh, my mistake, I missed this frame at livedataproc.MultThread$logfilewriter.invoke
which is your logfilewriter
but when you do io you need to wait for it complete in some way, otherwise you are just spinning up an unbounded number of threads to do io
so for future the way to wait for it to complete is to deref it, which blocks until the future completes, but when I say "don't do io in go blocks" what I really mean is "don't block in go blocks"
so futures are not suitable, which is why clojure.core.async/thread exists, it returns a channel which a go block can "park" on
so within teh go block if I call a future with logfilewriter... it should be ok...
if you don't wait for the future to complete that is fine, but calling future without at some point waiting for it to complete is an anti-pattern
so @<future(call to logfilewriter)>
is what you mean by deref... yes?
a bit confused there... you concur... yes?
oh don't do that
so what then?
ok... and this (async/<! (async/thread (logfilewriter ...)))
can be inside a go... yes?
ok thanks, let me try that
thanks @U0NCTKEV8 after the changes I don't get any errors
thanks to @U013JFLRFS8 too