Fork me on GitHub
#clojure
<
2016-08-27
>
puzzler03:08:21

I'm getting a bizarre error in 1.8 "UnsupportedOperationException nth not supported on this type:" but no type is actually printed, and the source lines in the stack trace don't make a whole lot of sense with respect to this error. Anyone seen this?

dpsutton03:08:12

are you writing a macro?

dpsutton03:08:37

i've seen it when i get my syntax wrong in a macro that i'm writing

puzzler03:08:33

I found the problem. A call to nth on a Java set. I understand why nth doesn't work on this, but what I don't understand is why the Clojure error failed to show the type, and why the stack trace pointed at the wrong line of code.

puzzler03:08:44

Minimal example: (nth (.keySet {1 2}) 0)

puzzler03:08:15

The type is printable: `=> (type (.keySet {1 2})) clojure.lang.APersistentMap$5`

puzzler03:08:27

So it should be able to print this in the error message.

puzzler03:08:41

So, seems like a bug in the error-throwing code associated with nth.

Alex Miller (Clojure team)04:08:04

nth spec would catch this before

hiredman08:08:16

puzzaler: if you look carefully, you'll see two exceptions being thrown in the repl, one is the nth one, the other is

CompilerException java.lang.RuntimeException: Unable to resolve symbol: ⁠ in this context, compiling:(NO_SOURCE_PATH:0:0) 

hiredman08:08:19

the reason you see a blank space in both exceptions where something should be is because the compiler calls getSimpleName on the class name, and for the anonymous inner class the simple name is ""

hiredman08:08:52

the second exception, which in my testing is the exception that actually bubbles out (the nth one has the error message printed and that is it), is the really weird one, because it is coming out of the compiler, not the runtime

hiredman08:08:38

what I am seeing is(with the clojure.main repl), for that exception, for some reason, the next time the repl passes through its loop "repl-read" is returning a symbol with the name "", which of course explains the unable to resolve symbol error message from the compiler

hiredman08:08:31

Hah, the second exception is just me chasing my own tail, because I copied and pasted the (nth (.keySet {1 2}) 0) from slack and it included trailing weird unicode non-breaking space characters

seylerius15:08:30

Okay, I'm producing hiccup-style structures from inataparse. I need help figuring out how to re-parse specific items within the structure.

seylerius15:08:41

Solo strings (unmatched with a tag) are one of the types I need to re- parse in place

seylerius15:08:40

s/inataparse/instaparse/

si1417:08:10

meta-question: how do you judge the quality of Clojure books without buying them? B/c I've bought https://www.amazon.com/Professional-Clojure-Jeremy-Anderson/dp/1119267277/ , was very dissatisfied with the purchase and somehow there are three 5-star reviews now (there were none when I bought it)

si1417:08:35

"dissatisfied" as in "wtf, how this can have "professional" in the title". like when they recommended to "slurp" a bytestream in Ring middleware without ever mentioning memory consumption concerns. or when they claimed that vector lookup is O(1)

reitzensteinm18:08:40

haha @si14, vector lookup being O(1), you're going to have fun debating that one with the channel

credulous18:08:47

Ok, I’ll be that guy… vector retrieval isn’t O(1)? Unless the book was ambiguous between lookup and search, since search is obviously O(n)

dpsutton18:08:12

log32 is taken as effectively constant for lookup

si1418:08:45

@reitzensteinm vector is a tree, so it's O(log(n)). we can argue that the tree is shallow, but that log may show up anyway (e.g. as an abrupt change in access times when first "layer" is filled up)

si1418:08:46

I mean it's unprofessional to claim O(1) index lookup without mentioning the underlying assumptions for the claim (like log32 being "effectively" constant)

reitzensteinm18:08:14

no arguments from me @si14. it's something that comes up often, and I don't personally think it's helpful to claim O(1)

credulous18:08:02

But the purpose of using big-oh is for comparison of structures and algorithms.

dpsutton18:08:10

i'm looking for the claim. I found this in chapter 7

dpsutton18:08:11

> Clojure is data oriented. Choosing the right datastructure is the prerequisite for writing fast code. Clojure encourages usage of O(1) and O(log n) data structures by providing the map, the set, and the vector literal notation.

reitzensteinm18:08:36

what is O(1) in that list though?

dpsutton18:08:48

> Vectors have O(1) lookup by index. Sets and hashmaps have O(logn) lookup by key. Sequences by contrast are O(n). Choosing the right data structure usually boils down to not doing combinatorial sequential lookups O(n∧2) or higher. Exponential complexity is the primary performance assassin for algorithms.

dpsutton18:08:51

ah here it is

dpsutton18:08:30

i'm ok with that statement. It could have included information about 32-way tries but for an intro book its ok

reitzensteinm18:08:45

but why not just say logn for everything?

reitzensteinm18:08:41

the performance implications are subtle, and not really captured by the big O notation. so 8 branches deep (assuming balance), you're in the objects-that-take-terabytes-of-ram. so effectively, your cap is going to be 6-7

