Fork me on GitHub
#clojure
<
2022-10-27
>
onetom06:10:38

What would be the unintended consequences of using ALL_CAPS var names in Clojure and/or ClojureScript? eg.:

(def ASD 123)
=> #'user/ASD
ASD
=> 123
seems to work in JVM Clojure

p-himik06:10:26

Shouldn't be any.

onetom06:10:44

not even in clojurescript? my only worry is that there is a convention of Java requiring that only class names can start with capital letters...

hiredman07:10:40

Java is fine with upper case letters to start class member names, and classes can be all lower case as well

🙏 1
hiredman07:10:01

(all upper case fields are used a lot)

p-himik08:10:21

From the design perspective and without any regard to implementation concerns, would it make sense to make keys, when it receives a map, return something that behaves in the exact same way it does right now with an addition of it being a set?

Alexis Schad08:10:28

Not really, most of the time you don’t need the result to be a set, so as a core feature I would not except to assume it’s the case. If you want it as a set you could just call set over it and that’s it. Composability.

walterl13:10:45

Interesting question, and got me investigating a bit. I agree with Alexis. KeySeqs are more streamlined for its purpose. If you need set semantics, you can add it when necessary. Even though much of the semantics overlap, there are some important differences. For one thing KeySeqs are seqs, while sets aren't:

