Fork me on GitHub
#clojure
<
2023-12-09
>
Casey10:12:11

After all these years of clojure I'm still pretty much a macro newbie. Any hints why this relatively simple macro isn't working?

(defmacro to-varargs [class-name & args]
  `(into-array ~class-name ~args))

Casey10:12:25

;; My macro to wrap a list of args into an array for java interop
(defmacro to-varargs [class-name & args]
  `(into-array ~class-name ~args))

;; Let's see what it expands to..
(macroexpand '(to-varargs String "foo" "bar"))
;; => (clojure.core/into-array String ("foo" "bar"))

;; Works..
(into-array String '("foo") )
;; => #<[Ljava.lang.String;@ac8eb0f>

;; Does not work..
(to-varargs String "foo" )
;; => Execution error (ClassCastException) at button2/eval49362 (REPL:23).
;;    class java.lang.String cannot be cast to class clojure.lang.IFn (java.lang.String is in module java.base of loader 'bootstrap'; clojure.lang.IFn is in unnamed module of loader 'app')

Casey10:12:25

(this might seem like not much, but i'm doing some heavy interop where I need to call lots of methods that take only varargs, and I want to cut down the keystrokes a bit)

lassemaatta10:12:27

I know very little of macros, but if I had to guess the difference is ("foo" "bar") vs '("foo" "bar")

❤️ 1
lassemaatta10:12:06

that is, your macro expansion attempts to invoke "foo" as a function

p-himik10:12:58

That's correct. When writing a macro, the very first question that should be asked is "do I need it as a macro". :) So, do you need it as a macro? into-array already exists and works. Even if you for some reason absolutely must pass values as separate and not in a vector, you can write a wrapper function - not a macro.

❤️ 1
oyakushev13:12:00

Like @U0178V2SLAY said, it's because ~args expands into a list, and the compiler treats it as a function invocation. Here's the fix:

(defmacro to-varargs [class-name & args]
  `(into-array ~class-name ~(vec args)))
Here, you transform the list of args into a vector, so that it can be safely embedded into the code as a collection, not as a function call.

❤️ 1
JI Lao12:12:48

ihello, Cursive plugin in idea has no shortcut to execute the last executed code block ?, I can only execute the result of the code block where the current cursor is now. but sometimes I need to change the content of a function in another location, and then I want to validate the last executed block in the comment macro directly. I don't want the cursor to jump over

p-himik12:12:34

There's #C0744GXCJ. When you need to load code A that depends on B, then modify B and retest A, that means that B itself needs to be loaded, thus becoming the last executed code block. So your need can't be answered by a plain "re-execute the most recently executed code block".

p-himik12:12:35

Cursive has REPL history - can be helpful here if you really don't want to reposition your cursor. If you run a particular code block multiple times, there are REPL commands that you can create yourself and assign a shortcut to. And if you're fine with repositioning the text cursor as long as you don't have to do it manually, there are Back and Forward controls, as well as named bookmarks.

JI Lao13:12:26

Thank you for your patient reply. These methods can solve my problem very well.

👍 1
agorgl13:12:52

Hello there! What would be the clojure idiomatic way to iteratively consume an input while generating an output from each part consumed? Lets say I have a vector of (string) tokens, and I want to make a parse function that consumes some of the input tokens, and produces some output value, and then I'll have to call the parse again on the remaining input tokens for the next output value etc.

p-himik13:12:10

Could you please edit the message and move large code blocks into attachments or the thread, so it doesn't hog the whole screen?

agorgl13:12:59

Yes, gimme a sec

agorgl13:12:45

To make this easier, I have this sample input:

interfaces {
    ethernet eth0 {
        address dhcp
        hw-id 00:53:00:00:aa:01
    }
    loopback lo {
    }
}
service {
    ssh {
        port 22
    }
}
system {
    config-management {
        commit-revisions 20
    }
    console {
        device ttyS0 {
            speed 9600
        }
    }
    login {
        user vyos {
            authentication {
                encrypted-password ****************
            }
            level admin
        }
    }
    ntp {
        server  {
        }
        server  {
        }
        server  {
        }
    }
    syslog {
        global {
            facility all {
                level notice
            }
            facility protocols {
                level debug
            }
        }
    }
}
That I tokenized to lines of tokens using these functions
(defn split-keeping-quoted [s]
  (re-seq #"[^\s\"]+|\"[^\"]*\"" s))

(defn tokenize-config [input]
  (->> input
       (str/split-lines)
       (map split-keeping-quoted)))
Into:
(("interfaces" "{")
 ("ethernet" "eth0" "{")
 ("address" "dhcp")
 ("hw-id" "00:53:00:00:aa:01")
 ("}")
 ("loopback" "lo" "{")
 ("}")
 ("}")
 ("service" "{")
 ("ssh" "{")
 ("port" "22")
 ("}")
 ("}")
 ("system" "{")
 ("config-management" "{")
 ("commit-revisions" "20")
 ("}")
 ("console" "{")
 ("device" "ttyS0" "{")
 ("speed" "9600")
 ("}")
 ("}")
 ("login" "{")
 ("user" "vyos" "{")
 ("authentication" "{")
 ("encrypted-password" "****************")
 ("}")
 ("level" "admin")
 ("}")
 ("}")
 ("ntp" "{")
 ("server" "" "{")
 ("}")
 ("server" "" "{")
 ("}")
 ("server" "" "{")
 ("}")
 ("}")
 ("syslog" "{")
 ("global" "{")
 ("facility" "all" "{")
 ("level" "notice")
 ("}")
 ("facility" "protocols" "{")
 ("level" "debug")
 ("}")
 ("}")
 ("}")
 ("}"))
My goal is to transform this tokenized input into this:
[[:interfaces
  [[:ethernet "eth0" [
     [:address "dhcp"]
     [:hw-id "00:53:00:00:aa:01"]]]
   [:loopback "lo"] []]]
 [:service
  [:ssh
   [:port "22"]]]
...
]
(Moved here from main question to not hog the screen as suggested)

oyakushev13:12:04

You want to parse a flat list of tokens into a structured tree. While this is possible to do just with Clojure and might be a good exercise, but I'd suggest taking an existing parsing library for that. Namely, https://github.com/Engelberg/instaparse. It is a Clojure library where you define parsing rules using EBNF notation, and it produces a parser for that. Despite instaparse being a possible overkill for this task, just being acquainted with it is in itself very useful.

p-himik13:12:00

Since it seems to be an NGINX config, I'd argue that it's best to use an existing parser, e.g. https://github.com/odiszapc/nginx-java-parser In any case, what you're doing is a depth-first traversal. Building a tree out of that is a plain loop with the current tree, the current item, and a peek/`pop` where needed.

👍 1
agorgl13:12:16

I'm mostly doing this as a practice/exercise, so I'm looking which is the most appropriate clojure only way to do this

agorgl13:12:40

And for the context, this is a vyos config section 🙂

oyakushev13:12:11

If it's an exercise then sure, but my argument is that trying out Instaparse as an exercise is very valid too, it is a godsent library.

agorgl13:12:38

my plan is to make a parse function that returns a vec of the parsed object along with the remaining tokens, I'm asking if there is a more idiomatic way

p-himik13:12:34

Or ANTLRv4. :) With clj-antlr. Last time I checked (years ago), Instaparse was significantly slower. But that's not always a concern, of course.

oyakushev13:12:57

You can build a parser combinator from scratch with Clojure, that's also valid. Just follow any of the Haskell guides (or there might be some Clojure guides too, I haven't checked)

p-himik13:12:08

@U03PYN9FG77 Your text stream represent a single document with nested structures. You cannot partially parse it and return some ready-to-use structure, so partial parsing makes little sense IMO.

agorgl13:12:15

I was looking at it as a recursive problem that a function could parse each (nested or leaf) structure by consuming part of the input tokens, no?

p-himik13:12:52

The way you wrote it in the OP makes it sound as if you're going to call parse on the very last } as well. :) Yes, it's a recursive problem. You can solve it recursively, you can solve it in a loop, up to you.

agorgl13:12:30

I suppose I need to study more about parsers in general

p-himik13:12:41

Maybe, but definitely not for this task.

p-himik13:12:08

All that's needed is to walk a flat collection of tokens, open a new vector on {, close on } - that's it, you got your nesting.

agorgl13:12:38

Any direction on what should I use from core?

p-himik13:12:31

As I said - I'd use loop with []/`peek`/`pop`/`conj`/etc. Just start writing it, you'll see how straightforward it is. Maybe not immediately, but in the end - for sure.

agorgl13:12:46

Thanks for the suggestions!

p-himik13:12:42

Of course, all that assumes that your tokenizer is correct and that required nesting can indeed be deduced from paired {}. I have no clue, don't have any plans on studying VyOS config grammar.

jpmonettas23:12:25

@U03PYN9FG77 Another approach that doesn't require a tokenizer is to use a PushbackReader like the Clojure reader does https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/LispReader.java You basically create a mutable stream of chars over your input and start consuming it calling different read functions depending on what you see. You have the same implemented in Clojure as part of the tools.reader project https://github.com/clojure/tools.reader/blob/master/src/main/clojure/clojure/tools/reader.clj

👍 1
igrishaev19:12:02

How can I use case with Java Enums? Imagine I have such code:

(case (.getTxStatus conn)
    TXStatus/IDLE :I
    TXStatus/TRANSACTION :T
    TXStatus/ERROR :E
    nil nil) 
The getTxStatus method returns an instance of the TXStatus enum, namely TXStatus/IDLE. But case says, there is no such a match.

p-himik19:12:48

case matches against compile-time values. So TXStatus/IDLE is a symbol there, not a value from that enum. As for the "how", I'd go with condp + = unless this matching is in a bottleneck.

igrishaev19:12:32

ah, again... thank you for remind me!

p-himik19:12:44

There was a long discussion about it not that long ago: https://clojurians.slack.com/archives/C03S1KBA2/p1693502915802759

igrishaev19:12:30

Or something like this in case if anyone is interested:

(let [-mapping
      {TXStatus/IDLE :I
       TXStatus/TRANSACTION :T
       TXStatus/ERROR :E}]
  (defn status ^Keyword [^Connection conn]
    (get -mapping (.getTxStatus conn))))

cch123:12:43

I’m eagerly awaiting 1.12's first class support for static and instance methods as functions as well as the new syntax for primitives and it made me wonder: will there be support for byte array literals in 1.12? See https://clojureverse.org/t/clojure-byte-array-literal/6434 and https://clojureverse.org/t/tagged-literal-for-clojure-core-vec-not-possible-for-clojure/6452 for background.

Alex Miller (Clojure team)02:12:23

No change there

✔️ 1
Alex Miller (Clojure team)02:12:47

The place to make a request and/or vote is on https://ask.clojure.org

1