Fork me on GitHub
#beginners
<
2018-10-25
>
Ben Grabow04:10:58

Hi all, I'm working through Clojure for the Brave and True ch. 6, and I'm seeing behavior I don't understand when I run the final code for the-divine-cheese-code exercise. When I lein run the app it does its work pretty quickly, but then my bash prompt hangs for about a minute before the java process shuts down. If I remove the call to clojure.java.browse/browse-url then the java process closes right away when the app's work is done. Is there a reason browse-url is supposed to keep the java process alive after it's done?

4
Ben Grabow04:10:14

Here's the code that is running. If I run this I get "All done!" in the console promptly, but the java process stays alive for a minute afterwards.

dangercoder04:10:54

Maps = preferable over passing several parameters around? I like maps. like.. [game-info] vs [owner-id owner-token player-id player-token game-identifier] Personally, yes parrot

seancorfield04:10:42

@ben.grabow Add a call to (shutdown-agents) at the end of your -main function.

👍 4
Ben Grabow04:10:15

Adding (shutdown-agents) does the trick. What's going on behind the scenes?

seancorfield04:10:47

Certain Clojure construct fire up thread pools in the background and some of them tend to stay "busy" for up to 60 seconds at the end of the JVM shutting down. But if you call (shutdown-agents) explicitly, it shuts those thread pools down quickly.

seancorfield04:10:24

(as far as I understand it -- I pretty much have to add (shutdown-agents) at the end of every non-trivial -main function these days!)

Ben Grabow04:10:34

I'll have to remember that, thank you @seancorfield!

CyberSapiens9705:10:37

guys, how would you test a function, that the input is too big for unit tests, and using spec to generate your input have a problem, because the input is a nested map, and you're extracting some nested value, and this can be either one item from a set of specific values or a empty map. so you can't possible know if you extracted the right one, using a 'random' generated data from spec. Should i create a function that generates the value that i want on the specific key, and test it against my original function that extracts it?

CyberSapiens9705:10:55

or i could generate the data from the spec and change the key/value that i want, and use it to unit test my function...

CyberSapiens9705:10:23

i guess the latter is less complicated

CyberSapiens9705:10:46

because i already have the spec for the data

seancorfield05:10:31

@cybersapiens97 My initial reaction is that maybe your function is too complicated and/or has too much knowledge of the whole data structure... could you refactor the code to have a function that only operates on part of the data structure? i.e., you pass it just a portion of the whole map instead.

CyberSapiens9705:10:26

[{:line-number 1 :line-columns {:a {} :b {}  :c {} :d {} :e {} :f {} :g {} :h {}}}
 {:line-number 2 :line-columns {:a {} :b {}  :c "Something" :d {} :e {} :f {} :g {} :h {}}}
 {:line-number 3 :line-columns {:a {} :b {}  :c {} :d {} :e {} :f {} :g {} :h {}}}
 {:line-number 4 :line-columns {:a {} :b {}  :c {} :d {} :e {} :f {} :g {} :h {}}}
 {:line-number 5 :line-columns {:a {} :b {}  :c {} :d {} :e {} :f {} :g {} :h {}}}
 {:line-number 6 :line-columns {:a {} :b {}  :c {} :d {} :e {} :f {} :g {} :h {}}}
 {:line-number 7 :line-columns {:a {} :b {}  :c {} :d {} :e {} :f {} :g {} :h {}}}
 {:line-number 8 :line-columns {:a {} :b {}  :c {} :d {} :e {} :f {} :g {} :h {}}}]

CyberSapiens9705:10:39

hmm i think it's easy if i break it into more functions, but i don't see how this would make the test easier

CyberSapiens9705:10:00

for example, you would receive [vector-above line-number line-column], in this case [vector-above 2 :c] => "Something"

CyberSapiens9705:10:50

doing the function is easy, testing without passing the entire structure for checking if it's the right thing is the difficult part for me now

CyberSapiens9705:10:46

it's ugly to use this entire structure on a unit testing, but will be enough for now...

seancorfield05:10:10

So the function only operates on "Something"?

seancorfield05:10:00

and something else (the caller?) does (get-in vector-above [1 :line-columns :c]) to navigate to that (line number 2 is the 1th entry, line number 1 is the 0th entry)

CyberSapiens9705:10:53

the function simply extracts any given line-number and column and whatever is inside of it, didn't designed yet but the data will be either an empty map or something else