(clojure.set/difference (supers clojure.lang.APersistentMap$KeySeq) (supers clojure.lang.PersistentHashSet))
; #{java.util.List
;   clojure.lang.Obj
;   clojure.lang.Sequential
;   clojure.lang.ISeq
;   clojure.lang.ASeq}
Sets have a bunch of extra bells and whistles that KeySeqs don't have, though:
(clojure.set/difference (supers clojure.lang.PersistentHashSet) (supers clojure.lang.APersistentMap$KeySeq))
; #{clojure.lang.AFn
;   java.lang.Runnable
;   java.util.Set
;   clojure.lang.APersistentSet
;   clojure.lang.IPersistentSet
;   java.util.concurrent.Callable
;   clojure.lang.Counted
;   clojure.lang.IFn
;   clojure.lang.IEditableCollection}
Interestingly, calling seq on a set returns a KeySeq: (type (seq #{1})) ; clojure.lang.APersistentMap$KeySeq

timothypratley21:10:59

I wish the result of keys was a set if we could disregard the implementation concerns... based on little more than the observation that I typically always end up writing (set (keys m)) to make use of them, and conversely them being a seq offers no surface benefit AFAIK (of course if we care about implementation, perhaps it would be desirable to have both keyseq and keyset /shrug

timothypratley21:10:01

Being able to get a set of keys fits into the basic properties of data structures that is desirable, and slightly opens the door to implementing a way to have maps deliver sets of keys efficiently (rather than building a completely new set every time).

timothypratley21:10:01

To quantify 'I typically always' it seems from searching my code it's more like 50% 😆 sorry for exaggerating.

Jakub Holý (HolyJak)09:10:04

Hello! Is there some good place where I can learn how the clojure Compiler.java works and why it does what it does?

1
reborg10:10:30

Have a look at this old presentation of mine, it gives an initial overview of the main parts and how they interact https://github.com/reborg/euroclojure-2014/blob/master/clojure-beasts-euroclj-2014.pdf

♥️ 2
1
Noah Bogart13:10:14

Oh that's tight, thanks for the presentation!

timothypratley23:10:14

Looks like your talk was also uploaded to Vimeo here: https://vimeo.com/100518965 🙂

🙏 1
reborg10:11:29

Right, I didn't post it because it forces you to open the slides regardless (no idea why it wasn't merged at the time). But if you feel you want the live talk feel, then by all mean open both and scroll along!

reborg10:11:54

(Remembering the cold sweats, Rich was in the first row :)

🙀 2
😎 1
Jakub Holý (HolyJak)08:11:09

BTW I love the first slide. Sufficiently scary 🙂

Jordan Robinson13:10:18

Hey all, I'm struggling to write a macro that I feel should be pretty simple but I feel like I'm missing something obvious and can't think what the terms to search for would be. More details in thread so I don't clog up the main channel

Jordan Robinson13:10:28

I have a working macro that looks like this:

(defmacro merge-alt!!  
  [& channels]  
  `(loop [i# 0  
          out# {}]  
     (if (= i# (count (vector ~@channels)))  
       out#  
       (recur  
         (inc i#)  
         (async/alt!!  
           [~@channels] ([response#] (merge out# response#))  
  
           [(async/timeout 5000)] (println "timed out"))))))
Which when called like this, works (merge-alt!! channel-1 channel-2) But I need to be able to call it like this: (def channels [channel-1 channel-2]) (merge-alt!! channels) And I'm not sure how to alter the original macro to be able to do this (apart from changing the args), does anyone have any advice or tips for what to search for? Thanks in advance!

gordon13:10:32

Silly question, is there a reason for this not to be plain function that you could then (apply merge-alt!! channels)?

Jordan Robinson13:10:37

not a silly question at all, and I did think of that first of all, but the problem is the bit where it calls async/alt!! which needs to expand out to be [channel-1 channel-2] etc

Jordan Robinson13:10:19

that's kind of the reason I went down the macro route, that alt!! call functions a lot like cond

Jordan Robinson13:10:28

I don't know if I'm explaining this very well so please let me know if I'm not

Jordan Robinson13:10:58

if I could do

(def channels [channel-1 channel-2])
(alt!! channels [:foo "bar"]
       [c3] [:c3 "baz"])
with https://clojuredocs.org/clojure.core.async/alt!! then I basically wouldn't need the macro, but I couldn't find a way of doing that either

Ed13:10:25

The spicing unquote ~@ requires that channels is know at macro expansion time, and you're passing in a symbol and not trying to resolve it into an actual value. What you've written will work for hard coded channels, but not for a variable number of channels. Do you need the list of channels to be variable, or are you trying to reference a constant value? If (def channels ...) doesn't change then you can dereference that symbol outside the macro expansion and use the list of channels found there?

Jordan Robinson13:10:27

that makes a lot of sense now you've said it, sadly it's a variable number

Jordan Robinson13:10:58

I guess if it needs it at compile time that means I kind of can't do it though right? 😅

Ed13:10:40

it looks like alt!! wants to have a concrete value at compile macro expansion time

Ed13:10:48

once you're in macro land you often get stuck in macro land ... what's the actual problem that you're trying to solve? why do you need to pass in a variable number of channels?

Jordan Robinson13:10:41

I think I can probably get round the initial problem by just duplicating a good chunk of code, but essentially we have a function that does fan-in style async network requests, similar to the macro above

Jordan Robinson13:10:52

but this varies on what those network requests are depending on earlier logic

Jordan Robinson13:10:35

the structure of the map etc always ends up roughly the same, but at the minute we basically have the same 40-50 line function copied and pasted 3-4 times that does very slightly different network requests

Jordan Robinson13:10:57

so this was my, perhaps foolish, attempt at genericizing it a bit to reduce the duplication

Ed14:10:31

I guess you could write a macro that does the copy-pasta bit? ... but I think it's difficult to know what to advise without seeing the code 😉

Jordan Robinson14:10:48

yeah that's totally fair, I think with the above macro I can get the duplication down from 40-50 lines to about 10 at least

Jordan Robinson14:10:57

sorry I can't share more but it's super domain specific 😅

Jordan Robinson14:10:01

I really appreciate the help btw

Ed14:10:22

to be clear, you can write something like this:

(def things [:a :b :c])

(defmacro some-things [t]
  (let [ts @(resolve t)]
    `(for [x ~ts]
       x)))

(comment

  (macroexpand-1 '(some-things things)) ; => (clojure.core/for [macro/x [:a :b :c]] macro/x)

  )
but if you change things by re def ing it you'll also have to re-expand the macro

Ed14:10:51

but your macro would work for (some-things [:chan1 :chan2])

Ed14:10:02

just not putting the value in a var

Ed14:10:12

> sorry I can't share more but it's super domain specific oh yeah ... totally understand 😉

Jordan Robinson14:10:09

yeah, that makes perfect sense - I can probably get away with e.g.

(if
 (= 3 (count foo))
 (merge-alt!! channel-1 channel-2 channel-3)
 (merge-alt!! channel-1 channel-2))

Jordan Robinson14:10:18

which is still a good hundred lines less than what was there before

Jordan Robinson14:10:36

thanks again for all your help, I feel like I understand macros a lot better now 🙂

👍 1
Ed14:10:11

the takeaway lesson is that macros are responsible for evaluating their arguments, which is great because you get to control how those args get evaluated, but also means that you have to evaluate the arguments yourself. With great power ..... 😉

👍 1
hiredman16:10:07

use alts!! not a macro

hiredman16:10:52

(let [[the-val the-chan] (alts!! (list* (timeout ...) the-chans))] (if (some #{the-chan} the-chans) (merge out the-val) (println "timed out")))

Jordan Robinson16:10:13

it's my understanding that alts!! is different to alt!! though right?

Jordan Robinson16:10:21

as in different behaviour in an asynchronous context

hiredman16:10:21

there are four different alts

hiredman16:10:34

alts!! alt!! alts! alt!

Jordan Robinson16:10:56

yeah that's what I thought, with alt!!/alts!! using <!! and >!!, and alt/alts! using <! and >!

Jordan Robinson16:10:51

so I might be a little confused but is the difference just that alts supports a variable number of channels in this way, but I'd have to have some custom logic on the timeout instead?

Jordan Robinson16:10:23

I hadn't really read up on that one till now so forgive my ignorance

hiredman17:10:28

alts!!(and alts!) takes the set of operations as a collection and returns the channel and value that where chosen

hiredman17:10:44

it is more primitive than alt!! and alt!, you can build alt!! on alts!!, but not the other way around

hiredman17:10:05

I forget if alt!! is actually built on alts!!, but it could be

Jordan Robinson07:10:13

that's super interesting, sorry for the late reply, timezones; in any case that's probably what I need, I'll do a bit of investigation but I didn't even think of changing the async function until now, thanks a bunch 👍

Dallas Surewood17:10:36

Selmer: Anyway to render arbitrary strings of html in the templating engine? where header = <h1>This is a header</h1>, render with

<div>
    {{ header }}
</div>

walterl17:10:01

Maybe selmer.util/without-escaping can help

Dallas Surewood17:10:27

Do those filters work with render-file?

Dallas Surewood18:10:41

Looks like yes! Thank you so much

seancorfield18:10:44

{{header|safe}} or set header to [:safe "<h1>This is a header</h1>"]

seancorfield18:10:18

(we use this a lot at work -- we generate hundreds of thousands of HTML emails every day with Selmer 🙂 )

Joshua Suskalo19:10:57

is there a reason that apply doesn't work with nullary functions? Like is there a reason (apply f) shouldn't work? It seems really useful to me if I want to do like (run! apply some-fns)

Joshua Suskalo19:10:40

I am well aware that it doesn't work and that it's not likely to change, I was just curious what the thinking was here.

Annaia Danvers19:10:44

I'm not 100% sure how the Clojure compiler handles it but in general this is common to other lists. At least in Racket, apply is used internally as well, it is the actual function that applies functions to arguments and so it must get some kind of argument list as there's no definition of it that makes sense otherwise.

Annaia Danvers19:10:52

but, an empty list is still a list.

Annaia Danvers19:10:16

so you can do (apply f []) and it will work.

Joshua Suskalo19:10:19

the actual usecase here is as a higher order function to run! or similar, so passing it extra args is useless here as I'd just pass #(%) instead

👆 1
didibus00:10:06

I don't understand what are you applying? Oh, I guess you're thinking you want a function that invokes a function?

reefersleep07:10:51

I don’t understand why it doesn’t work, so I can’t answer your question, but what about using

(run! (partial apply) some-fns)
?

reefersleep07:10:07

Maybe I’m misunderstanding.

Joshua Suskalo14:10:30

The problem is that apply requires at least one argument be passed to the function.

Annaia Danvers14:10:20

Yes. Apply has minimum arity of 2 args. The function, and a list to apply it to (or a first argument)

Annaia Danvers14:10:01

And because the function is in the first argument position, you can't do something like (partial apply [])

Annaia Danvers14:10:43

This should work though (but will make everyone cry): (run! (comp eval list) fns)

Joshua Suskalo14:10:39

the fns are function objects, not symbols, so #(%) is the way to go

reefersleep14:10:01

I see what you're aiming at.

Annaia Danvers14:10:28

yeah. I'm surprised there isn't some form of identity function on functions. I think Haskell has one.

Annaia Danvers14:10:01

(def idfn [f] (f))

didibus15:10:09

Ya, there's .invoke but there's no Clojure exposed one

tomd20:10:39

is there a trick to make this return :found, or are classes not available to case at compile time?

(case (class 4)
  java.lang.Long :found
  :not-found)
;; => :not-found

hiredman20:10:56

at compile time java.lang.Long is a symbol

hiredman20:10:06

user=> (type (read-string "java.lang.Long")) clojure.lang.Symbol user=>

tomd21:10:58

Ah I see. So this hack works:

(case (str (class 4))
  "class java.lang.Long" :found
  :not-found)
;; => :found
but maybe once you're constructing strings at runtime you're not getting much benefit over cond ?

hiredman21:10:35

if you write your own macro you can have it embed literal classes, but I would just use cond

🙏 1
Joshua Suskalo21:10:23

you could use condp with isa? or instance? as well

Joshua Suskalo21:10:52

the last major way to consider here as well would be to just have a map

(def classes #{java.lang.Long})
(def classes-map (zipmap classes (repeat :found)))

(classes-map (class 4) :not-found)
;; => :found

Joshua Suskalo21:10:08

if you have a lot of condp clauses that are equally likely the map will be faster

nice 1