Fork me on GitHub
#clojure
<
2023-03-10
>
Ian Fernandez14:03:27

Is there any .cljc implementation of hiccup?

jrychter15:03:59

Depending on what it is you need to do, there is also daiquiri (a fork of sablono) in rum. It's not strictly cljc, but it's a tool for server and client-side rendering of hiccup data structures.

Noah Bogart16:03:07

If I only read strings, do I need the "pushback buffer" capabilities of a PushbackReader? Or can I just move my cursor back and forth along the string?

Noah Bogart16:03:33

Seems to me like the unread capabilities are purely to handle read-only input streams, and that typically unread is going to be called with whatever character was just read in

lread16:03:44

Are you weighing using a stream vs working with a string?

Noah Bogart16:03:04

for simplicity, no. I'm slurping a file and working on the whole thing

andy.fingerhut19:03:49

If you are reading an entire file's contents into a string, then PushbackReader seems unlikely to be of any use to you.

👍 2
andy.fingerhut19:03:04

It tends to be used and/or required in situations where some code is reading the file that is parsing it for some syntaxes where the parser uses "lookahead" techniques, e.g. Clojure's EDN reader and Lisp reader code, probably some JSON parsers, etc.

👍 2
borkdude16:03:11

I noticed that async/timeout channels are re-used if they are in a 10ms difference from each other. This code:

