Fork me on GitHub
#clojure
<
2022-02-17
>
mx200004:02:30

Should 'def' really never be used other than top-level ? I am developing a graphical application and some vars will be intitialised in the main thread with graphics context. So I am declaring lots of vars, then the initialisation function which is called with the context sets the var roots with alter-var-root. I could also use def inside the initialisation function?

kokonut04:02:04

I believe it is not quite clojure-conventional, but if it works for your purpose and creates no issues like performance, why not?

mx200004:02:56

Thats my question - why not ?

Alex Miller (Clojure team)05:02:30

Just use intern if you're dynamically making vars

hiredman05:02:17

Dunno exactly what you are doing, but sounds like a bad idea

hiredman05:02:39

def'ing stateful things is bad

kokonut05:02:26

More strictly speaking, it wouldn't be a good practice because def creates "a global var with the name of symbol" according to the clojure-docs. Why would we declare global var attached to its namespace within a function or expression. If the purpose is to hold a internal state, it makes a better sense to use let. For global level state, I would simply use a var that has atom.

kokonut05:02:54

In the meantime, Racket okays define within function. It is the same as let though, just syntax diff.

Martynas Maciulevičius08:02:21

I sometimes do tests which reload app's generated state. And in that case it's sometimes useful to have a def inside of a defn :

(defn reload-my-value! []
  (def my-stuff _)
  )
;;And then generate that value for regular execution:
(reload-my-value!)

Joshua Suskalo11:02:14

You could replace that in your tests with using the with-redefs macro, which temporarily redefines the vars within a dynamic scope.

Martynas Maciulevičius12:02:21

I know but the def bit creates a very large blob and it's also stateless. So I decided to create it only once. It creates quite a large handler matcher. I.e. the point of this is to provide code reloading for tests.

mx200004:02:03

It works the same, but is considered bad style ?

phronmophobic04:02:07

As always, it depends: • Are the values you're setting from the main thread immutable values? • Why not set the values outside of the main thread? • Does the state need to be accessed outside of the main thread? • Have you considered using something like https://github.com/stuartsierra/component or https://github.com/tolitius/mount? Also, if you provide more info and context about your use case, we may be able to offer more specific help. • What type of application are you building? • Which GUI library are you using (swing, seesaw, cljfx, etc)?

mx200004:02:15

These values are dependent on the OpenGL context, I am using libgdx library for a game. It looks like this:

(declare texture-a, viewport-b, camera-x)

