Fork me on GitHub
#clojure
<
2018-04-03
>
arrdem00:04:44

Okay so I have a fun one for the room - is there a way to hint a clojure reify to emit a void method? I don’t know of one.

arrdem00:04:16

ah the hint goes on the method name?

tbaldridge00:04:05

And yes it’s strange

arrdem00:04:34

it works for now. Oh well.

arrdem00:04:00

One reflection warning down, lots to go.

Alex Miller (Clojure team)02:04:18

There’s a ^void you can use - I’ve run into this once or twice

Alex Miller (Clojure team)02:04:47

the bytecode is still not optimal though - it basically creates the return value and throws it away

tbaldridge03:04:03

@alexmiller does that affect performance? Seems like the JIT could remove that

Alex Miller (Clojure team)03:04:42

it does - I ran into this on an alioth program

Alex Miller (Clojure team)03:04:50

it’s not big, but it’s not 0

Alex Miller (Clojure team)03:04:06

admittedly, this was several jdks ago now though

Serge06:04:55

How to make graphql requests via clojure? I use Compojure/Ring

Denis G09:04:05

Does anybody know how to handle errors/exceptions in Clojure? especially if I have a function composition like

(->> (f x)
        (g)
        (h))
and every function can throw an error/exception, but I only want to call the next function if no error is thrown. Any ideas?

pesterhazy09:04:00

That's how exceptions work. They interrupt the flow of execution and exit all the call frames until they meet a matching catch block

pesterhazy09:04:13

But beware of laziness; if an exception occurs in a later element of a sequence, it may not be caught until some later processing has already occurred.

pesterhazy09:04:37

A simple fix is to sprinkle your pipeline with doalls

Denis G10:04:29

The question is whether or not this is a clojury way to do things

robert-stuttaford10:04:35

a rule of thumb i read a long time ago: - exceptions: the programmer did something wrong. e.g. assertions, network failure, etc. - errors (aka just a different kind of return data): the user did something wrong. exceptions do provide one nice feature; fast failure. e.g. buddy-auth uses exceptions to short-circuit auth failures to bypass the ring middleware stack quickly.

Denis G10:04:50

but there is no language construct for errors, right?

robert-stuttaford10:04:28

bottom line: errors are just another kind of data. the language doesn’t need to do something special. i spoke on this in my most recent post, with the check-n pattern: https://www.stuttaford.me/2018/03/31/bridge-dev-diary--events-api/

pesterhazy10:04:39

I went through a phase a few years ago when I thought that was more functional and thus better

pesterhazy10:04:01

These days I like to just throw around ex-info and be done with it

pesterhazy10:04:31

I suppose it depends on your specific case

Denis G10:04:33

@pesterhazy I was reading the good enough blog post right now 😄 Interesting ideas

robert-stuttaford10:04:50

one very frequent occurrence is for something to return nil instead of whatever you expected, and so we have some-> and some->> macros to handle early-stop-on-nil

👍 8
robert-stuttaford10:04:45

(if-some [val (some->> lookup-ref (d/entity db) :some/key)] … {:status 404}) sort of thing

pesterhazy10:04:48

it's only when you need to keep track of the kind of error you encountered when the more complex stuff is needed

pesterhazy10:04:01

e.g. to provide error messages to the user

robert-stuttaford10:04:14

yep. that’s what i’ve specifically decided to do in bridge

danm10:04:49

