Fork me on GitHub
#clojure
<
2020-01-24
>
seancorfield04:01:48

@deleted-user Have you looked at the side-effects macro in Expectations?

Jakub Holý (HolyJak)13:01:49

Hello, any tips how to avoid "IllegalAccessException: access to public member failed" when calling a default interface method under java 11 (it worked under java-8? See https://stackoverflow.com/questions/59897831/clojure-fails-to-call-an-interface-default-method-on-java-11-due-to-illegalacces for details. Thank you!

Jakub Holý (HolyJak)16:01:00

FYI @alexmiller calling the default interface method failed due to the way the proxy is coded so it has nothing to do with Clojure:

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Proxy: calling " + method);
        // Default methods are public non-abstract instance methods
        // declared in an interface.
        if (((method.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) ==
                Modifier.PUBLIC) && method.getDeclaringClass().isInterface()) {
            // see 
            final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
            if (!constructor.isAccessible()) {
                constructor.setAccessible(true);
            }
            final Class<?> declaringClass = method.getDeclaringClass();
            return constructor.newInstance(declaringClass, MethodHandles.Lookup.PRIVATE)
                    .unreflectSpecial(method, declaringClass)
                    .bindTo(proxy)
                    .invokeWithArguments(args);

        }
        return null;
    }

Alex Miller (Clojure team)14:01:13

which Clojure version are you using?

Alex Miller (Clojure team)14:01:30

we have fixed a couple tickets for this in recent releases

Alex Miller (Clojure team)14:01:00

don't know, would be happy to see a repro bug report at either http://ask.clojure.org or jira if you have access

👍 4
vemv14:01:58

Today I started setting -Dclojure.read.eval=false in production-like environments, as a security measure (one never knows which piece of code might do a read-string... present or future) However this caused a few libraries to fail. That was to be expected (and could be trivially fixed w/ binding), however sometimes I didn't really see a #=(...) in those codebases at all. That left me wondering if there's a subtle way in which libraries use the read-eval feature?

Alex Miller (Clojure team)14:01:23

which libraries and why did they fail?

vemv14:01:51

EvalReader not allowed when *read-eval* is false (is that sufficient info? would rather not share the libraries I use)

Alex Miller (Clojure team)14:01:55

not sufficient enough to know what's causing that

Alex Miller (Clojure team)14:01:09

#= is undoc'ed and not a public feature of Clojure, so generally no one should be using it

👍 4
Alex Miller (Clojure team)14:01:57

(it is used internally in parts of Clojure, ie print/read roundtrip on some types)

Alex Miller (Clojure team)14:01:35

reading java classes with #My.Class... is another thing that can read eval. that's public (for records in particular), but not common

4
Alex Miller (Clojure team)15:01:17

you can also run into the same issue if you happen to use tools.reader

vemv15:01:27

Thanks for the all pointers. I'll make sure to check them out and if something was still not clear I'd post a reproducer in ask.clojure (for posterity :) )

sogaiu15:01:52

just as data point, i think i have seen #= used on more than one occasion in lein project.clj files - often for obtaining a version string.

Alex Miller (Clojure team)15:01:06

people do abuse it, but it is not a public or supported feature

sogaiu15:01:12

yes, thanks for pointing that out. after your remark above to that effect, i noticed a similar statement at the bottom of: https://clojure.org/guides/weird_characters

Alex Miller (Clojure team)15:01:53

yes, I wrote that :)

🙂 4
lvh17:01:40

Can I generally count on expressions in data structure literals being evaluated in the order they appear in the source code, or should I be using an explicit let (regardless of style)

lvh17:01:08

I'm shelling out to a binary and one expression produces side effects in the environment that affects the second expression

lvh17:01:40

(let [a (atom 0)] {:x (swap! a inc) :y @a})
;; => {:x 1, :y 1}
is that a happy accident or a language property

hiredman17:01:54

Sort of both

hiredman17:01:38

But if you really care about ordering I would make it explicit

hiredman17:01:59

The ordering in map literals only holds for small maps

delaguardo17:01:27

it is not guaranteed even for small maps

dpsutton17:01:20

