Fork me on GitHub
#clojure
<
2021-10-17
>
seancorfield00:10:35

None of the books I know of focus on that.

seancorfield00:10:08

Given that it's an inherently interactive topic, I'm not sure a book could really get the message across...?

🙂 1
borkdude07:10:59

@qqq it's not a book, but there are quite lengthy docs here: https://clojure.org/guides/repl/introduction kudos to Val

Muhammad Hamza Chippa09:10:21

is there any way to use inside clojurescript

jaihindhreddy11:10:22

Does with-redefs work with macros? The docstring doesn't explicitly say whether it works or not. And it seemingly doesn't work, and also permanently alters removes :macro true from the var meta:

user=> (defn wrap [macro-var]
  (let [f @macro-var]
    #(do (println "called")
         (apply f %&))))
#'user/wrap
user=> (:macro (meta #'or)) ; before
true
user=> (with-redefs-fn {#'or (wrap #'or)}
  #(or 1))
1
user=> (:macro (meta #'or)) ; after
nil
user=> 
"called" is never printed here. Just a curiosity, don't want to use it anywhere. Edit: another weird thing I don't understand, is the fact that macroexpansion calls the macro-fn with two additional args, the full form to expand, and clojure.lang.Compiler.LOCAL_ENV.get() (https://github.com/clojure/clojure/blob/b8132f92f3c3862aa6cdd8a72e4e74802a63f673/src/jvm/clojure/lang/Compiler.java#L6996) Is there a macro which uses these additional args?

yuhan11:10:11

I believe those are the special variables &form and &env which can be used for advanced metaprogramming - eg. Meander seems to make use of them a lot https://github.com/noprompt/meander/blob/3db621309cf0a6c5de6118b7a18bba16d7a9b430/src/meander/epsilon.clj

alexmiller12:10:32

Yes, those are the 2 extra

alexmiller12:10:08

The other thing you're missing is that Var also has a flag marking it as a macro

jaihindhreddy14:10:18

I appears there's already a ticket about this, https://clojure.atlassian.net/browse/CLJ-1867.

Jakub Zika14:10:34

Hi here, I am trying to turn this Kotlin code into Clojure

class StartView(
    private val grid: TileGrid
) : BaseView(grid, ColorThemes.arc()) {
    init {
        val msg = "Welcome to Caves of Zircon."
...
I am not sure how to extend BaseView class and override its init function like in the code above. I have tried to use proxy but i am not successful.
(defn start-view [grid]
  (let [view
        (proxy [BaseView View]
            [grid (ColorThemes/arc)]
            (init []
              (println "test")]
    (.init view)))
Fails with No matching field found: init for class zircon_clj.core.proxy$org.hexworks.zircon.api.view.base.BaseView$View$8abc44ac I dont know Kotlin and I cant find any good resources about Clojure / Kotlin interop examples… 😞

p-himik15:10:47

In Kotlin, init there is not a method, it's an init block. You don't need to create an init method for that in Clojure. More details on init blocks: https://kotlinlang.org/docs/classes.html#constructors

p-himik15:10:21

If you need to create a Java type in Clojure that requires some complex initialization, it's a common pattern to leave the type rather simple and write a separate function that creates an instance of that type and initializes it properly.

👍 1
Jakub Zika17:10:55

@U2FRKM4TW thank you, i will take a look tommorow.

Jakub Zika09:10:38

@U2FRKM4TW This is the whole example I am trying to rewrite:

class StartView(
    private val grid: TileGrid
) : BaseView(grid, ColorThemes.arc()) {
    init {
        val msg = "Welcome to Caves of Zircon."

        // a text box can hold headers, paragraphs and list items
        // `contentWidth = ` here is a so called keyword parameter
        // using them you can pass parameters not by their order
        // but by their name.
        // this might be familiar for Python programmers
        val header = Components.textBox(contentWidth = msg.length)
            // we add a header
            .addHeader(msg)
            // and a new line
            .addNewLine()
            // and align it to center
            .withAlignmentWithin(screen, ComponentAlignment.CENTER)
            .build() // finally we build the component

        val startButton = Components.button()
            // we align the button to the bottom center of our header
            .withAlignmentAround(header, ComponentAlignment.BOTTOM_CENTER)
            // its text is "Start!"
            .withText("Start!")
            // we want a box and some shadow around it
            .withDecorations(box(), shadow())
            .build()

        // We can add multiple components at once
        screen.addComponents(header, startButton)
    }
}
And screen is initialized by BaseView. I am not sure how to .. tbh

p-himik10:10:51

Just a guess:

(defn start-view [grid]
  (let [view (proxy [BaseView View] [grid (ColorThemes/arc)])
        msg "..."
        header (...)
        start-button (...)]
    (.addComponents (.-screen view) header start-button)
    view))

p-himik10:10:46

I'm also not sure you need that View there - I think BaseView should be enough.

Jakub Zika12:10:32

Thank you.. but screen which should be initialized inside BaseView can’t be found No matching field found: screen for class zircon_clj.core.proxy$org.hexworks.zircon.api.view.base.BaseView$ff19274a

Jakub Zika12:10:21

@U2FRKM4TW In Kotlin instead of instance initializer blocks (`{}`) we have the `init {}` block to initialize an object. In the `init{}` block of our view we’re going to initialize the `Component`s that will be displayed. You might have noticed that there is no `screen` symbol in our code…so why is it available? The reason is that `BaseView` creates a `Screen` for us in its constructor and we can use it in our `init` block! https://hexworks.org/posts/tutorials/2018/12/28/how-to-make-a-roguelike-views-screens-inputs.html

p-himik13:10:07

I see. So seems like screen is not publicly available then. If so, you might need to create an interface with a single member function that would initialize your proxy. Alternatively, you can just drop down to Java and use that particular Kotlin functionality via Clojure-Java interop. If you have a project somewhere that I can easily clone and run, I could tinker a bit with that extra interface idea.

Jakub Zika14:10:59

@U2FRKM4TW I appreciate your help. I do not have much experience with java (or interop itself). Just run REPL and core/-main i am trying to follow up the tutorial 1:1 basically for now https://github.com/zikajk/zircon-clj

p-himik15:10:29

Alright, it was easier than expected because Kotlin generates getters for such fields:

(defn start-view [grid]
  (let [p (proxy [BaseView] [grid (ColorThemes/arc)])
        s (.getScreen p)
        header (-> (header)
                   (.withText "Hello, from Caves of Zircon")
                   (.withAlignmentWithin s ComponentAlignment/CENTER)
                   .build)
        start-button (-> (button)
                         (.withAlignmentWithin s ComponentAlignment/BOTTOM_CENTER)
                         (.withText "Start!")
                         .build)]
    ;; Easier to call a single-arity method two times
    ;; than a var-arg method once because of interop.
    (.addComponent s header)
    (.addComponent s start-button)
    p))

❤️ 1
Jakub Zika16:10:59

@U2FRKM4TW Thanks a lot!

👍 1
p-himik16:10:29

BTW, just in case - if you're using IntelliJ IDEA, you can just open the decompiled versions of the Kotlin classes that you use, and you'll see what you can actually call from Clojure. And if you use some other IDE, there are a few tools to decompile jars and see what's inside.

👍 1
Jakub Zika16:10:25

I am using emacs / cider / lsp without any specific java <-> clojure tools

Jakub Zika08:10:03

@U2FRKM4TW Do you have an idea why this

val startButton = Components.button()
            // we align the button to the bottom center of our header
            .withAlignmentAround(header, ComponentAlignment.BOTTOM_CENTER)
            // its text is "Start!"
            .withText("Start!")
            // we want a box and some shadow around it
            .withDecorations(box())
            .build()
rewritten as (previous code updated with .withDecorations method)
start-button (-> (button)
                 (.withDecorations (ComponentDecorations/box))
                 (.withAlignmentAround header ComponentAlignment/BOTTOM_CENTER)
                 (.withText "Start!") 
                 .buil
fails with Execution error (ClassCastException) at zircon-clj.core/start-view (form-init13017672271589547111.clj:9). class org.hexworks.zircon.internal.component.renderer.decoration.BoxDecorationRenderer cannot be cast to class [Lorg.hexworks.zircon.api.component.renderer.ComponentDecorationRenderer; (org.hexworks.zircon.internal.component.renderer.decoration.BoxDecorationRenderer and [Lorg.hexworks.zircon.api.component.renderer.ComponentDecorationRenderer; are in unnamed module of loader 'app') Thank you

p-himik09:10:21

When you see [L it means that something is expecting an array. Are you trying to use var-arg Java functions in Clojure?

👍 2
p-himik09:10:41

That's exactly why I left that comment in my version of start-view above.

p-himik09:10:23

So .withDecorations is probably a var-arg method. See if there's .withDecoration alternative (singular). If so, switch to it. If there isn't, follow the link above and use the right interop.

👍 2
Jakub Zika09:10:13

@U2FRKM4TW Thank you again! This works well.

start-button (-> (Components/button)
                          (.withDecorations (into-array [(ComponentDecorations/box)]))
                          (.withAlignmentAround header ComponentAlignment/BOTTOM_CENTER)
                          (.withText "Start!") 
                          .build)]

👍 2
Jakub Zika12:10:28

@U2FRKM4TW 😔 Do you have any experience with casting Clojure function to kotlin.jvm.functions.Function* ?

startButton.onActivated {
    replaceWith(PlayView(grid)) // 1
}
rewritten to
(.onActivated start-button #(.replaceWith p (play-view grid)))
fails with Execution error (ClassCastException) at java.lang.Class/cast (Class.java:3605). Cannot cast zircon_clj.core$start_view$fn__9094 to kotlin.jvm.functions.Function1 Btw. i am surprised that i cant find anything about clojure <-> kotlin interop (or clojure <-> java <-> kotlin interop)

p-himik12:10:04

But you can find tons of materials on Clojure-Java interop and on Java-Kotlin interop. :)

😀 1
p-himik12:10:32

Found this spec: https://github.com/JetBrains/kotlin/blob/master/spec-docs/function-types.md#functions-with-022-parameters-at-runtime So seems like Function1 is a simple interface with just one function. So, you can implement it inline:

(import '(kotlin.jvm.functions Function1))

(.onActivated start-button (reify Function1
                             (invoke [this event]
                               ...)))

p-himik12:10:40

BTW, it's just a nitpick but your :import form is something that I almost never see. It works, but it seems to me that the majority of people use the style outlined here: https://stuartsierra.com/2016/clojure-how-to-ns.html

👍 1
Jakub Zika13:10:06

That jetbrains’ docs are really usefull, thanks again. Yeah, i will make the code more idiomatic - the interop fight seems to be almost over and there is nothing stopping me from bringing zircon into clojure 🙂.

🎉 1