What's the pattern for mapping over a nested list? i.e. We have [[1 2 3] [4 5 6]] and we want as an output [[2 3 4] [5 6 7]]? mapcat would flatten the inner lists, which we don't want, but our existing (map #(map ...is just hideous

borkdude10:04:44

@carr0t That would be it yes. In other functional programming languages this might be expressed as repeated fmap and looks a bit less hideous, but they do it all the time.

x = [[2, 3, 4], [5, 6, 7]]
(fmap . fmap) (+1) x -- [[3,4,5],[6,7,8]]

borkdude10:04:29

it basically means: apply a function two levels deep into the structure

borkdude10:04:08

To make it read less hideous you could give the inner map function a name

pesterhazy10:04:38

(mapv (partial mapv fun) xs)

pesterhazy10:04:09

if that's ugly in your book, I'm happy to write ugly code

pesterhazy11:04:49

user=> (defn mapv2 [f xs] (mapv (partial mapv f) xs))
#'user/mapv2
user=> (mapv2 - [[1 2] [3 4]])
[[-1 -2] [-3 -4]]

carocad13:04:23

hey guys, does anybody know if it is possible to avoid getting two different exceptions whenever the compiler find an error? See example below. This gets really problematic the bigger the spec error is since you get two huge prints for a single error. Ideally I would like to limit it to a single error 🙂

(let [1 2])
clojure.lang.ExceptionInfo: Call to clojure.core/let did not conform to spec:
                                         In: [0] val: () fails spec: :clojure.core.specs.alpha/bindings at: [:args :bindings :init-expr] predicate: any?,  Insufficient input
                                         
clojure.lang.Compiler$CompilerException: clojure.lang.ExceptionInfo: Call to clojure.core/let did not conform to spec:
                                         In: [0] val: () fails spec: :clojure.core.specs.alpha/bindings at: [:args :bindings :init-expr] predicate: any?,  Insufficient input
                                          #:clojure.spec.alpha{:problems [{:path [:args :bindings :init-expr], :reason "Insufficient input", :pred clojure.core/any?, :val (), :via [:clojure.core.specs.alpha/bindings :clojure.core.specs.alpha/bindings], :in [0]}], :spec #object[clojure.spec.alpha$regex_spec_impl$reify__2436 0x5b77ed8c "clojure.spec.alpha$regex_spec_impl$reify__2436@5b77ed8c"], :value ([a 2 b]), :args ([a 2 b])}, compiling:(/Users/Camilo/Proyectos/kamal/src/hiposfer/kamal/dev.clj:55:1)

ghadi13:04:19

Use bbrinck/expound @carocad

carocad13:04:59

@ghadi I am using it, that is where the problem came from 😄. See https://github.com/bhb/expound/issues/81#issuecomment-378240072

carocad13:04:43

Unfortunately Expound only aggravates the problem since by pretty printing it you use much more space than what Clojure originally printed 😕 . Although the output is clearer there is still lots of noise

ghadi13:04:51

Tried pinpointer? (Just curious)

carocad13:04:39

what is that? I just checked it out. No I havent used it.

Alex Miller (Clojure team)13:04:01

in general, you can usually ignore all but the root cause exception in Clojure (the CompilerException in particular is nearly always a wrapper solely to transmit file/line/col info)

Alex Miller (Clojure team)13:04:43

clojure.repl/root-cause is one way to get that

😮 4
ghadi13:04:54

Heh, sorry I was on vacation and didn't read your q clearly... back to vacation 🙃

carocad13:04:08

@alexmiller but that would require me to catch the exception and then look for the root cause, which although can work is not the intended scenario. I am not sure if this is a Cursive problem or from Clojure but at least in Cursive if I type (let [1 2]) I get two exceptions printed in the console, not one as in every other case. It is not the case that I get one exception inside another but rather 2 different repl-returned exceptions for the same code execution. Hope that makes it a bit more clear 😅

Alex Miller (Clojure team)13:04:00

that seems weird. my typical experience in Cursive is that I see just the top exception (which is never the one with the correct information about the cause), so I typically call pst to get that

Alex Miller (Clojure team)13:04:52

maybe the outer exception here contains a message that includes the output from printing another exception

carocad14:04:03

it seems that you were right. If I do *e I get the full error which includes the two errors that are being displayed. So this is probably an IDE/tool parsing problem not a repl one

carocad14:04:07

Thanks for the help 🙂

mtnygard14:04:00

@denisgrebennicov I used some macros in Vase to make nested try/catches with values less ugly. https://gist.github.com/mtnygard/c786c67f5bb98cac883116c45330158c Basically lets me avoid doing (try (let [] (try (let [],,,

ajs15:04:03

when I run jps -lv from command line, even after closing down my repls, i see an entry there for clojure.main ... what is it?

mtnygard16:04:53

@ajs Are you on Windows by any chance?

mtnygard16:04:33

If you're on Windows and using lein or cider-jack-in from emacs, I've noticed that 1 of the 2 JVMs don't always exit.

mtnygard16:04:56

(lein uses 1 JVM for itself and 1 for the project+repl)

tjtolton16:04:33

hey, I need to test out an error serialization function.. what's a simple operation in vanilla clojure that's guaranteed to throw?

ajs17:04:36

(count 5)

tjtolton16:04:01

nice! thanks!

Naylyn16:04:45

In practice how do reference types and core.async interact? It seems like they solve the same or similar problems in different ways. Does core.async obviate a lot of the need for reference types or are there common patterns where they can be useful together?

hiredman17:04:52

they seem pretty different to me

pesterhazy17:04:46

yeah they seem orthogonal concepts, can you explain why you think they're solving the same problem?

hiredman17:04:48

the reference types are about identities, core.async is about communicating processes

tbaldridge17:04:02

There's a little overlap between agents and CSP, I wish there could be more. A lot of times what people use CSP for could be replaced with agents, if agents supported backpressure

tbaldridge17:04:34

But even then agents are communication via sending mutation functions to a reference type. While CSP is about sending messages to a process.

Naylyn17:04:32

It seems to me that they're both tools for managing shared state in concurrent systems. So if I have a resource that multiple parts of a program need access - like a map that serves as an in memory database - I should access it via either: * Having a process which owns it and other processes communicate with or, * Holding it in a reference type which different threads can access.

Naylyn17:04:43

I guess what I'm really asking is how are both used together in systems and does anyone have any examples?

andy.fingerhut17:04:54

@naylyn.gaffney Perhaps one difference is that with reference types, it is explicit in Clojure design that anyone can read the current value at any time, without blocking or consuming any resources from any other thread.

💡 4
andy.fingerhut17:04:15

If you need to send a message to some central place to read, that is quite different.

Naylyn18:04:27

Thanks everyone.

zentrope18:04:37

If you have a java jar with a com.foo.bar.Main and want to invoke main, how do you do that inside clojure?

zentrope18:04:49

(com.foo.bar/Main) ?

Alex Miller (Clojure team)18:04:17

no, it would be (com.foo.bar.Main/main)

zentrope18:04:42

Hm. Getting a class-not-found with that. Oy.

zentrope18:04:31

-Stree indicates its there. Okay. I’ll keep going.

zentrope18:04:23

No such field exception, I mean.

hiredman18:04:56

on the jvm, similar to C, there is a static main method that is used as the entry point, if Main doesn't have that, it isn't designed to be the initial entry point of the program

ajs19:04:05

is this main function required to accept arguments or is a no-arg arity acceptable? it seems to be in my experience but can't find any official doc on that

ajs20:04:16

interesting. I frequently see -main written with arity of [& args]. i don't know Java much but var-arg seems different to me than a single arg that is an array?

ajs20:04:41

or perhaps that Java arity is in fact var arg?

hiredman18:04:13

you may need to do something like (Main/main (into-array String []))

zentrope18:04:17

It’s there.

zentrope18:04:21

Oh, I’ll try that.

zentrope18:04:49

Yep. that fixed it.

Alex Miller (Clojure team)18:04:57

I should have said that :)