si1418:08:44

@credulous O(1) claim makes an impression that Clojure's persistence is a free lunch. It isn't, both in data locality and memory, so I would expect that the book that has the word "professional" in its title would mention this

reitzensteinm18:08:15

if an algorithm were to slow itself down so it always took 8 branches, it would be considered O(1)

reitzensteinm18:08:35

but it would be nice to discuss the unfriendlyness to cache of making those jumps around memory

reitzensteinm18:08:03

like, if you're going to pull out the big O notation, you're going deep enough that you might as well just explain the lot

reitzensteinm18:08:21

I don't have a problem with effectively constant though

reitzensteinm18:08:24

just my 2 cents

si1418:08:29

@reitzensteinm yeah, right! I wish there is a Clojure book that really dives in, like from the basics (HAMT and stuff) to how you compose large systems in Clojure (do you even DDD?)

si1418:08:52

or maybe I just don't know where to look

reitzensteinm19:08:47

i'm trying to make my way through the functional data structures book for my talk, it's a pretty harsh introduction though

reitzensteinm19:08:19

the one on the clojure bookshelf

si1419:08:16

tangential: is there a guide or book anywhere on how to build "enterprise-y" Clojure software? It feels like everything we have in a public discourse is a mix of "maps flowing through the system", "Datomic is awesome" and "data is king". While I completely agree with all of it, it doesn't help much to structure the code around complex "business logic". Java world has buzzwords like CQRS and DDD and a ton of people writing about the stuff; it feels like Clojure has almost none

quoll19:08:24

@si14 I would recommend 2 books to do that. The first is “Programming Clojure” by Stuart Halloway and Aaron Bedra. That takes you from the basics, but covers much more of them and much better than most other books. Then I recommend “Clojure Applied” by Ben Vandgrift and Alex Miller (Hi Alex!), to get you up to composing large systems

si1419:08:42

how would you execute a transaction that involves a few external services and a database? would you reuse Java's pattern of lifting a graph of objects from SQL DB and relying on some ORM to save it back, or should it be ad-hock selecting and updating? questions like this are hard to get right, or maybe I'm just too ignorant :(

si1419:08:05

@quoll thanks a lot, definitely will check them out!

si1419:08:52

@reitzensteinm may I ask where that talk is going to be presented? :)

quoll19:08:54

You’ll also find that many of these design frameworks are applied to Java to simplify complex flows and so on. While Clojure is not a silver bullet, many of the things that those patterns are trying to help with in Java are much more tractable in Clojure. This either makes those things unnecessary, or much simplified. Try looking at Bobby Calderwood’s Clojure/conj talk from last year where he looks at CQRS with Clojure: https://www.youtube.com/watch?v=qDNPQo9UmJA

reitzensteinm19:08:47

I am working on an experimental clojure->C compiler, that uses clojure.spec generators to specialize code

reitzensteinm19:08:57

so you don't have to check is-it-a-float a trillion times at runtime

si1419:08:36

@quoll I see what you mean, thank you. I believe there is an inherent complexity in some things, like distributed transactions (someone clicks a button, perform a charge, record its result to DB, send an email, every step can fail at any point), so it's not Java quirkiness at fault but an underlying world's messiness. Java people came up with some solutions, sometimes I think it can be helpful to look at how they do stuff, but it's hard to disentangle Java from the underlying ideas

si1419:08:28

@reitzensteinm sounds extremely interesting! Hope I will be able to attend :)

reitzensteinm19:08:58

@si14 there's a thingie I uploaded here

reitzensteinm19:08:29

you can drag the slider to see the compiler optimizations being applied

reitzensteinm19:08:05

and the eventual C output turning from a naive implementation of clojure's features that work at runtime, to eliding it all after specializing it

si1419:08:05

wow! "Drag slider to show optimizations being applied." is just plain awesome!

didibus23:08:34

@si14 I haven't found a lot of books that are that great or up to date. But DDD and CQRS are not Java specific, so they should apply to Clojure as is. Obviously, you won't be able to benefit from the OO pattern they describe to help you implement them. The basic idea behind DDD is that you should model things as they are in your organization. The same boundary should exist. Clojure records work well for the model part, combined with protocols to implement the service layer. You can use namespaces as bounded contexts. An ATOM as your repository. And events can just be functions. There's many other way I guess you could model it. I'd recommend looking at ClojureScript actually, there's more examples of enterprise software in it, with details of the frontend and the backend.

didibus23:08:36

Does anyone know how a macro behaves within a special-form?

didibus23:08:31

I'm trying to figure out if I can make a macro called (catch-any) that would take a vector of exceptions and return multiple (catch) blocks from it. And if it would be possible to use it within the try special-form

gfredericks23:08:05

didibus: if you want special catches you need to write a try macro, not a catch macro

didibus23:08:50

@gfredericks I was thinking that's the case, but I'd like to understand why that is?

hiredman23:08:38

because (catch ...) isn't really its own special form, it is part of the syntax of the try special form's dsl