that was way too large

hiredman17:01:55

It sort of is, because the reader creates array maps and it has been this way for so long that people are almost certainly relying on the behavior so it is unlikely to change

delaguardo17:01:52

this is not documented (at least I’m not aware) - meaning - you should not rely on it

hiredman17:01:09

I mean, it is

hiredman17:01:47

Maps start out as array maps and become hash maps at 8 elements and this is a well known thing

delaguardo17:01:06

and? why I should rely on order in map reading? the way how reader actually reads data is hidden from me

didibus17:01:00

This is probably similar to the read-eval :p, you shouldn't assume its not going to break in a future version of Clojure, but it most likely won't because in general core maintainers value not breaking people's code, even when they made unfortunate use of implementation details

hiredman17:01:33

Look dude, whatever, as I said if you care about ordering you should make it explicit, but for the most common cases of map literals in code (small maps) the order will be as written and that is unlikely to be ever change

delaguardo17:01:45

good luck with refactoring)

didibus17:01:46

But, it should be acknowledged though, that it is more likely to change than most other things.

didibus17:01:06

Also less likely to be portable across dialects

didibus17:01:38

If those risks are acceptable, its totally fine to depend on order of small maps

delaguardo17:01:35

it is not fine because you are making your code vulnerable. It can break if you add more entries to the map for example

didibus18:01:34

Ya, that's true. I guess I'm not trying to impose my own trade off assessment, but simply to provide the necessary information for people to make the choice themselves.

didibus18:01:33

Though I'm 100% with you, it wouldn't pass my code review at work

didibus18:01:09

Okay, that all said, I think we didn't answer @lvh question

didibus18:01:45

Which was if the literal guaranteed order of evaluation

didibus18:01:11

For example, this: (hash-map (doA) (doB) (doC) (doD)) Will guarantee left to right synchronous execution of the do fns.

didibus18:01:52

But will: {(doA) (doB) (doC) (doD)} ? I actually don't know. I think so. But I'm interested to know for sure as well.

Alex Miller (Clojure team)18:01:39

the values are guaranteed to be read in text order, but the eval order is not guaranteed

Alex Miller (Clojure team)18:01:47

and you should not rely on it

lvh18:01:28

awesome, thanks @alexmiller 🙂

Alex Miller (Clojure team)18:01:35

or at least, that is my opinion

lvh18:01:49