zentrope18:04:22

I also should have caught that. Had do to something similar for finding files (into-array FileAttribute []) or some such.

zentrope18:04:56

Cool. Using deps.edn, I can load a bunch of peer services into a single JVM for demo purposes.

zentrope18:04:03

JVM — the crusty unfashionable version of docker. ;)

ajs19:04:05

is this main function required to accept arguments or is a no-arg arity acceptable? it seems to be in my experience but can't find any official doc on that

Alex Miller (Clojure team)20:04:16

in Java, it is varargs, so 0 is ok

Alex Miller (Clojure team)20:04:32

but Java varargs in Clojure are represented as an array, so it’s 1-arity when invoking via interop

markw23:04:19

Question re: transducers. I keep reading they avoid the collection of intermediate results, and that otherwise composing sequence transformations e.g. map, filter etc. results in realizing intermediate results. But since those transformations are lazy, isn't the extent of any intermediate collection relegated to just whatever chunksize is pulled through in the thunk?

markw23:04:43

If that's the case, then you don't really save much on memory, right? Maybe just the computational overhead of setting up the thunks

arrdem23:04:44

In their fully applied sequence arities yes those operations are lazy and produce lazy sequences. As transducers they’re streaming operations which get forced eagerly when you use transduce.

markw23:04:00

