Fork me on GitHub
#beginners
<
2020-06-12
>
Chase00:06:17

If you had a map whose values were vectors and you wanted to use destructuring to name the values in that vector would you use something like this: (defn my-fn [{:keys [foo bar]}] (let [[a b] foo] ... or is there a more idiomatic way to get those a and b values out of that vector?

noisesmith00:06:44

(ins)user=> (let [{[a b] :foo} {:foo [1 2]}] (println :a a :b b))
:a 1 :b 2
nil

noisesmith00:06:00

I think the normal non-keys destructure is clearer

Chase00:06:14

ahh yeah. I can see that. Wonder if that gets unwieldy if I'm destructuring many keys. I don't have a use case, just checking for understanding.

noisesmith00:06:45

often multiple lines in a let is clearer than a nested destructure

noisesmith00:06:00

and it's likely to compile to more efficient code as well if that matters

Chase00:06:16

that was my other question. does destructuring provide any performance benefits or regressions versus just grabbing things using (first vector-in-foo) or whatever

Chase00:06:36

Or is it kind of like syntax sugar over such calls?

noisesmith00:06:20

if you expand the macro, you'll see that destructure is pretty much always worse perf wise than a manual binding - it is syntax sugar, but it does a lot of things your case won't need

noisesmith00:06:54

(ins)user=> (pprint (macroexpand '(let [{[a b] :foo} {:foo [1 2]}] (println :a a :b b))))
(let*
 [map__240
  {:foo [1 2]}
  map__240
  (if
   (clojure.core/seq? map__240)
   (clojure.lang.PersistentHashMap/create (clojure.core/seq map__240))
   map__240)
  vec__241
  (clojure.core/get map__240 :foo)
  a
  (clojure.core/nth vec__241 0 nil)
  b
  (clojure.core/nth vec__241 1 nil)]
 (println :a a :b b))
nil

Chase00:06:33

Oh, interesting. I was just telling myself yesterday to up my destructuring-fu as I've been seeing more and more of it while reading other's code. I don't mind just using let bindings though if it's a big performance difference. I find the latter more readable so far too. I'm sure a proper balance of both is the right answer

noisesmith00:06:17

IMHO clarity is king - the less effort required to figure out what the code is trying to do, the less likely a bug can remain unseen

noisesmith00:06:05

with the ammendment that it's worth learning certain idioms and constructs to make them clear :D

noisesmith00:06:30

(because in the long run they help your code be clearer if you learn them)

noisesmith00:06:59

so I'd learn destructuring, but not throw multiple or nested destructures into one line of code

Chase00:06:12

Haha. I can agree with all of that. Thank you.

Chase00:06:44

That being said, I really liked this whole :keys things as to me it clearly shows what keys you want to use from the map but you were thinking it's less clear. hahaha

noisesmith00:06:47

it's a trade-off - the reverse-key destructure is less commonly used, and slightly less elegant, but it lets me replace two lines with one

noisesmith00:06:54

and I'd argue that it's not obfuscating

noisesmith00:06:20

but communicating to human beings isn't a science, it's an art

Chase00:06:16

Well said. Thanks for the chat!

raspasov01:06:32

I personally like the reverse-key destructure 🙂 but always multiple lines, yes

raspasov01:06:45

That’s how I usually destructure a ring request, for example… some might say it’s unorthodox, but I do prefer it; it is almost documentation-without-documentation

raspasov01:06:39

It describes the shape of the datastructure, semi-formally, in a reverse order, and serves as code/local bindings at the same time

seancorfield01:06:26

If you're binding something to a different name, the reverse destructure is very useful, but I never find it as readable since it always looks like value :key value :key which is 👀 🤯 and then you have :as something at the end so you go back to the more "natural" :key value order.

seancorfield01:06:01

That's a good reminder that you can destructure on string keys (`:strs`) and symbol keys (`:syms`)

alexmiller01:06:42

The mnemonic is always symbols you’re binding on the left

noisesmith01:06:16

until you hit :as

alexmiller01:06:55

Modifiers are keywords but those are either symbols or maps with symbols on the left (like :or)

raspasov02:06:32

The “problem” with {:keys [a b c]} from my perspective is that you can’t “continue” the destructuring further down through a, b, c, etc

raspasov02:06:17

… and then later, if you want to destructure a, b, c either you need to do it in another data structure (which for me has the mental cost of piecing together the data structure later on), or replace/retype {:keys […]} with a reverse destructure

raspasov02:06:34

Like in :headers in the example above, I’m almost 100% certain that I won’t dig further down, since that’s typically key/value pair; so I use :keys/:strs there, no need to be verbose like {time-zone “time-zone”}

jeffmk04:06:09

Using just a simple clj and deps.edn setup, what's the best way to reload ClojureScript code via the REPL?

dpsutton04:06:29

You should be able to just eval and reload code just like clojure. Are you looking for hot reloading a react app though?

jeffmk04:06:58

Yeah, primarily say I make a change in blah.core, just want a one-liner way to reload that and re-render to the DOM (e.g., via reagent.dom/render as I'm using Reagent).

jeffmk04:06:31

For easiest hot reloading, I guess I will need to use something like shadow-cljs though?

jeffmk04:06:35

I have used figwheel in the past. I'm really trying to do things as boiled down to the basics as possible here, thus the deps.edn/`clj` setup.

jeffmk04:06:51

Could be that's just not worth it for this workflow though?

dpsutton04:06:20

Figwheel main is where you want to be the

jeffmk04:06:02

I guess just repasting a modified defn and the reagent.dom/render does get me partway there -- just no way to re-eval an entire file w/ one line?

jeffmk04:06:24

But yeah, I think for a serious tight loop, I've just gotta use something with proper hot reloading.

dpsutton04:06:38

how are you serving the webpage?

Jim Newton12:06:36

With regard to https://download.clojure.org/papers/clojure-hopl-iv-final.pdf , it's a nice historical perspective. I haven't finished reading all 40 pages of it. His recounting of the history of the development of the language is a bit different that I guessed from learning the language. for example, when I look at the low levels of many things I see someone who first tried to implement Common Lisp in Java, and later changed his mind. Does anyone know whether that is part of the history which has been rewritten?

alexmiller12:06:50

Not sure I understand the question at the end?

Jim Newton12:06:46

For example there is the cl-format function which must have been a huge undertaking, and the way the reader and printer work with global variables like *print-base* , *print-readably* , and *read-eval*

alexmiller12:06:27

cl-format was contributed, that wasn’t written by Rich

Jim Newton12:06:40

ah ha, that's an interesting piece of information.

alexmiller12:06:51

Tom Faulhaber did all of that

alexmiller12:06:28

very early on, Clojure was partly written in Common Lisp

Jim Newton12:06:51

did Faulhaber also write the reader?

Jim Newton12:06:57

> Clojure was partly written in Common Lisp

Jim Newton12:06:11

I missed that skimming the paper.

Jim Newton12:06:01

Something I did glean from the paper was that Hickey worked on clojure for about a year before realizing what he had done.

alexmiller12:06:08

Rich wrote the reader

alexmiller12:06:31

there were some pre-Clojure projects too with other attempts

alexmiller12:06:24

different points in the java/lisp interop space

Jim Newton12:06:00

He decided against reader macros (good choice BTW), but he did implement several # reader macros. not sure how that works, perhaps they are all explicitly baked in and not configurable?

Jim Newton12:06:18

Yes I saw jfli and foil mentioned in the paper. Didn't really know what they were though.

Jim Newton12:06:33

These historical perspectives are great stuff.

Jim Newton12:06:50

It's easy to forget unless someone documents them.

Jim Newton13:06:31

But we have a tendency to document the successes. Documenting failures is not really something people want to dedicate large efforts to, nor do readers really care to read about them.

MorongÖa13:06:29

Does the latest Clojurescript make it easier to use Storybook'. How has anyone used storybook clojurescript?

Jim Newton13:06:47

Question: In clojure, what is an object ?

Jim Newton13:06:33

In lisp and SmallTalk every value is an object. but in some OO langauges, an object is only an instance of some subclass of the Object class.

Jim Newton13:06:12

In Scala the term object is difficult to use because there is a langague construct called object which creates (as I understand) a companion object, and instance of a hidden class who contributes to the scope of a class of the same name as the object.

noisesmith13:06:27

all clojure values are objects, in some corner cases they can be unboxed by the compiler into primitives, but not in a way you can directly manipulate in the language

noisesmith13:06:09

clojure intentionally avoids hidden / clojure only compiler properties with no implementation in bytecode

noisesmith13:06:49

there's no spooky parallel reality here :D

Jim Newton13:06:55

@noisesmith, that seems to be a good description. It is the definition I infer from https://clojuredocs.org/clojure.core/read

noisesmith13:06:57

if you look at the clojure compiler / core code in github, everything takes and returns Object

noisesmith13:06:03

as in, the literal java superclass of all classes

Jim Newton13:06:05

hmmm, so object does have the implication that it is an instance of the Object class?

noisesmith13:06:18

conceptually the core definition as far as java code is concerned is Object IFn(Object ...)

noisesmith13:06:40

well, the way the java class system works, all instances of all classes are instances of object

noisesmith13:06:00

(ins)user=> (instance? Object [])
true
(ins)user=> (supers (class []))
#{clojure.lang.Indexed java.lang.Runnable java.lang.Iterable clojure.lang.IFn clojure.lang.IReduce clojure.lang.APersistentVector java.util.List clojure.lang.IPersistentCollection clojure.lang.IPersistentVector java.io.Serializable clojure.lang.IMeta java.util.Collection clojure.lang.Sequential clojure.lang.Seqable clojure.lang.Counted clojure.lang.Associative java.util.RandomAccess clojure.lang.IKVReduce clojure.lang.IReduceInit java.util.concurrent.Callable clojure.lang.ILookup java.lang.Object clojure.lang.IEditableCollection clojure.lang.Reversible clojure.lang.IPersistentStack clojure.lang.IHashEq clojure.lang.AFn java.lang.Comparable clojure.lang.IObj}

noisesmith13:06:05

that empty vector is an instance of each of those classes

dpsutton13:06:21

ClojureScript is a clojure and has a very different notion of what a js/Object is. That might give some hesitation to conflating Object and java.lang.Object

dpsutton13:06:11

don't have time to find the passage from the HOPL document but I believe he called it Clojure in Clojure

noisesmith13:06:25

oh, yeah, cljs is its own beast and I wasn't attempting ot describe it at all here

sova-soars-the-sora14:06:33

so there's an illegal reflection party in pretty much every package i clone now... can someone explain what reflection is in the case of clojure?

alexmiller14:06:53

when you make a Java interop call, Clojure has to emit bytecode to make the call

alexmiller14:06:20

if it can infer (or if you provide type hints), it will emit exactly the right invocation

alexmiller14:06:32

if not, it will emit a call to use Java reflection to inspect the object at runtime to find the method to call

alexmiller14:06:46

since they introduced the module system in Java 9, many reflective calls to implementation classes are also illegal accesses due to the new module visibility rules (you need to invoke the public exported interfaces, not the module-private impl classes)

sova-soars-the-sora14:06:28

so it's invoking private methods** rather than methods that are exposed?

noisesmith14:06:57

"methods" are not reified in the vm, you inspect a class and get descriptions of methods

noisesmith14:06:14

it is asking about an implementation class, instead of the public superclass, that's the error

sova-soars-the-sora14:06:14

oh... that's a good point.. just a bag of instructions

sova-soars-the-sora14:06:21

okay, i still don't get that 100%, that's only 10% clear to me, but i wonder... is there a way for clojure to do the inspect and then let me know the right type hint? some of the reflection bits are on packages upstream

noisesmith14:06:50

IMHO it's weird and brittle to allow access to a method, but only allow reflective access to its description via a superclass, but that's water under the bridge

sova-soars-the-sora14:06:16

haha it seems like a weird deviation

noisesmith14:06:35

you can fix that for functions you define, but the hint would need to be offered in the body being compiled, so you'd need to own the code of the lib to fix it

alexmiller14:06:39

the reflection api is designed to tell you about stuff, including stuff you can't invoke

alexmiller14:06:34

the frustrating part is to not be able to know whether the call you are reflectively looking at is invokable without invoking it

sova-soars-the-sora14:06:43

so when it warns on reflection... is it still invoking the wrong method? right method? right method and needs a typehint now that it found it?

alexmiller14:06:50

you can tell whether it's illegal and the Clojure reflector will use that information (if you set --illegal-access=deny ) to filter out those options

alexmiller14:06:24

but currently, because it's only a warning, there is no feedback mechanism in the reflection api

sova-soars-the-sora14:06:47

where do i put that flag?

alexmiller14:06:32

the best solution is for the call to not be ambiguous in the first place

alexmiller14:06:38

but you may not control that code

alexmiller14:06:24

the common case for this is having a public IFoo interface with a method m(), and then a non-visible implementation FooImpl extends IFoo that implements m(). At runtime you have a FooImpl object instance. If you try to invoke FooImpl.m(), it will complain but invoking IFoo.m() will be fine.

alexmiller14:06:20

in both cases you're invoking the same method on the same object, it's just how you're saying it

sova-soars-the-sora14:06:00

technically Foo Impl could offer a different definition of m(), non?

alexmiller14:06:25

IFoo is an interface here so there is no impl at all, it just defines the signature

noisesmith14:06:51

you can't get two different implementations of the same method on one object can you?

alexmiller14:06:52

but yes, IFoo could have a default method now and FooImpl could provide a different version

alexmiller14:06:21

@noisesmith no, but that's not the right question

alexmiller14:06:40

the question is given an object and a method, which method impl will be invoked

sova-soars-the-sora14:06:02

if you extend an object i believe you can redefine a method, so when you call extended-object.method you get something different than original.method

alexmiller14:06:17

and there are other weirder cases

noisesmith14:06:35

oh right because the method is bytecode, it's not a "thing" owned by the object per se - so eg. you can call the superclass' method inside your own impl without creating a separate instance of the super, right?

sova-soars-the-sora14:06:17

is there some preprocessing step that could happen that would delete the need for reflection?

alexmiller14:06:24

yeah, type hints :)

alexmiller14:06:00

if you just tell the compiler it's an IFoo, it will call IFoo.m() :)

noisesmith14:06:10

oh I know - a source code monkey-patcher that adds hints programmatically! (sarcasm, please don't do this)

alexmiller14:06:28

so, inference?

sova-soars-the-sora14:06:31

i was thinking on the other side, before it gets to clojure land

sova-soars-the-sora14:06:40

but i see what you mean, type hints solve it

alexmiller14:06:07

in some sense, what they did with the access warnings is the worst case for us

sova-soars-the-sora14:06:11

java is the bank and java is also feeling around in the dark for my account information

alexmiller14:06:01

they defined a new way to restrict access but allowed it to continue working anyways, with no feedback mechanism for knowing what it would complain about

sova-soars-the-sora14:06:43

haha okay it's not just me a little unhappy with upstream java

sova-soars-the-sora14:06:03

how about this really irritating change that doesn't affect performance ? push globally

alexmiller14:06:15

if they had a) not warned, no one would see it and things would continue working as before or b) denied it and provided feedback via isAccessible() then we can work harder - we do this now

sova-soars-the-sora14:06:06

.isAccessible makes sense... static bytecode analysis seems like not something we should be doing

alexmiller14:06:34

that exists, and we use it (that's why it works when you set it to deny)

noisesmith14:06:43

sure - but yes, and we also commit to late extension, which means that you could see a class at runtime that hadn't been written yet when your code compiled

sova-soars-the-sora14:06:20

yeah... anything that adds brakes (stop lights) to compilation seems problematic for such endeavorrs

sova-soars-the-sora14:06:40

made sense to me but i'm out there

alexmiller14:06:10

this one of many compromises they had to make in bolting on the module system semantics. to me, they punted on the interesting hard problems (like having version semantics for modules and classloader entanglement) and just added a bunch of complexity. plus they had to weaken all of the enforcement so that existing stuff could keep working, so you don't even get the benefits they were going for.

sova-soars-the-sora14:06:32

yeah but why work on interesting hard problems?

alexmiller14:06:49

thumbs down from me - worst thing they ever did to java

alexmiller14:06:35

they did work on it - it was really hard and they prototypes that did do interesting stuff in this area

alexmiller14:06:57

but I think it's just impossible to do the right things at this point in Java's lifecycle

alexmiller14:06:16

they should have just killed it

alexmiller14:06:32

modules, not Java :)

alexmiller14:06:54

there's no harm in deciding not to do something

Michael J Dorian14:06:26

Is this also tough for the other JVM languages right now?

noisesmith14:06:00

I'm sure scala doesn't notice, as it uses inference / declaration and doesn't rely on runtime reflection

Michael J Dorian14:06:18

Right, more dynamic more reflection

noisesmith14:06:23

iirc kotlin uses inference rather than reflection too

noisesmith14:06:28

those are the big competitors :D

alexmiller14:06:54

yeah, the statically typed langs have the types

noisesmith14:06:26

jruby would be comparable, I am sure there has been some cross-seeding of techniques between their impl and clojure

alexmiller14:06:36

groovy might be one to ask about

alexmiller14:06:02

I haven't paid attention to it for a long time

alexmiller14:06:48

might be interesting to see what they did

noisesmith14:06:11

that post mentions invokedynamic - it hadn't occured to me that would help

sova-soars-the-sora14:06:37

when is the right time to fork Java

ghadi14:06:00

I did some research on this. Alex is correct that Groovy is the one to look into

alexmiller14:06:02

that top code is interesting, we could do something similar

alexmiller14:06:01

it's annoying to do it while maintaining java 8 as well (which has none of those apis), but it's doable

alexmiller14:06:27

Ghadi if you wanted to make a jira for that, go for it

amirali18:06:58

hi folks. How can I recur a whole map to defn the same way I do it with vectors or values? and have access to all its keys and values?

adam20:06:48

What's wrong with my destructuring here (fn [{:keys [lang routes :as request] {:keys [email]} :params}] It's working fine for all args except request is empty. I am trying to capture the original map

noisesmith20:06:05

:as should not be inside the vector

noisesmith20:06:20

also, moving some of the destructuring into a let will make this easier to read / maintain without a performance cost

adam20:06:14

Thanks @noisesmith that solved it. I will see if I can make it cleaner with let