CyberSapiens9706:10:28

btw this is me trying to do a chessboard lol

CyberSapiens9706:10:26

and i'm realizing that :line-number key isn't needed because i'm using a sorted vector...

CyberSapiens9706:10:47

and spec'ing data before it's final shape seems too much time consuming

seancorfield06:10:08

get-in is all you need to navigate that -- you don't need a custom function.

seancorfield06:10:16

Simplify. Rely on built-in functions.

seancorfield06:10:41

Operate on the smallest piece of data possible and separate navigation from transformation.

CyberSapiens9707:10:57

nice advice, thanks. i tend to make things complex without need

andy.fingerhut07:10:19

Anything that calls clojure.core/future causes this. Several of them are listed here: http://clojuredocs.org/clojure.core/future

andy.fingerhut21:10:19

Probably this line in browse-url, used to do the opening of the URL on Mac and Linux. clojure.java.shell/sh does use future internally, here: https://github.com/clojure/clojure/blob/4ef4b1ed7a2e8bb0aaaacfb0942729252c2c3091/src/clj/clojure/java/shell.clj#L119

TeMPOraL07:10:13

geez, call it a brainfart, took me a day to realize that (is (every? true? (map = [1 2 3] some-seq))) could be rewritten as (is (= [1 2 3] some-seq))

andy.fingerhut07:10:40

And actually the second version is more restrictive than the first.

andy.fingerhut07:10:59

The first one, if some-seq contains 4 or more elements, will pass if the first 3 elements are 1 2 3

andy.fingerhut07:10:07

The second will fail if some-seq contains anything other than 3 elements.

andy.fingerhut07:10:45

because when you call map with 2 or more sequences, it stops when it reaches the end of the shortest sequence.

TeMPOraL07:10:36

right, didn't even think of that (even though I'm pretty sure I used this attribute of map in the past)

TeMPOraL07:10:54

I still sometimes forget that Clojure defaults to value comparison on =; too much time spent with Java and Common Lisp :<

andy.fingerhut07:10:34

Both of your examples rely on =. Do you mean you are unused to being able to compare arbitrarily nested collections?

TeMPOraL07:10:31

yes, I'm mostly used to reference comparisons for anything other than primitive values

andy.fingerhut07:10:46

Yes, = and many other Clojure operations working on arbitrarily nested immutable data structures is an awesomely nice thing. You can even use them as set elements or keys in maps.

TeMPOraL07:10:35

what do you mean by "them" in: > You can even use them as set elements or keys in maps. ?

TeMPOraL07:10:38

the data structures?

andy.fingerhut08:10:01

You can use a vector, or a map, or a set, as a key inside of a map, as well as a value in a map.

andy.fingerhut08:10:27

e.g. { [1 2] :alive [7 4] :dead} is a map that has vectors as keys

TeMPOraL08:10:03

cool, though... I'm having a hard time imagining a use case right now

andy.fingerhut08:10:08

It isn't always useful to do so, but if you can think of a use for it, it works.

TeMPOraL08:10:10

i.e. when would I like to have a vector as a key?

andy.fingerhut08:10:36

One use case would be in representing a 2-D or 3-D sparse structure, where the vectors represent positions

TeMPOraL08:10:52

hm. yeah, that makes sense

andy.fingerhut08:10:56

and the values represent objects located at those positions.

TeMPOraL08:10:00

memoization in general would probably be another one

TeMPOraL08:10:12

i.e. vector of function arguments as key in a map

andy.fingerhut08:10:35

Yes, Clojure has a built-in memoize function that can take any pure function that takes 1 or more immutable values as arguments, and memoize it.

andy.fingerhut08:10:17

and it is implemented just as you describe

TeMPOraL08:10:43

hmm, just thought of another use case - treating keys as opaque objects

TeMPOraL08:10:15

i.e. I may have a map that's mapping a "person" to some "stuff" and not care what that "person" actually is, beyond that it can compare with =

TeMPOraL08:10:30

and some other part of the program knows that "person" is actually a collection

TeMPOraL08:10:54

I must say, the more I work with clojure, the more I like it

andy.fingerhut08:10:56

You need to be cautious not to use any mutable stuff inside of such a collection if you want to use it as a set element or map key, because then the hashing and = can break down, but Clojure's default immutable data structures makes it easy to avoid that, unless you are doing Java interop or things like that.

TeMPOraL08:10:45