(defn on-create []
  (alter-var-root #'texture-a (texture....))
  (alter-var-root #'viewport-b (...))
...

mx200005:02:34

It can be only set during the application-thread because of the context requirement. on-create is called by the application when starting with the right context.

mx200005:02:03

(def on-create []
  (def texture-a (texture...))
  (def camera-x (camera ...))
  (def viewport-b (viewport ...))

mx200005:02:10

This works the same, but I just wonder why it is bad style

phronmophobic05:02:15

There are issues with global state, but for this particular use case there may also be thread-safety issues. I assume that some of these values should not be accessed outside of the main thread.

phronmophobic05:02:55

Not sure how you're consuming libgdx, but their examples seem reasonable for showing how to initialize and deal with state within the context of libgdx, https://libgdx.com/wiki/jvm-langs/using-libgdx-with-clojure

mx200005:02:42

There are no thread -safety issues as the variables are only initialised once ( create is only called once ). Let-ting over the gamestate is not really a possibility for bigger applications

phronmophobic05:02:37

you'd be surprised

mx200005:02:50

I guess it's just a question of code style

phronmophobic05:02:31

are texture, camera, viewport, etc read only? or are they mutable? If they're mutable, are their thread safety issues if they're mutated outside of the main thread?

phronmophobic05:02:27

Generally, I would claim that global state is worse for large applications. For smaller applications, it doesn't really matter as much.

mx200005:02:52

Thank you, I will consider

phronmophobic05:02:20

fwiw, it does seem like the type of thing you can refactor later if needed.

Joshua Suskalo11:02:17

@U0ALH6R89 you could also make those dynamic variables and use binding to give them a value inside the dynamic scope of your application

didibus04:02:05

Funny how much harder I find doing leetcode is in Clojure, but day to day programming working on business problems I find so much easier in Clojure 😛

💯 4
kokonut05:02:10

Oh, does leetcode provide Clojure? When I checked a few months ago, I only saw Racket but no clojure .

didibus05:02:01

No it doesn't in the browser, but I just do the questions in my own editor.

💯 1
jaihindhreddy11:02:47

I'm sure day-to-day is much easier with Clojure (although I haven't used Clojure in anger yet), but I find it to be easier on Leetcode as well for most problems (compared to Python). Any particular ones you found Clojure difficult to work with @U0K064KQV?

didibus02:02:33

I might just be bad at problem solving algos without mutation and functionally. The ones where say I have to iterate in reverse over an array. Or ones where you need to track the location of things inside an array as well as the value.

Dumch13:07:08

Sorry for disturbing, but they deleted previous one. Please, vote one more time https://leetcode.com/discuss/feedback/2355129/Clojure-support-please!-Interesting-notes-inside

👍 1
cddr10:02:48

Is it possible to somehow configure the names assigned to the parameters in a class method generated by gen-class ?

p-himik10:02:32

You mean the :methods argument? No, because argument names are not a part of signature. But the implementation can use any names it wants.

p-himik10:02:30

Like in this example with setLocation [String] and -setLocation [this loc]: https://clojuredocs.org/clojure.core/gen-class#example-542692d3c026201cdc326fbb

cddr10:02:38

Yeah, the audience of this is potential users of the generated class files who might be using other languages. We’re trying to provide a better experience to our kotlin devs than [Object var1, Object var2]

p-himik10:02:39

I think var1 etc don't even come from Clojure - that's just how your viewer, whatever you're using, is assigning names. In bytecode, argument names don't exist at all (unless debugging information is provided, which I don't think is possible to include for gen-class). And gen-class doesn't generate anything but bytecode. That's why you'll see stuff like var1 all over the place when decompiling Clojure class files as Java code. Same deal in Java:

jshell> String.class.getDeclaredMethods()[5]
$6 ==> public void java.lang.String.getChars(int,int,char[],int)

jshell> String.class.getDeclaredMethods()[5].getParameters()
$7 ==> Parameter[4] { int arg0, int arg1, char[] arg2, int arg3 }

jshell> String.class.getDeclaredMethods()[5].getParameters()[0]
$8 ==> int arg0

jshell> String.class.getDeclaredMethods()[5].getParameters()[0].getName()
$9 ==> "arg0"

p-himik10:02:38

Maybe you'll have more luck with it if you rewrite gen-class in a .java file and compile it with debug information. Or provide the .java file as part of the distribution and let clients of other languages figure parameter names themselves. Maybe some Clojure libraries that serve as alternatives to gen-class can let you stay in Clojure while creating Java classes with debug information included.

Alex Miller (Clojure team)12:02:10

My recommendation if you want to provide an api for other jvm langs is to write Java interfaces in Java, then implement those in Clojure

👍 1
Alex Miller (Clojure team)12:02:50

You can use the Clojure Java API to bootstrap into that with a small amount of Java code (or translate to other langs)

Alex Miller (Clojure team)12:02:42

If you makes the Java interfaces, you have a hard target API, all the jvm langs should be able to work to that, you can gen javadoc, etc

Alex Miller (Clojure team)12:02:29

I've done this with Java several times, but I think the same principle would apply for Kotlin.

lread13:02:06

@U064X3EF3 Using the technique https://clojure.org/reference/java_interop#_calling_clojure_from_java, yeah? Then generate classes for that very thin interface? (I ask because I've seen folks run into problems, maybe accidentally, aoting thicker interfaces)

Alex Miller (Clojure team)13:02:23

Yeah. I don't know of any issues like that.

lread13:02:35

The scenario I've seen is a Clojure library that wants to be useable from java. So the lib naively uses gen-class and aots. But then the aoting brings in other classes and the jar ends up with many classes that aren't related to the desired thin interface. And these other classes cause conflicts and issues. (BTW, I'm not suggesting I understand anything here, just trying to learn if this scenario is real).

Alex Miller (Clojure team)13:02:23

Well that's not what I'm suggesting. You don't need gen-class at all

Alex Miller (Clojure team)13:02:00

You can aot compile your Clojure impl, but you don't even need to do that

Alex Miller (Clojure team)13:02:35

(Which means you can still interactively develop in the context of the app)

lread13:02:25

Yeah, I know, I got that, sorry, I think I'm hijacking this thread, I will go away now. simple_smile

joshkh11:02:33

greetings Clojurians! i have a question about profiling. i have a slow, top level function that calls many deeply nested functions, including some i/o, and i would like to find the bottlenecks. i don't care much about repeatedly profiling individual functions (such as use Tufte) - instead i would like to systematically report back the timing of each nested function call in one journey of the graph when the top level function is called. profiling tools seem like an obvious choice but i'm a little out of the loop. also, i'd like to capture the data from a deployed Ion where it may be tricky to attach a profiler (though i'm not sure). what's a good way to get started? many thanks.

Lennart Buit11:02:21

(Not sure how that works with Ions tho)

joshkh11:02:28

i'll take a look @UDF11HLKC, thanks for your help

Ben Sless11:02:41

If it's possible to run those functions locally then async profiler is the way to go. I'd also try to separate the IO and CPU parts to figure out if the slowness is due to program structure (blocking and waiting for things sequentially) algorithm (is there a better solution with regards to complexity) or non optimal implementation

🙌 1
joshkh17:02:10

coming back to this thread, https://github.com/clojure-goes-fast/clj-async-profiler is pretty great and the flamegraphs are useful, but i'm specifically looking to capture the execution time (not CPU) of each nested function call. any ideas?

vlaaad18:02:02

I don't do exactly what you want when profiting, but what I do is I compose clj-async-profiler and criterium at the repl

vlaaad18:02:43

So I profile the benchmarking process, and the result is a flamegraph with relative cpu usage + absolute numbers of total execution time

joshkh18:02:49

criterium warms up and repeatedly stress tests the expression, is that right?

Ben Sless18:02:03

There are some tracing tools you can use, but they won't instrument all of your code automatically, see mulog for example https://github.com/BrunoBonacci/mulog

Ben Sless18:02:27

You can still trace interesting places

joshkh18:02:58

hmm. my function graph has lots of i/o so i'm not interesting in stress testing them, just measure a typical journey through it. i started using https://github.com/technomancy/robert-hooke to add a hook with time to each function in various namespaces, since i want to do this automatically, but it started to become a project. i was hoping a tool existed. darn.

Ben Sless18:02:08

I seem to recall there was something like that, hang on I'll search

joshkh18:02:08

oh this might be what i'm looking for! i'll take a closer look when i'm back on the clock. thanks @UK0810AQ2

Jo Øivind Gjernes12:02:05

Is there any performance difference between #(do-stuff %) and (fn [one] (do-stuff one)) ?

Jakub Holý (HolyJak)12:02:08

I don't expect so. It is simply IMO a reader macro that end up (fn [gen_name1, ...] body) - see https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/LispReader.java#L896

Jakub Holý (HolyJak)12:02:55

On a related note, any idea what is the reasoning for preferring partial over an anom. fn https://guide.clojure.style/#partial ? @bozhidar?

Jo Øivind Gjernes12:02:24

One argument in favour of partial is that you immediately know where the argument will be applied. With #() you can get a lot of complicated permutations

bozhidar12:02:04

Yeah. Also arguably it can simplify a bit the code and is the "more functional" way of doing things. There's no strong reason to prefer it, though.

p-himik12:02:05

But also, partial is still useful when you have a function with many arguments and you want to bind just one. Compare (partial f 1) to #(apply f 1 %&).

❤️ 1
bozhidar12:02:05

Good point. It'd be nice if we include more examples in the guide.

p-himik12:02:03

There are still downsides though, like having an extra idiom and having an extra item in your stacktrace that doesn't even point to your code. It also becomes much less obvious how many arguments the resulting function should receive. I myself prefer #(), except for maybe that case above, which I fortunately almost never need.

👍 3
Jo Øivind Gjernes12:02:18

With write once, read many times, i rarely think #() is nicer than (fn [named-argument])

2
☝️ 1
Alex Miller (Clojure team)12:02:56

Yeah, I almost always prefer fn these days first, then #(), then rarely partial

👍 3
tatut13:02:50

#() usually when it's very small, never use if for things where the code spans multiple lines

Jakub Holý (HolyJak)14:02:48

So perhaps we should modify the style guide to prefer #() or (fn)? I know partial is the more functional way (that's why I like it) but it is long, you don't know how many arguments it will take, and it is not the Clojure way, according to Rich and Alex. What do you think, Bozhidar? Regarding #(), https://stuartsierra.com/2019/09/11/clojure-donts-short-fn-syntax says "Use #() only for a single function or method call." which resonates with me.

lassemaatta14:02:54

one difference (correct me if I'm wrong) is how (or rather when) higher order functions (partial, comp etal.) evaluate the arguments compared to an anonymous function. I've had several confusing problems related to stuff like (partial satisfies? SomeProtocol) or using partial with instrumented functions

p-himik14:02:19

In your example it shouldn't matter. But if you have something like (partial f (get-val)) as opposed to #(f (get-val) %) then yeah, there's a difference. Another potential issue is when you redefine your vars. So if you use alter-var-root, (partial ...) won't be affected but #(...) will be.

lassemaatta14:02:48

my memory is hazy (and I'm having the flu), but I have a recollection that you can accidentally stumble on that "redefining vars" territory without realizing it. I think (again, I may be wrong) enabling instrumentation does it and also extending procotols can "alter" the protocol, whereby it no longer satisfies the value captured by partial.

p-himik14:02:57

Oh, hm, that's right. I didn't realize that extend works differently from using the protocol when calling defrecord/`deftype`. Thanks!

Alex Miller (Clojure team)14:02:20

yes, partial closes over the function value, #() will refer to the var (so you retain the indirection)

Alex Miller (Clojure team)14:02:45

partial can also do this of course if you use #'f instead

slipset17:03:20

The one argument I use for partial is that whenever I see (partial… I know what’s going on, when I see #(… or (fn [… I know I need to write the full thing to understand it. In other words. partial very clearly communicates the intent.

👍 1
Alex Miller (Clojure team)17:03:59

For me, it's the reverse :)

1
cyppan13:02:13

Is there a naming convention to make it clear that a function returns a future or a promise? Like my-fn-f ?

Jakub Holý (HolyJak)14:02:35

Not really IMO. If appropariate, I include -promise or -f[uture] in the name.

👍 1
cyppan14:02:04

I usually write schemas for input / outputs of my functions, but how to introduce the concept of promises/futures in schemas :thinking_face: I don’t think it’s supported in Prismatic, malli, or spec

Jakub Holý (HolyJak)15:02:29

no, b/c those are opaque things

cyppan20:02:53

It is not really opaque in the sense it is a promise of a data structure shape, I would like to have the same expressivity than what we have with type systems, where you could have a Promise[Account] for the return type of a function

👍 1
Simon13:02:23

Refactoring ideas?

(condp <= v
    10E12 (str (.toPrecision (/ v 10E12) 3) "T")
    10E9 (str (.toPrecision (/ v 10E9) 3) "B")
    10E6 (str (.toPrecision (/ v 10E6) 3) "M")
    10E3 (str (.toPrecision (/ v 10E3) 3) "k")
    v)

Ben Sless13:02:45

you might find this cute or terrible

(defn order-of-magnitude
  ^long [x]
  (long (Math/floor (Math/log10 x))))

(def quantifiers
  {1 "K"
   2 "M"})

(defn quantify
  [x]
  (get quantifiers (quot (order-of-magnitude x) 3)))

Ben Sless13:02:16

another option

(let [[x s]
      (condp <= v
        10E12 [ 10E12 "T"]
        10E9 [10E9 "B"]
        10E6 [10E6 "M"]
        10E3 [10E3 "k"]
        v)]
  (str (.toPrecision (/ v x) 3) s))

Felipe Cortez14:02:43

looks worse to be honest, but at least there's no repetition

(reduce (fn [v [mag unit]]
            (if (<= mag v)
              (reduced (str (.toPrecision (/ v mag) 3) unit))
              v))
          v
          [[10E12 "T"] [10E9 "B"] [10E6 "M"] [10E3 "k"]]

p-himik14:02:02

That can be replaced with some, as in my reply in #clojurescript

p-himik14:02:41

@UK0810AQ2 That will blow up on the default condp clause. ;)

Ben Sless14:02:55

I told myself I need to remember to return a pair there and forgot

flowthing14:02:34

with-precision?

p-himik14:02:53

That works only for BigDecimal. And .toPrecision is actually from JS. Also converting to a string is not a numerical operation, so it ignores with-precision in its entirety.

flowthing14:02:41

Ah, all right, maybe not then. 🙂 I don't think I've ever had the occasion to use with-precision, actually.

p-himik14:02:39

Same, I just went through its source to make sure. :)

👍 1
cyppan14:02:04

I usually write schemas for input / outputs of my functions, but how to introduce the concept of promises/futures in schemas :thinking_face: I don’t think it’s supported in Prismatic, malli, or spec

pinkfrog14:02:58

Does clojure support separating a number to make it more readable? e.g., something like 100_000, or 100'000?

Alex Miller (Clojure team)14:02:34

we have a ticket about this, not sure we'll ever do it

👀 1
p-himik14:02:59

FWIW I'd probably use (int 1e5), unless it's not a round number.

p-himik14:02:29

Although I'm not entirely sure that there are no values for which it'll end up in a number that's one less than needed...

borkdude15:02:25

or:

user=> (int (math/pow 10 5))
100000

borkdude15:02:08

you can probably make a macro which turns symbols like i100_000 into values at compile time :)

Alex Miller (Clojure team)15:02:45

not sure you'd retain primitiveness that way

hiredman16:02:17

Re: pow, you can just use scientific notation

hiredman16:02:06

(as is done earlier)

Alan Thompson18:02:37

I would like to give a big shout-out and huge "Thank you!" to Sean Corfield (@seancorfield ) for the excellent work on next.jdbc. In particular, https://cljdoc.org/d/seancorfield/next.jdbc/1.2.659/doc/getting-started/tips-tricks#working-with-json-and-jsonb for getting the JSONB datatype working between CLJ<->Postgres were detailed, complete, and worked on the first try. Wow!

13
gratitude 9
seancorfield19:02:41

Thanks -- but I can't take any credit for the PG docs: they've all been contributed by PG users (which I'm not). The community are gratitude awesome gratitude !

mkvlr19:02:51

is there anything besides java.util.regex.Pattern that I can be read and does not have value-based equality in Clojure?

hiredman19:02:34

functions are not readable

mkvlr19:02:39

this is only after read

hiredman19:02:03

user=> (read-string (pr-str (fn [] )))
Execution error at user/eval156 (REPL:1).
No reader function for tag object
user=>

hiredman19:02:32

with tag readers, the reader can produce anything

hiredman19:02:47

user=> #java.lang.Object[]
#object[java.lang.Object 0x6df7988f "java.lang.Object@6df7988f"]
user=>

hiredman19:02:38

user=> (read-string "#java.net.Socket[]")
#object[java.net.Socket 0x3241713e "Socket[unconnected]"]
user=>

jeff.terrell19:02:22

Aren't functions readable because e.g. (read-string "#(- x)")? Of course they don't round-trip from being printed to being read in as the same thing, but I didn't think readability required that.

Alex Miller (Clojure team)19:02:21

can I back up and ask why you asked for this in the first place?

vlaaad19:02:33

what if you write a macro that annotates the defined function with its form and insructs print-method to print the form?..

mkvlr19:02:23

@U064X3EF3 using the read forms (in the case they don’t define a var) as edges in Clerk’s dependency graph and discovering regular expressions being a first thing that makes this seem like a bad idea

hiredman19:02:06

user=> (type (read-string "#(- x)"))
clojure.lang.Cons
user=>
not a function

hiredman19:02:06

its a list that evaluates to a function

👍 1
mkvlr19:02:08

(I’m using edamame to read so anonymous functions return consistent forms from read)

Alex Miller (Clojure team)19:02:13

seems like giving up control over equality in your primary data representation might generally be suspect

hiredman19:02:00

the fun about reading vars #'foo is it doesn't return a var

Alex Miller (Clojure team)19:02:34

seriously though, that could easily change in 1.12

Alex Miller (Clojure team)19:02:56

well, I guess we'd probably use a tagged form for rehydrating vars, so maybe not

hiredman19:02:27

user=> (type (read-string "#'foo"))
clojure.lang.Cons
user=> (read-string "#'foo")
(var foo)
user=>

Alex Miller (Clojure team)19:02:13

he (or she) who controls the reader, controls the world

Alex Miller (Clojure team)19:02:49

but back to the matter at hand, seems like there are several ways to handle this - either by wrapping into your own type or using a protocol with default impl but with the ability to override per other cases etc

andy.fingerhut19:02:39

Not sure whether ##NaN is in this category you are asking about, or not.

mkvlr19:02:04

and then walk the form again before I hand it to eval?

andy.fingerhut19:02:44

This article mentions the Regex pattern case, ##NaN, and a few others I'm not recalling at the moment, but not sure whether they are relevant for your use case: https://clojure.org/guides/equality

vlaaad19:02:15

How about {clj/eval clojure.core/eval} in data_readers.clj? 😄

hiredman19:02:27

yeah, that is what it means for the reader to be extendable with tag literals, it can return any type

hiredman19:02:19

and that is without going into the most verboten two words in clojure

😆 1
mkvlr19:02:58

I’ll try the protocol, thanks everyone for the help!

Joshua Suskalo19:02:26

Not quite directly related to the reader but please don't use java.net.URL if you can help it, especially not with clojure data structures. It has a hash function that depends on making a DNS lookup so long running processes, systems with inconsistent or fragile internet connections, or also just anything that happens to run around when a dns cache gets evicted can see odd behavior like maps with two keys using "the same" url.

3
phronmophobic21:02:08

instead of read-string, you could use something like (memoize read-string) and guarantee that reading the same form will give you the same value

borkdude21:02:10

it was already solved using edamame which supports reading regexes like :regex #(list 're-pattern %) which is guaranteed to result in the same hashing

🙌 1
borkdude21:02:27

might want to use clojure.core/re-pattern fully qualified though

👍 2
Drew Verlee04:02:08

@U0NCTKEV8 > and that is without going into the most verboten two words in clojure The use of the word verboten has made this to much of a mystery for me. What are these forbidden words? Do they grant grate power but take a terrible toll?

hiredman04:02:23

riny ernqre

👆 1
1