(I wanted to move it into a let for explicitness anyway but I wasn't sure if that was a bug or just style)

didibus18:01:29

Cool, but for function evaluation like hash-map I'm correct that there evaluation order is a guarantee right?

Alex Miller (Clojure team)18:01:41

afaik, this is not documented in reference docs, so I would treat it as undefined

Alex Miller (Clojure team)18:01:18

sorry, that comment was in reference to my prior statement, not in answer to you didibus

kwladyka18:01:23

@didibus do a Thread/sleep to be sure

Alex Miller (Clojure team)18:01:50

for hash-map, I would say, yes, that should be expected

didibus18:01:24

Cool! :the_horns:

kwladyka18:01:31

and remember if this is lazy value it can be evaluated much later than you expect at all

Alex Miller (Clojure team)18:01:01

for the specific case of hash-map, which is a function. macros of course are not constrained to that

💯 4
didibus18:01:32

@kwladyka That shouldn't be. I mean, yes what is lazy will evaluate later, but function arguments are supposed to be evaluated eagerly as far as I know. Assuming they are functions and not macros.

kwladyka18:01:56

hmm it looks like that, I thought first on this collection wouldn’t call all fn

kwladyka18:01:17

but I was wrong

didibus18:01:26

You can wrap it in a delay if you want to make it lazy

👍 4
Alex Miller (Clojure team)18:01:03

Clojure has eager eval of args (as opposed to something like Haskell), but eval does not necessarily mean realize

kwladyka18:01:07

(let [lazy-foo #(lazy-seq (println 1))]
  (last [(println 1) (lazy-foo) (println 3)]))
1
3
=> nil
actually this is not true. With lazy seq all fn are not evaluated

kwladyka18:01:29

and this is what I was thinking about

kwladyka18:01:40

when order matter

kwladyka18:01:57

I would force this to evaluate always with doall or other fn

ghadi18:01:47

you are incorrect here

ghadi18:01:31

it's still creating a lazy seq. When the seqfunction is called on that lazy-seq is when its thunk is evaluated (when the print happens)

ghadi18:01:07

it happens that your example avoids calling seq on that second item of the vector

ghadi18:01:20

(it doesn't have to, just skips over the item)

kwladyka18:01:51

(let [lazy-foo #(lazy-seq (println 1))
      s [(println 1) (lazy-foo) (println 3)]]
  (first s)
  (last s))
1
3
=> nil

ghadi18:01:02

same deal

ghadi18:01:08

the lazy seq is created

ghadi18:01:22

seq creation is not the same as seq realization

kwladyka18:01:45

but the context is: > left to right synchronous execution of the do fn

kwladyka18:01:59

so it doesn’t guarantee

ghadi18:01:08

you are conflating two different things

ghadi18:01:55

arguments are eagerly evalued, that particular seq was created, just never realized

ghadi18:01:10

realized for a lazyseq means something called seq on it

kwladyka18:01:19

My assumption is that was purpose of this question. Unless I didn’t understand intention.

Alex Miller (Clojure team)18:01:06

lazy evaluation and lazy sequence realization both use the word lazy, but mean different things

ghadi18:01:12

(defn f
  [arg]
  arg
  :done)

(let [a-lazy-seq #(lazy-seq (println :never))]
  (f (a-lazy-seq)))

ghadi18:01:29

no print happens, but f returns :done

ghadi18:01:32

(let [a-lazy-seq #(lazy-seq (println :never))]
  (seq? (a-lazy-seq)))
=> true

didibus18:01:59

Wrapping in lazy seq is similar to wrapping in delay somewhat. Now when the argument gets eagerly evaluated, it evaluates the lazy-seq macro. The macro in turn will not evaluate the given println, instead it will return a sequence of thunks which later, when realized, will evaluate the contained println

didibus19:01:25

(defmacro super-lazy-seq [& body]
  (println "super lazy seq was evaluated")
  `(lazy-seq ~@body))

(first [(println 1) (super-lazy-seq (println 2))])

super lazy seq was evaluated
1
nil

kwladyka19:01:26

I know, but I understood the intention of the question to evaluate fn from left to right. So under the hood because of side effect or something.

didibus19:01:13

We might be saying the same thing in different words then.

ghadi19:01:44

@kwladyka there's no magic... fn args are evaluated left to right

didibus19:01:37

Only for Functions though. lazy-seq is a macro, so its arguments are evaluated in custom order based on the macro definition

kwladyka19:01:13

yeah but the question was to guarantee

didibus19:01:23

The first function does evaluate all arguments in order from left to right eagerly before executing. But one of its argument is a macro which returns a lazy sequence, that macro being a macro will not necessarily evaluate its argument from left to right eagerly, which is why in this case println does nothing.

kwladyka19:01:24

doesn’t matter, everything is clear 🙂

😄 8
ghadi19:01:34

there was a separate question about map evaluation order

ghadi19:01:03

{:a (call1) :b (call2)}

ghadi19:01:28

for map literals

didibus19:01:13

Summary: 1. Function evaluation is guaranteed eager and in order from left to right 2. Macro evaluation is different for every macro, read its documentation to know how a particular macro evaluates (if at all) its arguments. 3. Map/Set literals evaluation is eagerly evaluated, but order is undefined. Assume it could be in any order. 4. Vector/List literals evaluation is eagerly evaluated, and order is guaranteed from left to right.

ghadi19:01:16

order undefined for things that don't have order, specifically

ghadi19:01:18

(maps/sets)

didibus19:01:59

Oh, I see. So you can assume literal vectors evaluate in order of left to right?

bfabry19:01:51

oh that's interesting, I never even thought about that

didibus19:01:43

Ya, I learn every day

ghadi19:01:32

same with lists, they have order

didibus19:01:33

And list literal is a tricky one, because it quotes. So nothing is actually evaluated

didibus19:01:07

Is it even fair to call ' a list literal?

ghadi19:01:47

no it's quotation

ghadi19:01:59

can quote a map, people do it all the time in datomic

didibus19:01:43

Or it just happens to create a list of unevaluated forms?

ghadi19:01:05

(1 2 3) is a list literal

didibus19:01:21

Hum.. oh ya, I forgot about that :)

ghadi19:01:30

whether a REPL is treating that as code is a separate matter

didibus19:01:34

Yup okay. Got it. So ya, list literal are also guaranteed in order evaluation. Cool, that one makes sense, otherwise code would evaluate in undefined order :p

sogaiu22:01:58

don't know why i did it, but (.File/pathSeparator) seems to work (as compared with .File/pathSeparator). is that an intentionally working / supported thing? i don't get the sense that it matches any of what's listed here: https://clojure.org/reference/java_interop#_member_access

bfabry22:01:26

the first is an example of invoking a static java method

borkdude22:01:01

but pathSeparator is not a method, but a field

didibus03:01:51

Is it a static builder?

didibus03:01:17

For me this works: .File/pathSeparator

didibus04:01:03

Hum I see, they both work

borkdude22:01:44

Was the Foo/bar syntax to access static fields introduced after the dot form maybe?

borkdude22:01:14

(.File/pathSeparator) expands to (. .File pathSeparator)

borkdude22:01:19

which is valid for accessing a field according to the docs, but it seems to be ambiguous with method calls, so the compiler might do some sort of reflection to disambiguate?

hiredman22:01:25

under the hood for clj the '.' special form is the only host interop

hiredman22:01:50

static field access desugars to it as well

hiredman22:01:15

because on the jvm you can use reflection to distinguish between the different kinds of things '.' does

hiredman22:01:46

for cljs .- does something slightly different from .

borkdude22:01:59

would you say (.File/pathSeparator) is "idiomatic" for accessing a field though? I would say no, because this make it look like a call?

noisesmith22:01:15

it works, but not idiomatic IMHO

noisesmith22:01:35

I mean, (Math/PI) works too, but is even more obviously wrong

noisesmith22:01:01

(uh oh, my program is slow, better take out the parts where I calculate pi!)

borkdude22:01:18

so in theory it could be a feature for an alternative Clojure implementation to not resolve (Math/PI) to Math/PI and only allow the latter for field access?

noisesmith22:01:33

oh the other hand, I've had coworkers who insisted on using (PersistentQueue/EMPTY) instead of PersistentQueue/EMPTY because using a static field as a data structure feels weird

borkdude22:01:16

For compatibility it might be better to just support it I guess

bfabry22:01:11

fwiw, in clojurescript (js/document) is invalid. so there's already a clojure implementation that distinguishes

borkdude22:01:31

hmm, good point

bfabry22:01:18

and for that reason prob a good idea not to get in the habit of using it. in case you ever want to write cljs. better to keep the muscle memory consistent

hiredman22:01:53

(js/document) is a whole other thing because the js/ prefix is special

bfabry22:01:07

it's also true of Math/PI vs (Math/PI)

hiredman22:01:27

(js/document) is not an interop field or method invoke

borkdude22:01:48

cljs.user=> (macroexpand '(Math/PI))
(Math/PI)

user=> (macroexpand '(Math/PI))
(. Math PI)

bfabry22:01:52

cljs.user=> Math/PI
3.141592653589793
cljs.user=> (Math/PI)
#js {:isException true, :value "TypeError: Math.PI is not a function\n at <anonymous>:1:33"}

hiredman22:01:26

(.- Math PI)

hiredman22:01:26

cljs tries to map clj's java interop to js interop, but js is not java so it isn't very clean

hiredman22:01:28

js has no such thing as static fields, and there is no way to distinguish fields from methods

hiredman22:01:01

in the Math/PI case, Math is actually an object that PI is instance field of

hiredman22:01:22

so the most correct thing would be something like (.- js/Math PI)

borkdude22:01:00

which nobody does

😂 4
sogaiu23:01:41

thanks all for the exploration 🙏