Fork me on GitHub
#beginners
<
2020-06-12
>
chase-lambert00: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

chase-lambert00: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

chase-lambert00: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

chase-lambert00: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

chase-lambert00: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

chase-lambert00:06:12

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

chase-lambert00: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

chase-lambert00: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 👀 :exploding_head: 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?

dpsutton04:06:29

For the easiest I would say figwheel main. Should be very similar to and even mimic cljs main. Shadow does a lot more and patches the cljs compiler a bit to extend some features. Makes npm interop much easier (or possible) but api is not similar to nor does not try to imitate cljs main

dpsutton04:06:29

For the easiest I would say figwheel main. Should be very similar to and even mimic cljs main. Shadow does a lot more and patches the cljs compiler a bit to extend some features. Makes npm interop much easier (or possible) but api is not similar to nor does not try to imitate cljs main

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?

jeffmk04:06:17

Just via clj -- similar to: clj --main cljs.main --compile hello-world.core --repl where it opens a webpage to localhost:9000.

jeffmk04:06:17

Just via clj -- similar to: clj --main cljs.main --compile hello-world.core --repl where it opens a webpage to localhost:9000.

jimka.issy12: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?

jimka.issy12: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

jimka.issy12: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

jimka.issy12:06:51

did Faulhaber also write the reader?

jimka.issy12:06:57

> Clojure was partly written in Common Lisp

jimka.issy12:06:11

I missed that skimming the paper.

jimka.issy12: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

jimka.issy12: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?

jimka.issy12:06:18

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

jimka.issy12:06:33

These historical perspectives are great stuff.

jimka.issy12:06:50

It's easy to forget unless someone documents them.

jimka.issy13: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.

mmakgaba13:06:29

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

jimka.issy13:06:47

Question: In clojure, what is an object ?

jimka.issy13: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.

jimka.issy13: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

jimka.issy13: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

jimka.issy13: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

sova14: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?

sova14:06:47

i thought it was for java to introspect itself

sova14:06:47

i thought it was for java to introspect itself

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)

sova14: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

sova14:06:14

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

sova14: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

sova14: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

sova14: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

sova14:06:47

where do i put that flag?

sova14:06:08

right on.

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

sova14: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

sova14: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?

sova14: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?

sova14:06:31

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

sova14: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

sova14: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

sova14:06:43

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

sova14: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

sova14: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

sova14:06:20

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

sova14: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.

sova14: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

doby16214: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

doby16214: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

sova14: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

amirali.sadeghloo18: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?

smith.adriane18:06:28

yup:

(defn my-fn [{:keys [num] :as m}]
  (when (pos? num)
    (prn num)
    (recur (update-in m [:num] dec))))
> (my-fn {:num 4})
4
3
2
1
nil

smith.adriane18:06:28

yup:

(defn my-fn [{:keys [num] :as m}]
  (when (pos? num)
    (prn num)
    (recur (update-in m [:num] dec))))
> (my-fn {:num 4})
4
3
2
1
nil

camsbury719:06:03

what is a nice way to split a collection on a predicate? i.e. split a map into two where one is for the key value pairs satisfying the predicate, and the other for the ones failing

camsbury719:06:03

what is a nice way to split a collection on a predicate? i.e. split a map into two where one is for the key value pairs satisfying the predicate, and the other for the ones failing

camsbury719:06:42

all I can think of is reduce-kv

camsbury719:06:42

all I can think of is reduce-kv

camsbury719:06:54

is there some kind of map-some idiom with maps to map over them and remove nils?

camsbury719:06:54

is there some kind of map-some idiom with maps to map over them and remove nils?

dmnkbrgr20:06:07

Hi everyone. Is it possible to have circular dependencies on two multimethods? I got it working for normal functions using resolve, but now I wanted to change them both into a multimethod, but it isn't working anymore. Instead it gives the following exception: java.lang.NullPointerException at puf.codegen.b$code_V.invokeStatic(b.clj:8). P.S.: I know this is generally not good code, but I am implementing something from uni where I have two functions which each depend on each other. Also I generally just want to find out why it would not work anymore with multimethods, I can of course just use simple functions instead.

dmnkbrgr20:06:07

Hi everyone. Is it possible to have circular dependencies on two multimethods? I got it working for normal functions using resolve, but now I wanted to change them both into a multimethod, but it isn't working anymore. Instead it gives the following exception: java.lang.NullPointerException at puf.codegen.b$code_V.invokeStatic(b.clj:8). P.S.: I know this is generally not good code, but I am implementing something from uni where I have two functions which each depend on each other. Also I generally just want to find out why it would not work anymore with multimethods, I can of course just use simple functions instead.

somedude31420: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

somedude31420:06:14

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