Fork me on GitHub
#clojure
<
2022-12-07
>
roklenarcic07:12:26

How can I expand varargs when calling macro within a macro?

(defmacro reg-parse [l bind reg statement & more-clauses]
  `(if-let [~bind (re-find ~reg l)]
     ~statement
     ~(when (seq more-clauses)
        (apply reg-parse l more-clauses))))
So in this case I want macro to emit an iflet for each 3 items given, calling itself recursively

Abhinav08:12:44

You can’t apply reg-parse because it is a macro. Is there any reason reg-parse must be a macro?

roklenarcic08:12:25

(defmacro reg-parse [l bind reg statement & more-clauses]
  `(if-let [~bind (re-find ~reg ~l)]
     ~statement
     ~(when (seq more-clauses)
        (apply list `reg-parse l more-clauses))))

Abhinav08:12:47

(defmacro reg-parse [l bind reg statement & more-clauses]
  `(if-let [~bind (re-find ~reg ~l)]
     ~statement
     ~(when (seq more-clauses)
        `(reg-parse ~l ~@more-clauses)))
the unquote splice ~@ operator was created for this exact problem.

Apple19:12:47

(take 3 (iterate pop [1 2 3]))) I didn't know this is ok. I thought lazy-seq, at least some of them, works 8 or 16 in a chunk.

Alex Miller (Clojure team)19:12:59

it's immutable data - what's the harm in popping more?

Alex Miller (Clojure team)19:12:45

but note that you're losing all the data being popped, so usually this is probably not a very useful thing to be doing

Apple19:12:37

the goal is to get [1 2 3] then [1 2] then [1]

Apple19:12:27

i check source of map/filter they use chunkXXX so iterate although returning lazy-seq but doesn't use chunk

Alex Miller (Clojure team)19:12:08

why would it matter if take was chunked?

Alex Miller (Clojure team)19:12:26

or if iterate produced a chunked seq?

Alex Miller (Clojure team)19:12:10

from a consumer perspective, there is no difference. something might do more work in a chunk but it's unlikely to actually matter in most cases, esp when manipulating immutable collections

Apple20:12:08

user> (iterate pop [1 2 3])
([1Error printing return value (IllegalStateException) at clojure.lang.PersistentVector/pop (PersistentVector.java:470).
Can't pop empty vector

Alex Miller (Clojure team)20:12:42

that's actually the weird thing :) most coll ops have defined behavior for empty/nil

Alex Miller (Clojure team)20:12:39

user=> (take 10 (iterate butlast [1 2 3]))
([1 2 3] (1 2) (1) nil nil nil nil nil nil nil)

Apple20:12:28

public PersistentVector pop(){
	if(cnt == 0)
		throw new IllegalStateException("Can't pop empty vector");

Apple20:12:08

public IPersistentStack pop(){
		if(end - 1 == start)
			{
			return PersistentVector.EMPTY;
			}
		return new SubVector(_meta, v, start, end - 1);
	}

Apple20:12:49

static public Object pop(Object x){
	if(x == null)
		return null;
	return ((IPersistentStack) x).pop();
}

Alex Miller (Clojure team)20:12:04

I mean, you don't need to go that deep even:

user=> (doc pop)
-------------------------
clojure.core/pop
([coll])
  For a list or queue, returns a new list/queue without the first
  item, for a vector, returns a new vector without the last item. If
  the collection is empty, throws an exception.  Note - not the same
  as next/butlast.

👍 1
zakkor21:12:43

How can I create custom reader macros? I know it is not possible by design, and I know what are the reasons for why it's not good to be able to do it, but what if I really wanted to do it?

reefersleep21:12:04

I’ve seen it done, but I forgot how

reefersleep21:12:51

Ah. Well. Not so hard!

delaguardo21:12:53

it is called tagged literal https://clojure.org/reference/reader#tagged_literals and it is not hard at all

hiredman21:12:24

it depends what you mean by a reader macro

hiredman21:12:29

common lisp has reader macros which can let you do things like use the lisp reader to reader json, or embed almost arbitrary syntax in common lisp programs (I think, haven't used them, just seen blog posts)

hiredman21:12:47

clojure doesn't have those

hiredman21:12:04

I believe the state reason it doesn't is because they don't compose well

hiredman21:12:45

clojure has tagged literals, which let you build representations for things to be read out of forms the reader already understands (it is compositional)

hiredman21:12:54

but not as general / powerful

Joshua Suskalo21:12:44

If you aren't satisfied by tagged literals the solution is a fork, right?

hiredman21:12:12

depends on the problem

Joshua Suskalo21:12:55

Well I know I for one was frustrated with the inability to make it so that I could have a #rope literal that allowed evaluated symbols inside it

hiredman21:12:29

that is totally possible

Joshua Suskalo21:12:29

but that's more extending the evaluator than the reader

Joshua Suskalo21:12:48

I'd love to hear how, my tests with it showed it as troublesome

hiredman21:12:15

Clojure 1.11.1
user=> (def f identity)
#'user/f
user=> (set! *data-readers* (assoc *data-readers* 'rope #'f))
{rope #'user/f}
user=> #rope foo
Syntax error compiling at (REPL:0:0).
Unable to resolve symbol: foo in this context
user=> '#rope foo
foo
user=> (defn f [x] (list 'quote x))
#'user/f
user=> #rope foo
foo
user=>

Joshua Suskalo21:12:40

Might be worth it to get into another thread to not clutter this one. I'm not so lucky here with my usecase.

Joshua Suskalo21:12:12

Evaluating symbols in data-readers 🧵

Joshua Suskalo21:12:27

So like you said if you return a symbol it'll get evaluated as normal @U0NCTKEV8

Joshua Suskalo21:12:39

However, if I return a custom data structure it can't recursively evaluate it.

Joshua Suskalo21:12:52

And unfortunately the #=() syntax doesn't evaluate the arguments past the function

Joshua Suskalo21:12:41

Unfortunately I can't do something like

(let [[a b c] "xyz"]
  #rope [a b c])

Joshua Suskalo21:12:57

because it'll return a rope with those symbols in it, not a rope with x y and z in it

hiredman21:12:07

yeah, it isn't really a templating system

Joshua Suskalo21:12:19

Right, but I'd expect to be able to do this with data structures defined inside clojure core, and it's frustrating that I can't make a new data structure that's equally supported

hiredman21:12:26

you can make #rope return code that builds a rope when evaluted, not a rope itself, but that also has issues

Joshua Suskalo21:12:49

that's a thought I guess

hiredman21:12:50

literal records have the same behavior, values aren't evaluated

Joshua Suskalo21:12:32

What's the issue you're referring to with generating code that makes a rope rather than returning one? Stuff with identity?

Joshua Suskalo21:12:42

It looks like it could actually solve my problem.

hiredman21:12:47

if you read without eval

Joshua Suskalo21:12:57

right, it wouldn't be a rope

Joshua Suskalo21:12:20

which is why I was thinking of falling back to the #= but that doesn't evaluate the args either for sensible reasons

Joshua Suskalo21:12:31

So yeah, that all makes sense, it's kinda one or the other if you can't extend the evaluator.

hiredman21:12:03

you can have a #rope-template tag that produces code that creates a rope, and have all ropes still print using the #rope tag

Joshua Suskalo21:12:32

I'll think about it for sure.

Sam Ritchie10:12:03

That’s what I do in #sicmutils for complex numbers, quaternions etc https://github.com/sicmutils/sicmutils/blob/main/src/sicmutils/complex.cljc#L77

diego.videco23:12:09

Got a question about EDN vs Transit. I’ve got an endpoint that is really underperforming by using Transit and I’ve just found that by using EDN it is 10x faster. It is my understanding that Transit should be more performant than EDN so maybe I am doing something wrong. This is the first endpoint that we have that has biggish payloads (ca 1-2mb) so maybe that’s why we hadn’t noticed. Anyways, we are using pedestal (on the server) and reframe (in a react native environment). Any thoughts about this problem?

isak23:12:52

My understanding was transit was only supposed to be faster in the browser

1
isak23:12:24

Since the browser has a native JSON parser, but not a native EDN parser

1
diego.videco23:12:20

Makes sense, thanks @U08JKUHA9

ghadi23:12:10

Hard to say without seeing the serialization code

diego.videco23:12:42

we are using clj-ajax on the react native app

Michael Gardner23:12:55

I have two libraries, lib1 and lib2, and an app that uses both. lib1 contains a simple defrecord:

(ns lib1.core)

(defrecord ARecord ...)
lib2 provides some additional support for this type, by extending a third-party protocol to it:
(ns lib2.core)

(extend-protocol clojure.java.jdbc/ISQLValue
 lib1.core.ARecord
 (sql-value [x] ...))
This compiles fine, but in my app I find that this ISQLValue implementation doesn't work even though I've required lib2.core. What's strange is that if I evaluate that exact extend-protocol in my app's REPL, it works. What could cause this? It's maddening to track down because there are no errors, and it works from a REPL.

hiredman00:12:19

any number of things, usually related to having multiple versions of the record type or the protocol type

hiredman00:12:32

often the result of evaluating the source files multiple times

hiredman00:12:08

also possibly the result of having an aot'ed version on disk and then creating the type again, etc

hiredman00:12:01

given you are using the full time name in the extend call lib1.core.ARecord and don't require lib1.core in lib2.core it is likely the aot issue

hiredman00:12:31

a good rule of thumb is, if you use a type, you must require the namespace that creates that type

Michael Gardner00:12:55

I am actually requiring the type in lib2, sorry for the oversimplified code

hiredman00:12:18

requiring is something you do for namespaces, not something you do for types

hiredman00:12:32

what do you mean by "requiring the type"

Michael Gardner00:12:52

(ns lib2.core (:require [lib1.core :as lib1]))

hiredman00:12:27

another rule of thumb with protocols is when extending a protocol to a type you should own either the protocol or the type

hiredman00:12:49

the ISQLValue protocol basically breaks that rule of thumb by existing

hiredman00:12:03

and the issue that can arise when you break that rule is conflicts

hiredman00:12:28

like you could be loading some later code that defines a different extension of ISQLValue to ARecord

hiredman00:12:48

and those are last one wins

Michael Gardner00:12:15

in this case lib1 is my own library so it's not likely there are other extensions lying around

Michael Gardner00:12:18

agreed that there are issues with the general approach of ISQLValue, but we've been using it without issue on various types like clojure.lang.Keyword for a long time

hiredman00:12:54

you can look in the protocol to see what types it is extended to (see the user.Foo under impls)

user=> (defprotocol X (foo [x]))
X
user=> (defrecord Foo [])
user.Foo
user=> (extend-protocol X Foo (foo [x] x))
nil
user=> X
{:on user.X, :on-interface user.X, :sigs {:foo {:tag nil, :name foo, :arglists ([x]), :doc nil}}, :var #'user/X, :method-map {:foo :foo}, :method-builders {#'user/foo #object[user$eval156$fn__157 0x49aa766b "user$eval156$fn__157@49aa766b"]}, :impls {user.Foo {:foo #object[user$eval191$fn__192 0x963176 "user$eval191$fn__192@963176"]}}}
user=>

hiredman00:12:03

and you can capture the class for your record and extension time, and compare it to the class for the record when the extension seems to have failed to check to see if the record is being redefined

Michael Gardner00:12:24

it does list ARecord , so I guess it must be that the record is getting redefined. What's the remedy for that? defonce?

hiredman00:12:51

figure out why it is being redfined and stop doing that

Michael Gardner00:12:26

I mean there's only one namespace that calls defrecord, so I'm not sure how it's possible

Michael Gardner00:12:41

there's no macro trickery or anything

hiredman00:12:49

something forcing a reloading of the code

hiredman00:12:43

specifically forcing a reload of the defrecord code, but not a reload of extension code (or possible reloading both but in the wrong order)

👀 1
Michael Gardner00:12:11

ah, that gives me something to go on. Thanks, I'll do some more digging