didibus23:08:33

So its something with the transformation order. If the (catch-any) macro would expand first, wouldn't it be able to return a list with the first element being (catch) which would then be processed by (try) as if I had written that?

hiredman23:08:19

no because try isn't looking for a list with the first element that is a catch clause

hiredman23:08:46

a macro expands to a single form, it cannot expand to multiple forms and some how splice them in to the surrounding form

didibus23:08:53

Hum, that part I don't understand. Shouldn't the macro just pre-process the AST into one form to another?

gfredericks23:08:32

"to another" not "to several"

hiredman23:08:38

a macro takes and returns forms

hiredman23:08:18

(catch ...) is a form, and is specifically the kind of form the try special form is looking for as catch clauses

hiredman23:08:38

((catch ...) ...)) is another form, and is not the kind that the try special form is looking for

gfredericks23:08:10

the "expand to one form" vs "expand to many" isn't even the main limitation though; somehow try is processing the forms below it before any macroexpansion gets done

didibus23:08:17

Ok, I see, so a macro can't produce a list of forms, which means it wouldn't be able to return (catch...) (catch...) etc.

gfredericks23:08:49

e.g., you can't even do this: (defmacro rescue [& args] (cons 'catch args))

didibus23:08:12

Thanks, I hadn't think of the output one form vs many issue, but I'd like to understand about the processing order

hiredman23:08:26

a list of forms like X, is different from a bunch of forms like X embedded in a surrounding form

hiredman23:08:02

(try ... (catch ...) (catch ....)) is two catches in the surrounding list that starts with the symbol try

didibus23:08:08

Why does try happens before macroexpanssion, say I just wanted to have a macro that expands to a single catch form

gfredericks23:08:23

@didibus: that's how it was written; not sure if there's a good reason

hiredman23:08:55

(try ... ((catch ...) (catch ...))) is a list of catch forms in the list that starts with try

didibus23:08:56

Do every special-form have their own execution order rules like that? Or do all special-form follow a pattern?

gfredericks23:08:19

@didibus: most of them don't have this nested syntax aspect, so the question doesn't really apply

didibus23:08:55

I guess that's true. What about macro inside macro? which macro expands first?

gfredericks23:08:03

I suppose there are parallel things like "You can't write a macro that expands to an arglist and use it inside of defn"

hiredman23:08:13

its like if you had a macro that you wanted to expand in to a binding pair inside the bindings given to let

gfredericks23:08:32

@didibus: the outer macro runs first, so gets the ultimate say in everything

didibus23:08:54

I see, so maybe the same rule is applied to special-form

gfredericks23:08:56

although you a macro can't expand its body, which is pretty annoying

hiredman23:08:41

every macro is basically its own dsl and can do whatever, and unless a lot of care and consideration is given, that whatever is unlikely to be arbitrarily pluggable

didibus23:08:39

So macros only compose if the outer most macro designed specifically for it.

hiredman23:08:49

basically never

gfredericks23:08:05

I suppose "a macro can't expand its args" means you couldn't even reasonably write your own defn that supported macros expanding to arglists

gfredericks23:08:25

do any other lisps have that capability?

hiredman23:08:54

I could imagine another implicit argument to macros that is a macro expand function that the compiler passes in

didibus23:08:56

Clojure is my first Lisp, but I remember reading that some Lisp can in fact compose macros

hiredman23:08:09

there are many many kinds of macro systems

gfredericks23:08:45

I wonder how easy that would be to add to the clojure compiler

hiredman23:08:51

and they are sort of a sub domain of rewrite systems

hiredman23:08:05

very, would be my guess

gfredericks23:08:17

I keep running into uses for it

hiredman23:08:33

it would break everyone elses macroexpanders

gfredericks23:08:47

oh because it'd be a 3rd positional arg?

gfredericks23:08:59

let's pass it in as metadata on the &form

hiredman23:08:12

a well-known key in &env

gfredericks23:08:24

(-> &form meta :clojure.core/macroexpand)

hiredman23:08:33

':well-known/macroexpand'

gfredericks23:08:59

:clojure.core/d5a7abcf-8dde-4f3c-8375-a9f40fc2b891

hiredman23:08:28

rewrite systems in general are very neat

didibus23:08:00

I'm guessing its a compromise between allowing people to do too many unpredictable things

hiredman23:08:05

I wrote a lisp -> go compiler using something like macroexpansion, but the expansion happened outside in, then inside out, and could pass information up and down the expansion

gfredericks23:08:48

I've never heard of any downsides to something like this

hiredman23:08:33

I think it is an issue of scope

gfredericks23:08:49

@hiredman I bet "Unable to resolve symbol" error messages would be more complex

gfredericks23:08:01

the compiler would have to expand down and back up and only then start looking for problems

hiredman23:08:24

you want local transformations, to have an understandable system

gfredericks23:08:40

what does that mean?

hiredman23:08:59

I think I am off thinking about general rewrite systems, not and particular changes to clojure's macro system