Right - so when I continually read that transducers avoid collecting intermediate results, that's kind of half true no?

markw23:04:11

the amount of intermediate results is going to be pretty limited

markw23:04:52

For example: (filter even? (map inc (range 100))) is not going to generate 100 results before passing to filter

markw23:04:04

it's going to generate something like 32

markw23:04:22

I think the emphasis on efficiency gains probably pertains more to the time saved by avoiding building up thunks

arrdem23:04:24

Sure but that’s the lazy sequence path. If you (transduce (comp (map inc) (filter even?)) conj [] (range 100)), that (range 100) will yes get lazy sequence chunked, but as it does so the transducer stack (the comp) will be eagerly driven element-at-a-time over the input.

bronsa23:04:08

reducible uses of range won't do any consing at all since 1.6 or 1.7

bfabry23:04:46

there'll be 32 at every point in the pipeline though. so (->> (map inc) (filter odd?) (remove pos?) (map dec)) = 96 + the overhead of tracking 3 computation promises

markw23:04:07

OK that was what I was thinking

markw23:04:21

So you save some cpu overhead but the memory gain is pretty limited

markw23:04:47

it's really more about abstraction and generalizing to any input than making it faster then

tbaldridge23:04:18

(transduce (comp (map inc) (filter even?)) conj [] (range 100)) Compiles down to something that looks really close to a for loop in Java

tbaldridge23:04:10

range never creates a cons cell and neither to the transforms

greglook23:04:27

it’s the creation of all the intermediate cons cells that takes the memory in the lazy-seq case

greglook23:04:46

even if you’re only realizing 32 at a time, that’s still increased GC pressure

bfabry23:04:01

yeah I wouldn't say "pretty limited". it's a whole mess of big extra object graphs. also really depends on the size of the items you're iterating over

tbaldridge23:04:08

(into [] (comp (map inc) (filter even?)) (range 100)) is even better, because in this case you create almost no garbage at all, In this case transients remove the garbage created by conj

tbaldridge23:04:49

for (object i = 0; i < 100; i++)
{
  v = i + 1
  if (even?(v)) lst.append(v);
}

that's about what that transducer pipeline executes

bfabry23:04:02

and yeah garbage collection of all those interim things is significant. java garbage collection is fast but not as fast as no gc at all

tbaldridge23:04:17

The problem is also GC pressure, I've worked on many systems that execute fine in isolation, but all that "fast allocation" adds up in the end to a system that has GC pauses every 20 sec.

tbaldridge23:04:37

Allocation is almost free on the JVM, GC is not

bfabry23:04:43

yeah collection *and allocation definitely

markw23:04:46

Got it, appreciate the clarifications. I was coming it at from the perspective of trying to choose whether it's worth it to move to transducers from a current pipeline of lazy transformations on 100's of gigs.

tbaldridge23:04:08

In that case? Very recommended, but for one more reason as well:

tbaldridge23:04:26

It's almost impossible to hold onto the head of a collection with transducers. And its' really hard not to with seqs.

👍 4
markw23:04:36

yeah i've done that more than once

markw23:04:46

and it was not easy to figure out lol

tbaldridge23:04:49

So one cool thing with processing that amount of data. Reducing functions, like conj, can be replaced by anything that wants a stream of stuff. clojure.lang.IReduceInit can be implemented on almost anything that produces a sequence of stuff. So I've written pipelines that do stuff like this:

(transduce
  xform
  append-line
  (file-writer "file")
  (file-reader "ifile"))

tbaldridge23:04:42

And with that there's no way you can mess up and hold on to something. Also you can replace parts of that with clojure.core.async/pipeline-blocking and then you can get parallel transforms.

tbaldridge23:04:59

So one CPU reads, another writes, and the rest transform.

markw23:04:39

Alright I definitely need to explore that avenue... parallelizing it would be a big win.