yeah, I'm actually doing Java interop a bit in a project I'm working on (that made me use clojure in the first place), and I'm generally isolating it as much as I can

TeMPOraL08:10:55

i.e. clojure data structures come in, clojure data structures come out

TeMPOraL08:10:05

and java interop happens in a bunch of specific functions

andy.fingerhut08:10:30

In some cases I think that people write conversion code to convert Java mutable structures to Clojure immutable ones at the boundaries.

andy.fingerhut08:10:39

where it makes sense to do so.

TeMPOraL08:10:14

yes, that's what I do at the moment

jaihindhreddy-duplicate10:10:43

Inverse of transients, so to speak.

TeMPOraL11:10:57

hmm, in Common Lisp, there's a difference between (apply 'foo bar) and (apply #'foo bar) - the former will always call the function currently named foo, and the latter will call the function that was the value of #'foo when that expression was evaluated

TeMPOraL11:10:06

how's this done in Clojure?

TeMPOraL11:10:30

I understand that (apply func data) in Clojure is the equivalent of CL's (apply #'func data)

Alex Miller (Clojure team)12:10:00

For the other, you could probably use @#’func (deref the var to get the function at a point in time), but I’m not sure why I would ever do that

henrik12:10:05

@alexmiller I’ve done that with routes in the past, when I want them reloadable in dev mode.

TeMPOraL12:10:50

yup, in CL you'd do that in places where you store functions that you want redefinable at runtime - say hooks

Alex Miller (Clojure team)12:10:29

Just make a dynamic var?

TeMPOraL12:10:23

@alexmiller let me give you an example from current Clojure project I'm working on - we have a key -> handler map that's floating around in a long-lived application

TeMPOraL12:10:44

say I add a some-key -> some-fun entry there, because the handler I want to use is a named function

TeMPOraL12:10:01

then, at some point, I redefine some-fun via REPL

TeMPOraL12:10:59

depending on the design, I might want for that map to pick up that the definition of some-fun has changed; however, if I naively pass some-fun to that map, it'll store the ref to a particular function definition at particular point in time

TeMPOraL13:10:35

in CL, the usual way this is handled is by using the fact that apply and funcall accept both a function and a symbol (together known as "function designator")

TeMPOraL13:10:57

if you apply a function, you'll call that particular function; if you apply a symbol, the call will go to the function currently defined under the name represented by that symbol

schmee13:10:07

@temporal.pl sounds like a use-case for an atom

schmee13:10:32

you can pass the atom to your function and deref it on each call to get the possibly updated value

joelsanchez14:10:15

sorry but isn't this just prepending #'?

joelsanchez14:10:18