(require '[clojure.core.async :as async])

(def ^java.util.concurrent.Executor virtual-executor
  (java.util.concurrent.Executors/newVirtualThreadPerTaskExecutor))

(def n 200000)

(let [chans (atom #{})
      begin (System/currentTimeMillis)
      state (atom 0)]
  (dotimes [_ n]
    (.execute virtual-executor
              (fn []
                (let [chan (async/timeout 1)]
                  (swap! chans conj chan)
                  (async/<!! chan))
                (swap! state inc))))
  (while (< @state n))
  (println "Spawned" @state "threads and finished in" (- (System/currentTimeMillis) begin) "ms")
  (prn (count @chans)))
for varying n , in JDK19, launched with clj -J--enable-preview -M /tmp/timeout.clj, will either show that @chans is a very low number, much lower than n, or it will error with:
java.lang.AssertionError: Assert failed: No more than 1024 pending takes are allowed on a single channel.

borkdude16:03:31

My question: why the buffering of timeout channels and could this be considered a bug in core.async, or intentional?

borkdude16:03:49

And are there more places in core.async where channels are re-used?

hiredman16:03:59

One of the patches on https://clojure.atlassian.net/browse/ASYNC-234 adds an alternative timer implementation that gets you a unique readport every time you call timeout, and also stops timers from being able to hold references beyond when they are actually being waited for

borkdude16:03:41

For virtual threads I think it can be as simple as:

(defn timeout [ms]
  (if virtual-executor
    (let [chan (async/chan nil)]
      (.submit virtual-executor (fn []
                                  (Thread/sleep ms)
                                  (async/close! chan)))
      chan)
    (async/timeout ms)))

borkdude16:03:12

Which patch contains the improved timeout?

hiredman17:03:18

the non-sharing wasn't the primary goal, the primary goal was to avoid leaking in active handlers, so without that change the new timers aren't usable as is

hiredman17:03:10

and rich doesn't care for those patches, so they will never get merged

😭 2
Jakub Šťastný16:03:15

Is there an easy way to run exec syscall? I don't see it listed in https://clojuredocs.org/clojure.java.shell If not, no problem, I can just use sh, but exec would be preferable.

Jakub Šťastný16:03:56

As in "replace current process (JVM) with the new process". I want some preprocessing and then launching a command which should replace the JVM process, so then it automatically exits with exit status of the new process and I don't have to deal with possible exceptions etc.

Jakub Šťastný17:03:41

... not to mention with clojure.java.shell/sh I end up with :err and :out which is annoying and showing one and then the other might not represent how messages went in time (as there might be out then err then out).

delaguardo17:03:43

i don't understand. sh will block until subprocess excited with some exit code. output and error then will have everything that subprocess send to stdout and stderr.

hiredman17:03:37

no, the jvm doesn't provide a way to run exec

👍 2
Jakub Šťastný17:03:52

@U0NCTKEV8 OK that's fair enough, good to know.

Jakub Šťastný17:03:04

Sucks but whatever 🙈

hiredman17:03:13

the jvm is a fairly complicated process, execing in it is not a great idea

hiredman17:03:59

exec is a very posix thing, and the jvm is intended to be able to run in many different environments

Jakub Šťastný17:03:12

Yeah I get that. I don't ever run out of POSIX though, so ... not a feature for me.

lread17:03:42

There is some support in GraalVM native image and babashka process does expose https://github.com/babashka/process/blob/master/API.md#babashka.process/exec. If you are using babashka, it is available.

Jakub Šťastný17:03:26

Thanks @UE21H2HHD. It's a project I'm porting from Babashka to Clojure, so I can build it as one thing with everything (JAR in this case), so on the CI I don't have to install both Java and Babashka....and I already need Java/Clojure for ClojureScript, so the one who's out is Babashka, despite the fact that I really like it a lot.

Jakub Šťastný17:03:57

In the future I want to look into native compilations with GraalVM, but for now Clojure/Java is the past of least resistance.

delaguardo17:03:45

what's wrong with babashka + clojure combo? most of ci platforms can cache dependencies like jvm

Jakub Šťastný17:03:09

I just don't want more dependencies, that's all.

Jakub Šťastný17:03:43

OK for dev, not keen on too much things going on out of dev env.

delaguardo17:03:27

right, because you mention ci i assumed this is to build something

Jakub Šťastný17:03:54

It's a task manager, think of babashka's tasks in bb.edn, but this takes it from literate programming files (org-mode). So instead of having tasks defined in one language like CLJ, you can just have:

#+name: install
#+begin_src sh
  apt-get install ...
#+end_src
In your http://README.org or any .org file and read it directly with et install. That's the idea. And since it's a task runner, I want it to be as much standalone as possible. GraalVM binary would be best for the future.

Jakub Šťastný17:03:35

I have a project that's completely done as a literate programming and this allows me to run all the tasks that are defined/documented in the org files where the project lives.

clyfe17:03:43

Can I get some help writing a macro kinda like this, but working? It's a sort of chicken and egg issue.

(defmacro defm [name syms]
  `(def ~name
     (reify Module
       (exec [vals#]
             (for [i# (range 0 (count ~syms))
                   :let [~(get ~syms i#) (get vals# i#)]]
               ~@body)))))

(defm foo [a b] (+ a b))
(exec foo 1 2) ; => 3

hiredman17:03:39

hard to tell what the intent of the macro is, but should almost certainly just be a function

hiredman17:03:30

1. adding new def forms is gross

hiredman17:03:07

2. you need macros to create bindings and create sort of new control flow forms

hiredman17:03:24

it is hard to tell from the above, but maybe this needs to be a macro for creating new bindings, but I am not sure

hiredman17:03:46

and of course it violates #1

clyfe17:03:02

gist: a module encapsulates some computation and some data input signature; later we execute the module computation with some data that matches the signature;

hiredman17:03:21

sounds like a function?

hiredman17:03:55

in which case all you needs is to wrap a function in a module, which you can do with another function

clyfe17:03:35

let me consider

hiredman17:03:03

(fn [f] (reify Module (exec [_ vals] (apply f vals))))

hiredman17:03:15

the problem with people writing macros that are new def forms is people always conflate creating and X and binding X to a global top level name

hiredman17:03:18

and that sucks, imagine if the only way to create a function was defn, sure defn gets used a lot, but how annoying would it be if clojure provided defn and not also def and fn

clyfe17:03:55

Don;t think i can use a fn

clyfe17:03:16

1. I need a macro that returns a Module

clyfe17:03:32

2. The module has spatial input and temporal input

hiredman17:03:35

you are asserting you need a macro right there

hiredman17:03:58

what is the feature of macros that you require

clyfe17:03:55

A Module is like a function that also has temporal input

clyfe17:03:07

normal function only has spatial input

hiredman17:03:15

what does that mean?

clyfe17:03:23

long story

hiredman17:03:55

is it an input to a function?

clyfe17:03:56

gist is temporal input or output is like core.async channels

hiredman17:03:06

but passed as an argument

clyfe17:03:31

temporal input is read from a channel (depending on runtime)

hiredman17:03:25

like, macros don't have "temporal input" as a feature, so you would be using macros to building such a thing, so you must have some idea how those are represented in regular clojure code first

hiredman17:03:56

so my question is what does that look like in regular clojure code

clyfe17:03:12

I want to implement a macro like "defn" with a secondary input and output (temporal)

clyfe17:03:34

(ag/defm sum [a b] [c d]
  [[(+ a c) (+ a d)]
   [(+ b c) (+ b d)]])

hiredman17:03:38

(the reason you must know that is because macros just expand into clojure code, so you have to know what your feature looks like in clojure code first)

hiredman17:03:17

and if you had the macro and it existed, what would the expansion for that example input be?

clyfe17:03:23

(defm sum [a] [b]
  (0 (+ a b)))

(sum 1 (ch 2))

(reify Module
  (exec [[1] (ch 2)]
        (let [a 1
              b (<!! ch)]
          (+ a b))))

hiredman17:03:50

that isn't a valid reify

clyfe17:03:48

I wrote it fast as an example

hiredman17:03:31

sure, but if it isn't valid clojure I can't tell what you are doing

clyfe17:03:57

It's the "let" bit that hassles me

clyfe17:03:09

Ok give me a few mins I'll give a pristine example

hiredman17:03:45

anyway, once you know what the input looks lie (what it looks like to call your macro) and what the output looks like (what the clojure code that actually runs looks like) you just need to write a function that is a mechanical translation between the first data structure and the second

clyfe18:03:02

ty, sorry me slow now, im on meds

clyfe18:03:07

;; A module has a spatial in and temporal in
;; and can execute
;; and can return a spatial out and temporal out in a pair vector
(defprotocol Module
  (exec [sin tin]))

;; A module with 2 slot spatial input (a, b)
;; and 2 slot temporal input (c, d)
;; returns 0 in spatial output 
;; and speaks the 2 sums in the temporal out
(defm sum [a b] [c d]
  [[0] ;; spatial out
   [(+ a c) (+ b d)]]) ;; temporal out

(def c (a/channel)) ;; a chanel for temporal input
(def d (a/channel)) ;; another chanel for temporal input
(exec my-sum [1 2] [c d])
(a/>!! c 3)
(a/>!! d 4)

;; defm above should expand to
(reify Module
  (exec [[a b] [c d]]
        (let [c' (<!! c)
              d' (<!! d)]
          [(+ a c') ;; spatial out
           (+ b d')]))) ;; temporal out

clyfe18:03:13

^pristine

hiredman18:03:43

(reify Module
  (exec [[a b] [c' d']]
        (let [c (<!! c')
              d (<!! d')]
          [(+ a c) 
           (+ b d)]))) 

hiredman18:03:07

(it matters where you put the new names because you don't want to traverse through the body and rewrite the names)

hiredman18:03:46

and that isn't actually pristine because you are missing the this argument for the reify

hiredman18:03:57

for the exec in the reify

hiredman18:03:10

in the protocol definition too

hiredman18:03:13

a macro is a translation from A to B, it is easier to write the translation if you have B around, complete, and working already

2
2
hiredman18:03:22

https://gist.github.com/hiredman/1179073 might be a useful reference, it is doing some manipulation of bindings, in the bodies of functions some names are bound to delays instead of the actual values so those need to be forced and rebound

Jakub Šťastný20:03:31

OK, follow-up question: some of the commands I have are interactive, like running a REPL etc. I can't use https://clojuredocs.org/clojure.java.shell for this. (Another issue that would be avoided if there would be the bloody syscall exec ...). How do I do that, running an interactive command from CLJ?

Jakub Šťastný20:03:42

clojure.java.shell/sh exits immediately. For instance when running a Deno REPL:

(sh "deno" "repl")
{:exit 0, :out "Deno 1.31.1\nexit using ctrl+d, ctrl+c, or close()\n", :err ""}

Jakub Šťastný20:03:50

Does ProcessBuilder work with interactive commands? Before I start digging into it, I know fuck all about Java.

phronmophobic20:03:11

you probably need to pass in a stdin

Jakub Šťastný20:03:24

Well I don't know what I'm going to type to the REPL yet!

Jakub Šťastný20:03:37

It's to run an interactive command.

emccue20:03:33

right, but you can forward your process's stdin

emccue20:03:03

(-> (ProcessBuilder. ["node"])
    (.inheritIO)
    (.start)
    (.waitFor))

emccue20:03:19

this will be interactive @U024VBA4FD5

enn20:03:50

specifically what interactive command are you trying to run? there are ways with ProcessBuilder to make this mostly work with simple readline-based interactivity. I can dig up an example in a few. I have never been successful in getting this to work for applications that expect full TTY control (e.g., console Emacs).

Jakub Šťastný20:03:30

@U060QM7AA deno repl which is Deno (JS) REPL.

enn20:03:58

ah then emccue’s suggestion will probably do the trick

borkdude21:03:55

This https://clojurians.slack.com/archives/C03S1KBA2/p1678481223174479?thread_ts=1678478431.608579&amp;cid=C03S1KBA2 or:

(require '[babashka.process :refer [shell]])
(shell "node")
Also checks the exit code

jeaye22:03:52

What is the purpose of IPersistentMap.assocEx? I see that it first checks that the key is not present and asserts if it is. But I don't see it actually being used anywhere in the Clojure code; it's just being defined for the different map types.

p-himik23:03:09

It was used up to 2007 by PersistentHashtreeMap, but then that class got removed. It's just guessing on my part, but the method was probably forgotten till later, when it was too late to remove it. Or maybe something outside of org.clojure/clojure was using it even back then.

jeaye23:03:13

Perfect. I appreciate the insight!

borkdude23:03:13

They seem mostly just implementations for completeness

jeaye23:03:41

Ohh, never seen http://grep.app before. Thanks.

jeaye23:03:18

Looks like something I can avoid for jank. Happy to find things I don't need to implement. 🙂

borkdude23:03:03

yeah, http://grep.app is very useful for research like this

borkdude23:03:25

another thing I use a lot for "who is doing what in clojure and how often" is this: https://github.com/borkdude/grasp

👍 2