Fork me on GitHub
#clojure
<
2022-04-05
>
Al Baker01:04:06

are there any clojure learning materials for kids? I've got a 9 and 13 year old that it's basically time to get these kids programming.. I've been using Clojure for the Brave and the Bold as a good starter, along with gorilla repl as their workspace

Ben Sless04:04:11

I'll look into night code, Zach Oaks used it as a teaching environment for children, it's interactive and they can get their hands dirty by modifying a game and tinkering instead of pedagogically learning to program More fun and engaging https://github.com/oakes/Nightcode

Ben Sless04:04:38

Even shoot him a message and ask if he has lesson plans

dgb2307:04:34

Have they tried http://www.maria.cloud? It’s very visual and friendly.

👍 1
practicalli-johnny11:04:20

There are beginner friendly learning paths at https://clojurebridgelondon.github.io/

borkdude09:04:26

I'm expecting "no" but is it possible to do something like this?

(reify java.util.Iterator (remove [this] (if-let [m (get methods 'remove)] (m this) (invoke-default-method this))))
Where methods is a map of symbol to function and if not found, invoke the default method from the interface. How does one get invoke-default-method, if at all possible?

borkdude09:04:38

Maybe it's possible using reflection

borkdude09:04:08

This is still invoking the provided implementation:

user=> (let [m (first (.getMethods java.util.Iterator))] (.invoke m (reify java.util.Iterator (remove [this] (prn :hello))) nil))
:hello
nil

tatut09:04:16

(.invoke (->> "java.util.Iterator" Class/forName .getMethods (filter #(= (.getName %) "remove")) first) (reify java.util.Iterator) (into-array Object []))

tatut09:04:24

so perhaps a default method invocation system could be done

borkdude09:04:28

@U11SJ6Q0K It should work with reified objects that have already an implementation for remove

borkdude09:04:39

like in the example above your example

tatut09:04:46

so could bb reify automatically add any missing methods with default invocations? idk how well graalvm reflection works

tatut10:04:25

ah, but there was already some master object, that implemented all the interfaces in bb

borkdude10:04:35

No, you can't do that :/ The way it works now is: there is a reified implementation which dispatches dynamically, but there's already a 'remove method. Yes, indeed.

borkdude10:04:13

It might be possible with findSpecial

borkdude10:04:25

Something like this:

(def method-handle (.findSpecial java.util.Iterator "remove" (java.lang.invoke.MethodType/methodType nil)))

borkdude10:04:45

And then:

(let [m (first (.getMethods java.util.Iterator))] (let [o (reify java.util.Iterator (remove [this] (prn :hello)))] (let [m (.bindTo method-handle o)] (.invokeWithArguments m))))

borkdude10:04:53

But can't figure out the exact interop

borkdude10:04:39

A little closer:

(def method-handle (.findSpecial lookup java.util.Iterator "remove" (java.lang.invoke.MethodType/methodType (Void/TYPE)) java.util.Iterator))

borkdude10:04:59

But now:

user=> (let [o (reify java.util.Iterator (remove [this] (prn :hello)))] (.invoke ^java.lang.invoke.MethodHandle method-handle o))
Execution error (ClassCastException) at user/eval326 (REPL:1).
class user$eval326$reify__327 cannot be cast to class [Ljava.lang.Object; (user$eval326$reify__327 is in unnamed module of loader clojure.lang.DynamicClassLoader @14e2e1c3; [Ljava.lang.Object; is in module java.base of loader 'bootstrap')

borkdude10:04:47

user=> (let [o (reify java.util.Iterator (remove [this] (prn :hello)))] (let [m (.bindTo method-handle o)] (.invoke ^java.lang.invoke.MethodHandle m (into-array []))))
Execution error (WrongMethodTypeException) at java.lang.invoke.MethodHandle/asTypeUncached (MethodHandle.java:861).
cannot convert MethodHandle()void to (Object[])Object
:thinking_face:

borkdude10:04:56

How does one write java.lang.invoke.MethodType.methodType(void.class) in Clojure?

eskos10:04:51

(java.lang.invoke.MethodType/methodType (Void/TYPE))
Perhaps? At least seems to work in my REPL.

eskos10:04:55

The Void type is a tricky one…

borkdude10:04:08

Bingo!

user=> (def method-handle (.findSpecial lookup java.util.Iterator "remove" (java.lang.invoke.MethodType/methodType (Void/TYPE)) java.util.Iterator))
#'user/method-handle
user=> method-handle
#object[java.lang.invoke.DirectMethodHandle$Special 0x29c5ee1d "MethodHandle(Iterator)void"]
user=>

🎉 1
borkdude10:04:11

Any ideas one this one?

(let [o (reify java.util.Iterator (remove [this] (prn :hello)))] (let [m (.bindTo method-handle o)] (.invoke ^java.lang.invoke.MethodHandle m (into-array []))))
Execution error (WrongMethodTypeException) at java.lang.invoke.MethodHandle/asTypeUncached (MethodHandle.java:861).
cannot convert MethodHandle()void to (Object[])Object

borkdude10:04:33

The goal is to execute the default method of the interface - not the overridden method

eskos11:04:07

I’m not sure if that’s possible from outside the object, you’d need to get the super reference within for that.

borkdude11:04:34

This is where I hoped that findSpecial would help

borkdude11:04:59

But if not, then reflecting on o 's superclass can possible be done?

borkdude11:04:27

I'm afraid that will still invoke the virtual method, which I think can be prevented with findSpecial, but not sure

eskos11:04:04

There’s https://clojuredocs.org/clojure.core/proxy-super which seems sort of related but I’ve never touched this part of Clojure so dunno really… Reflection sort of works, but yeah, the virtual method call goes to the instance which follows the encapsulation rules. You could define an additional method for getting the super instance and call it from there, but that’d be quite hacky.

eskos11:04:37

And I can’t guarantee that would work either 🙂 Although my memory is getting fuzzy on all things Java, have had a 5 year pause at it at this point…

Alex Miller (Clojure team)12:04:53

For a direct call, you really need to be in a direct subclass and I think proxy is your only path on Clojure to call the super. For reflection, I think the key is you’d want super to be the target and not sure there is any way to do that. Maybe back out to the actual problem?

borkdude12:04:16

Sure. The actual problem is that I need to construct a reified Object at compile time that dispatches on functions dynamically, so I have to already implement those methods before-hand

borkdude12:04:26

But when someone doesn't provide an implementation, I want to call to the default method.

borkdude12:04:40

Perhaps I can make the compile time object using proxy, that would be an interesting option

Alex Miller (Clojure team)13:04:56

Do you actually mean an interface default method?

Alex Miller (Clojure team)13:04:48

Isn't the whole point that you don't explicitly do anything? If the method is absent you should get the default

borkdude13:04:45

Yes, but the problem is that in SCI you don't know when the method is absent, unless you pre-generate all permutations (which leads to a big image size). So it has to provide some object with all methods already in place

borkdude13:04:11

The idea is: one compile time object, which dispatches for all runtime (interpreted) representations of that object

Alex Miller (Clojure team)13:04:40

In Java, you can make this explicit call with TheInterface.super.theMethod(…) but there is no such syntax to say that in Clojure

Alex Miller (Clojure team)13:04:41

Maybe it's possible to do with reflection, have never tried that

borkdude13:04:31

I tried this using MethodHandles but when you have the methodhandle it still invokes the "virtual" method. I read something about findSpecial which may provide a workaround for this

borkdude13:04:24

This is what I got so far (not working yet):

(def method-handle (.findSpecial lookup java.util.Iterator "remove" (java.lang.invoke.MethodType/methodType (Void/TYPE)) java.util.Iterator))

user=> (let [o (reify java.util.Iterator (remove [this] (prn :hello)))] (let [m (.bindTo method-handle o)] (.invoke ^java.lang.invoke.MethodHandle m (into-array []))))
Execution error (WrongMethodTypeException) at java.lang.invoke.MethodHandle/asTypeUncached (MethodHandle.java:861).
cannot convert MethodHandle()void to (Object[])Object

ghadi14:04:42

Clojure doesn't know what to do with signature polymorphic calls, like MethodHandle.invoke* and VarHandle.*

borkdude14:04:38

@U050ECB92 Good to know. But in this case, there is only one overload of remove right?

ghadi14:04:42

yes, it found the correct call (when you grab a MethodHandle it is already narrowed down to a single target)

ghadi14:04:47

but when it is trying to invoke it, it's trying to invoke it in an Object returning context

ghadi14:04:08

sig polymorphism is a wild special case in the vm

ghadi14:04:27

> When the JVM processes bytecode containing signature polymorphic calls, it will successfully link any such call, regardless of its symbolic type descriptor.

borkdude14:04:37

@U050ECB92 Is there any way to do this in Clojure, or should I revert to Java for this?

ghadi14:04:14

(unless you write bytecode)

borkdude14:04:24

(oh god no, not yet)

ghadi14:04:00

btw why do you need to execute the default intf method from inside the impl?

borkdude14:04:08

@U050ECB92 In SCI I cannot use reify to implement reify at runtime. So I need to create one reified object at compile time which dynamically dispatches to a map of implementations at runtime. But if a method is not provided in that map, I need to call the default method.

Joshua Suskalo17:04:38

Gotta say, writing code against invoke with https://github.com/jgpc42/insn is actually really pleasant, and you can do nice things like define clojure ifns that have bytecode bodies like

(bc/fn something ^long [^long n]
  [[:lload 1]
   [:ldc2 1]
   [:ladd]
   [:lreturn]])

borkdude17:04:15

Looks interesting!

borkdude17:04:44

Maybe I'll just automate the .java class generation though and compile into an artifact

borkdude20:04:54

Ended up going with the bytecode approach using insn. Thanks for the suggestion @U5NCUG8NR!

Yuhri Graziano Bernardes11:04:06

Hi eveyone \o Hope you're all doing well. Does anyone know where can I find documentation about clj -Ttools? I tried clj -Ttools --help, clj -Ttools --help but got errors. I tried to google it too but don't found anything.

practicalli-johnny11:04:22

There is description and examples of Clojure CLI execution options at https://practical.li/blog/posts/clojure-which-execution-option-to-use/ Includes examples of using -T as an installed tool and with aliases

❤️ 1
Yuhri Graziano Bernardes11:04:50

Thanks! It's really great!

Joshua Suskalo16:04:33

Is there a particular reason that the forkjoin pool and fjtask are public in reducers but fjinvoke, fjfork, and jfjoin are private?

Joshua Suskalo16:04:16

It seems to me that it'd be useful to have all of them be public for people who wish to implement CollFold in libraries, like I'd like to do with my rope implementation.

Alex Miller (Clojure team)17:04:19

don't know off-hand - if you have a concrete request, interested in a better problem statement at https://ask.clojure.org - are you looking to do something like what the PHM extension does?

Joshua Suskalo17:04:14

I'm not familiar with what the PHM extension is. The concrete example of what I want to do is just implement CollFold for a rope, which is a tree structure for constant time concatenation in the average case of long sequences, mostly used in text editors. Since these tend to be very large structures, having CollFold could help a lot with performance sensitive things. I can throw something on the website later today.

seancorfield20:04:38

I ran into this when I implemented fold for next.jdbc and just copied them into next.jdbc.result-set as private functions.

;; ForkJoinTask wrappers copied in from clojure.core.reducers to avoid
;; relying on private functionality that might possibly change over time

(defn- fjtask [^Callable f]
  (ForkJoinTask/adapt f))

(defn- fjinvoke
  "For now, this still relies on clojure.core.reducers/pool which is
  public but undocumented."
  [f]
  (if (ForkJoinTask/inForkJoinPool)
    (f)
    (.invoke ^ForkJoinPool @r/pool ^ForkJoinTask (fjtask f))))

(defn- fjfork [task] (.fork ^ForkJoinTask task))

(defn- fjjoin [task] (.join ^ForkJoinTask task))

Joshua Suskalo20:04:50

Yeah, I'm considering doing the same.

seancorfield20:04:44

The direct use of @r/pool bothers me, but the other functions are "trivial" and don't feel like they need to be part of the reducers API...?

marciol19:04:53

Hey, given that core/assert-args macro is private, but really useful, do you see any problem in copying it to our projects, into a util or misc namespace?

fadrian20:04:56

Does anyone have a pointer to a library/program that figures out stats on Clojure program files? I'm thinking of # of functions/macros, LoC with/without comments, etc. I figure if I had do, I could spin my own crappy version, but there's no sense in re-inventing stuff that already exists and, if there is something out there, it probably deals better with corner cases in the language than I have time to fool around with.

phill23:04:08

The venerable cloc.pl (I think its page is http://cloc.sourceforge.net/) detects and counts Clojure. cloc.pl has no Clojure-specific metrics, but if you are counting heterogeneous stuff like a project that also includes Java, XML, XSL, CSS, bash, etc etc etc, it is quite convenient.

Joshua Suskalo20:04:58

I can't say if such a tool already exists, but I can say that such a tool could almost trivially be implemented atop #clj-kondo, so maybe head over there and ask about the analysis output if you can't find anything that meets your needs.

winsome20:04:51

There's also https://github.com/jpmonettas/clindex where you can write arbitrary queries about a codebase

fadrian20:04:11

Thank you both. clindex seems to deal more with the semantics of the various constructs rather than the syntactic structure, but tres interesting nonetheless.