user=> (defn a [] :a)
#'user/a
user=> (def m {:a #'a})
#'user/m
user=> ((get m :a))
:a
user=> (defn a [] :b)
#'user/a
user=> ((get m :a))
:b

joelsanchez14:10:43

you don't need atom for this...

Rafael14:10:11

Hi everyone. I need a help I converted a string to a JSON object, but now and I need get a value by the key. I have tried it using [cheshire.core :as json] and [clojure.data.json :as jsons] but whitout success. for example, {"language":"Clojure", "paradigm":"functional"} anyone knows how I can get the value by the key language?

narendraj914:10:29

(get {"language":"Clojure", "paradigm":"functional"} "language")

👍 4
narendraj914:10:43

Or you can use the map directly as a function and pass the key as an arugment.

narendraj914:10:49

({"language":"Clojure", "paradigm":"functional"} "language")

frenata14:10:08

above works great! even more idiomatic: (:language (cheshire.core/parse-string some-json true))

👍 8
frenata14:10:41

true being an optional param to cheshire that will change string keys to keyword keys

Rafael14:10:13

thanks!!! all options works fine. today is my third day working with Clojure. And my sixth months with Elixir. sometimes I`m get confused with two syntax .

lxsli14:10:10

Is there an empty? that works on non-seq please? Using (or (nil? i) (and (coll? i) (empty? i))) atm

souenzzo15:10:32

(empty? nil) return true

seancorfield16:10:54

Yes, but (coll? nil) returns false so he needs the (or (nil? i) .. test too.

Audrius15:10:13

why can't i have a result (1 array) or (array 1) ?

lxsli15:10:34

Are you after (vector 1)? If you want a Java array, try make-array or into-array?

Audrius15:10:57

clojure.lang.ArraySeq

lxsli15:10:33

I don't understand what you want

Audrius15:10:43

for example I can run (:key map) on a map, but why can i not run (0 array) on clojure.lang.ArraySeq ?

souenzzo15:10:43

0 is a java.lang.Number java.lang.Number cant implement clojure.lang.IFn because java.lang.Number is defined before IFn

Audrius15:10:55

can you see the analogy?

ianbishop15:10:57

This works because keywords are a function

ianbishop15:10:12

So it's more to do with keywords than it is to do with ArraySeqs

lxsli15:10:53

Yeah, basically keywords and maps are 'magic' because they implement IFn

lxsli15:10:32

get, get-in, assoc-in etc can handle numeric indices

noisesmith16:10:57

the cool thing about this is clojure is less "magic" than (:x m) makes it look - that's really the same syntax as a standard function call, the trickery is in a keyword being a function

noisesmith16:10:43

also

user=> ([:a :b :c :d] 1)
:b

🤯 8
andy.fingerhut18:10:29

Well, there are some integers, floating point numbers, characters, strings, namespaces, and symbols down there, too (not a complete list of non-function things) 🙂

deliciousowl18:10:27

eh, those are all ultimately just binary functions 🙂

Braden Shepherdson19:10:32

any idea why most of my files would work fine, but one loads without errors, but exports nothing?

Braden Shepherdson19:10:27

I just get "no such var" for all of its symbols.

andy.fingerhut19:10:26

Clojure on Java? If so, what method are you using to load your files?

Braden Shepherdson19:10:44

a mix of clj and cljc files. some of each type are working fine.

andy.fingerhut19:10:51

Also, the Eastwood lint tool can find some kinds of problems, e.g. mismatches between file names and the namespace defined within them that can cause problems for some tools.

andy.fingerhut19:10:42

Do you have any namespace or file name that is the same between two files, but one has a .clj suffix, and the other has .cljc? In such a case, Clojure/Java will ignore the .cljc file.

Braden Shepherdson19:10:03

I can (use '[zm.core :as c] :reload-all) at the repl and it works fine, but there's nothing loaded.

andy.fingerhut19:10:37

I think there is a :verbose keyword option for that, IIRC, that shows what is actually loaded.

Braden Shepherdson19:10:47

empty template-created core.clj file getting in the way T_T

Braden Shepherdson19:10:03

thanks, that was a baffling error.

andy.fingerhut19:10:29

Given that stack traces show the file name core.clj for functions in Clojure core, I would recommend considering avoiding the use of the file name core.clj in your own projects, to make it easier for you to figure out which file the functions in the stack trace come from.

Braden Shepherdson19:10:25

yeah, I've moved it to something more meaningful. I've still got a core.cljs but that's used in a bunch of places so I'm not going to touch it.

noisesmith20:10:33

a fun thing, is if you run lein new foo.bar/baz it will create foo/bar/baz.clj instead of a core.clj

Charlot23:10:24

Hello. I'm looking for a way to check if something is an error.

Charlot23:10:40

I tried using class and isa?, but that doesn't seem to work

noisesmith23:10:00

user=> (isa? (try (assert false) (catch Error e (class e))) Error)
true
- isa? knows that the class of an AssertionError is a descendent of Error

noisesmith23:10:24

Exceptions are different though

user=> (isa? (try (/ 1 0) (catch Exception e (class e))) Error)
false
user=> (isa? (try (/ 1 0) (catch Exception e (class e))) Exception)
true

Charlot23:10:37

(isa? (class *e) Error)
just returned false for me

Charlot23:10:50

and *e itself is not nil

noisesmith23:10:00

what's the class of *e

noisesmith23:10:16

it might be an Exception, for example

Charlot23:10:26

the class of the most recently thrown error or exception

Charlot23:10:42

Yeah so my orginal question is wrong

noisesmith23:10:45

I know what *e is, I mean in your specific context

Charlot23:10:52

I need one that does both error and exceptions

noisesmith23:10:10

you could check them individually, or check for Throwable which is superclass to both

Charlot23:10:11

clojure.lang.Compiler$CompilerException

noisesmith23:10:46

yeah, that doesn't derive from Error so isa? won't recognize the relationship, but checking both or checking Throwable would

noisesmith23:10:35

maybe this helps

user=> (isa? Exception Error)
false
user=> (isa? Exception Throwable)
true
user=> (isa? Error Exception)
false
user=> (isa? Error Throwable)
true