Fork me on GitHub
#beginners
<
2019-06-20
>
mzavarella00:06:34

Anyone happen to know where is the leiningen profile.clj located for Windows users?

gilmar01:06:27

Hello @U0DHQ67U5 I think project.clj stay C:\Users\{username}\.lein or C:\Documents and Settings\{username}\.lein, sorry for my horrible english

mzavarella01:06:13

Plenty readable by me! Thanks for the response 🙂

gilmar01:06:04

you're welcome 🙂

Mitch01:06:38

General rules on using extend-type vs extend-protocol? I am anticipating extending several types and that the bodies of the extensions will be pretty meaty. Should I use an extend-protocol and call separate functions in each implementation body or just use several extend-types? Sorry if my wording isn't clear

seancorfield03:06:18

@mitchell_clojure Hard to say without more details... what problem are you trying to solve here?

Mitch03:06:47

Hey Sean, so I am extending a POI class (XMLSlideShow) to implement datafiable. However, since it is an object that is composed of many other objects (and those are composed of other objects in turn), I am finding that I will need to implement the protocol for a large number of class

seancorfield03:06:02

extend-protocol expands into calls to extend-type so it's mostly going to be about readability I guess...

Mitch03:06:05

yeah, working through it myself, it seems like there are classes that it is natural to group together, so I am putting those in extend-protocol blocks

seancorfield03:06:17

OK, you have just one protocol and lots of classes to extend it to to... yes?

seancorfield03:06:50

extend-type sounds slightly more appropriate here then I think.

seancorfield03:06:33

(because you have large implementations)

seancorfield03:06:12

If your implementations are small, extend-protocol would make more sense (one protocol, lots of types).

seancorfield03:06:33

extend-type is normally one type, several protocols.

Mitch03:06:26

yeah, it be kind of redundant to use extend-protocol just to call a functions that have the implementation elsewhere, when I can just group in extend-type blocks

seancorfield03:06:15

Will this code be published as open source? I'd love to see it, when it's done.

seancorfield03:06:41

And you'll be implementing Navigable too for some things?

Mitch03:06:19

yeah, definitely. It's my first project and I want to make sure I am hitting all of the right marks (test coverage, thorough documentation, extendability, idiomatic design) and I am working on this after my day job so it might be a bit before we have something working

Mitch03:06:09

but the promise once it is on github will be to stay current with the development of POI on the java side, as their API isn't stable yet

Mitch03:06:18

As for Navigable, that will probably be a consideration further down the line. First objective is to represent slideshows as data, as I have a use-case at my current job for slideshow generation from a template

Mitch03:06:08

I'll let you know once significant progress is made. Thanks for all the help so far! You are the man!

seancorfield03:06:18

@mitchell_clojure Sounds like it'll work great with REBL 🙂

seancorfield03:06:07

(speaking of which, I spent a bit of time yesterday and today getting REBL and Liquid to play nice together -- Liquid is a very interesting Clojure project: an in-process, vim-like editor that runs inside your project... because I love REBL so much!)

Nate Sutton03:06:59

my first real macro I wrote (that I'm going to use):

(defmacro condj
  "Conditionally conj a value with a collection."
  [test coll x]
  `(if ~test
     (conj ~coll ~x)
     ~coll))

Nate Sutton04:06:15

I guess it's not necessarily on the end

seancorfield04:06:47

(cond-> coll test (conj x))

Nate Sutton04:06:44

yeeeeaaaahhhh

Nate Sutton04:06:50

a friend talked me out of that macro too

Nate Sutton04:06:53

with the same code

seancorfield04:06:07

We're here to help :rolling_on_the_floor_laughing:

Nate Sutton05:06:09

condj is still a cute name for the operation :)

Daouda05:06:01

hey Folks, due to my poor english, I’m facing some difficulties to get right the meaning of this paragraph:

Note that recur is the only non-stack-consuming looping construct in Clojure. There is no tail-call optimization and the use of self-calls for looping of unknown bounds is discouraged. recur is functional and its use in tail-position is verified by the compiler.
` What does this mean exactly? recur is the only non-stack-consuming looping construct in Clojure. There is no tail-call optimization. ? Which loop constructs in clojure are stack-consuming? stack-consuming is that a bad or good thing? references: https://clojure.org/reference/special_forms#recur

seancorfield05:06:22

@quieterkali a simple recursive call of the function would be stack-consuming.

andy.fingerhut05:06:23

Recursion can be used to do loops, and recursion in Clojure causes the stack to grow

andy.fingerhut05:06:51

If you use recursion in a way where processing each element of a sequence does 1 recursive call deeper, and call that function with a sequence containing 100,000 elements, then the stack will grow by 100,000 stack frames before you are finished, or more likely with the default settings for maximum stack depth in most JVMs, you will get an exception.

andy.fingerhut05:06:16

Here is a simple example of writing a recursive function like that in Clojure. It only adds up a collection of numbers, but the point is that it does so with a recursive function call on the rest of the sequence:

user=> (defn total [coll]
  (if (seq coll)
    (+ (first coll) (total (rest coll)))
    0))
#'user/total
user=> (total [1 2 3 4])
10
user=> (total (range 1000))
499500
user=> (total (range 10000))
Execution error (StackOverflowError) at user/total (REPL:3).
null

👍 1
Daouda05:06:23

Thank you very much for the example and explanation guys. So the only way to avoid stack consuming in clojure is by using recur function. Am I right? and All others recursive approach are stack-consuming.

didibus05:06:12

@quieterkali Yes, except for Trampoline

andy.fingerhut05:06:18

There is another approach that is not often used, called a trampoline

Daouda05:06:13

Any particular reason for not being used as often?

andy.fingerhut05:06:49

Most built-in Clojure sequence processing functions are implemented internally in a way that is not stack-consuming, so loop/recur is not used as often as you might guess, either. But more than trampolines.

andy.fingerhut05:06:09

There simply aren't as many use cases that tend to come up where a trampoline is useful.

didibus05:06:42

trampoline is more challenging to use, and has some quirks you need to be aware off, and people rarely have use cases that the simpler recur can't cover

didibus05:06:02

Its even hard for me to come up with an example right now which wouldn't work with recur, and you'd need trampoline for

Daouda05:06:11

ok, I got it. those explanation helped a lot. Thank you very much guys 🙂

alexmiller09:06:27

In 10 years of professional Clojure use, I’ve used trampoline exactly once.

Crispin06:06:17

mutually recursive functions need trampolining instead of loop/recur. That is where a calls b calls a calls b and so on... can implement finite state machines with this approach.

Ahmed Hassan07:06:48

Is there a way to require npm module in .clj file to define macros using it, So, I can require those macros and use them in .cljs file.

Ahmed Hassan07:06:22

I want to make a wrapper for antd-mobile similar to antizer.

Ahmed Hassan07:06:08

I'm using shadow-cljs as build tool.

Ahmed Hassan07:06:56

It is giving errors. What am I missing here?

Ahmed Hassan07:06:06

Antizer uses (def antd-module 'js/antd) at the top of antizer/macros.clj namespace and uses antd-module variable everywhere, in the places where I'm using antd-mobile.

orestis08:06:15

You can’t use the string require in a .clj file. And you don’t actually need to, since the macro will generate just code which is symbols etc. So you just need to make sure you use the correct symbols in your macro, and it is the job of the consumer to make sure that antd-mobile is required.

orestis08:06:54

An example use of the macro and how you expect to use it would be helpful, it’s hard reading macro code as it is, and macro code that goes from CLJ->CLJS is a little bit harder 🙂

Ahmed Hassan09:06:18

These macros would be required by another namespace antmob/reagent.cljs and called like (export-reagent-components).

Crispin08:06:39

best practice with cljs and macros now is not to use a macros namespace, but put them in a standard .clj namespace. Then (:require-macros ...) that namespace in the same namespace as a .cljs file. Once you do this you can just import the .cljs namespace with (:require ...) and you will get the macro to use, no need to individually require-macros

Crispin08:06:05

if you don't do this, and your expanded macros contains cljs side calls to cljs function in the same namespace, then the user of you macro will also have to know to :require those other namespaces for the expanded macro call to work

Ahmed Hassan09:06:39

I understand what you said above. But not this one.

Crispin09:06:21

say i have a seperate macros.clj file that contains a macro (defmacro foo [&args] `(bar ~args))

Crispin09:06:01

and a client (:require-macro [my.macros])

Crispin09:06:19

then in that namespace uses the macro (foo 1 2 3)

Crispin09:06:34

they will get a very strange and difficult to diagnose error

Crispin09:06:09

about something undefined. I forget the error. But it's a little obtuse in the reporting wording. anyway....

Crispin09:06:34

the thing is the client also needs to (:require [my.macros])

Crispin09:06:37

ie. (ns baz (:require [my.macros]) (:require-macros [my.macros]))

Crispin09:06:00

that brings in client side the bar function you wanted to call

Crispin09:06:17

a similar error occus for all functions in that macro expansion

Crispin09:06:02

if your macro expands to say (some.other.namespace/somefunc ...) then the client will also need to remember to (:require ...) that namespace

Crispin09:06:36

there will be all these "a list of namespaces I need to require to get the macros functioning fully"

Crispin09:06:56

by :require-macro'ing the clj namespace from the same cljs namespace, you get all those cljs requires specified bought in by the client when they :require your new namespace

Crispin09:06:59

so for example in your code, the client using the export-reagent-components macro needs to also :require reagent.core and (possibly) goog.object namespaces. Or the macro won't work. They might already have them :required, so not notice it. But they need to know about it or the error will be utterly confusing. goog.object? I didn't use any goog.object? where is this coming from? (its coming from inside the expanded macro)

Crispin09:06:29

so in your case your namespace would be antmob.component perhaps

Crispin09:06:17

and in the component.cljs file, you require-macros these macros. and you also (:require [goog.object] [reagent.core])

Crispin09:06:23

then anyone wanting to use the macros or any func inside components.cljs, just (:require [antmob.component :refer [export-reagent-components some-other-cljs-func]])

Ahmed Hassan11:06:58

I understand most of what you are saying. I think you are talking about Two File ClojureScript Namespace Pattern: https://blog.fikesfarm.com/posts/2018-08-12-two-file-clojurescript-namespace-pattern.html

Crispin13:06:41

that's it, yes. thanks for the link.

Ahmed Hassan14:06:56

@UKH2HDSQH Thanks. I did arranged namespaces as you said and it works.

Crispin08:06:27

if they are in the same namespace, you can define these dependedncies in the .cljs file and it will all just work

Crispin09:06:34

when you (:require [infinitelives.pixi.canvas :as c]) in a cljs file, you will automatically get the macros that are in the infinitelives.pixi.canvas clj file here: https://github.com/infinitelives/infinitelives.pixi/blob/master/src/clj/infinitelives/pixi/canvas.clj

Crispin09:06:31

the code these macros expand to use functions from namespaces that are imported at the top of the .cljs file without needing to be explicitly imported by the client

Andrew12:06:06

@jason821 do is a special form that you can't take the value of. https://clojure.org/reference/special_forms#do. This might be close to what you're trying to achieve:

(def mylist `((println 123) (println 456) (println 789)))
(eval mylist)

andy.fingerhut16:06:16

I am not sure what code you are responding to, but the documentation about do that you link to explicitly says: "and returns the value of the last". A do form does return a value -- but only the value of the last form inside of it. The return value of all except the last form inside of the do are discarded, that is true.

andy.fingerhut16:06:01

Oh, I see the code example now. Yes, apply cannot be used with macros, nor with special forms, only functions.

jason poage19:06:38

i am only trying to return the last item in the list, exactly how do works

jason poage19:06:53

and eval seems to return all the values in the list and return the list

jason poage19:06:21

This is probbly closer to what I’m trying to do:

(def mylist ['(str 123) '(str 456) '(str 789)])
(eval mylist)

jason poage19:06:09

I think this is what I’m looking for (last (eval mylist))

andy.fingerhut19:06:11

I do not know what the purpose of the code you are working on is, but just a note that if the data you are passing to eval can be provided from an untrusted source into a system, you are pretty open to lots of attacks.

andy.fingerhut19:06:11

If all of the eval'd stuff you trust not to attack you, then no worries.

Andrew20:06:26

What are you trying to do? I had assumed that you had a sequence of expressions that you wanted to execute, perhaps I missed the mark? It's pretty rare that you need to use eval.

jason poage20:06:17

`;; What I would like to do: 

; Take javascript as input and return javascript
(defn parseJS [name args & body]
  ; Define a new function
  (def name
    ; Convert arguments to clojure
    (let [d (js->clj args)
          ; Execute body using the new arugments
          ; This is the part I don't understand
          result (last (exec body))]
      ; Convert back to javascript
      (clj->js result))))
; Defines a function that takes javascript as input and returns javascript
(parseJS some-function [args] body)
; Calling the function that takes javascript as input and returns javascript
(some-function data)

jason poage20:06:22

im wondering if i need to create a macro to do this, but even so, i tried that and im not sure what im doing.

jason poage20:06:20

This is my current working solution:

; Converts data to clojurescript
; Sends the clojure data to the callback
; Converts data back to javascript
(defn parseJS [data callback]
  (let [d (js->clj data)
        newData (callback d)]
    (clj->js newData)))

; Take javascript as input and return javascript
(defn ^:export some-function [& args]
  (parseJS args
           (fn [args]
             ; This is where the actual logic goes
             args)))

jason poage21:06:26

I think I found a solution. I am just going to create wrappers for my functions that work with javascript

(defn do-something-with-cljs [data]
; This is where the actual logic goes
  data)
(defn parseJS [data callback]
  (let [d (js->clj data)
        newData (callback d)]
    (clj->js newData)))
; Take javascript as input and return javascript
(defn ^:export js-wrapper [js]
  (parseJS js do-something-with-cljs))

Felix Linker12:06:07

I sometimes use private functions in namespaces for obvious reasons. Is there a way to easily load a file into the repl with the private functions being accessible?

Andrew13:06:38

You can use the function var to access the private functions: (#'other-namespace/private-fn arg1 arg2)

👍 1
❤️ 1
Andrew13:06:46

You're welcome

Ahmed Hassan14:06:31

How is situation of Clojure/ClojureScript development on windows and specifically windows 10? I've previously worked with Ubuntu.

Mitch14:06:08

I haven't seen a lot of discussion on it so far, but I believe there are people using it. Another option I have heard of is installing Clojure through the Linux Subsystem for Windows, which I believe others have had success with

✔️ 1
Ahmed Hassan14:06:20

Thanks. That helps.

mzavarella14:06:10

I have a desktop box at work with Windows 10 installed on it. I usually stick with my linux laptop but for when I need to use the desktop, I use WSL with Ubuntu to get my work done. It's much better than using windows directly

✔️ 1
Ahmed Hassan14:06:09

Thanks @U0DHQ67U5 How is experience when using WSL as compared with original Ubuntu?

mzavarella15:06:42

I'll always prefer to use a full linux system of WSL. With WSL, I wind up in a funky filesystem setup in order to keep my C: drive mounted to the WSL disk. I also find myself running into issues of productivity since I'm always stuck in tmux. I start to miss my window manager and better terminals like urxvt

Ahmed Hassan17:06:30

@U0DHQ67U5 What is full Linux system of WSL?

mzavarella17:06:16

A typo! A full linux system over WSL. My bad

Ahmed Hassan17:06:49

How to run full Linux system over WSL?

mzavarella18:06:31

By full linux system I mean installing Linux on your computer or running it in a VM. Windows, as an OS, doesn't lend itself well to my development workflow. I'm very terminal based and Windows doesn't provide a good terminal system by default, has no good package managers based in a terminal, and is generally difficult to maneuver. Pile on top of that the differences between running the app on windows vs. linux, and I'm completely turned off to the idea of working on it.

mzavarella18:06:46

However, the Windows Subsystem for Linux can make it much less painful. You can install a 'mini' Linux system on Windows 10 now that will provide you with good enough terminal support and whatnot. It's not quite pretty to look at so I also use ConEmu (a terminal emulator for windows) instead of the built in terminal for WSL to get everything looking correct. I still run into a fair bit of lag though, especially if I have to remotely control my desktop.

Ahmed Hassan18:06:39

Thanks. I was using Ubuntu on my previous laptop. Now I've got HP Envy with Windows 10. So, I'm weighing options to do Clojure (Script) development.

mzavarella18:06:00

I'm going to speak in the hypothetical 'you' here. If you're comfortable with using Ubuntu, I'd always recommend switching to Ubuntu (or whatever Linux you'd prefer). You don't have to completely wipe the disk, you can always do a dual boot. Windows and Ubuntu is a very common dual boot system. If you're not super comfortable, I usually recommend trying Ubuntu via VirtualBox. No worries about deleting an OS or losing files and if you blow up your Linux system, you just exit out of VirtualBox, delete the bad virutal disk, and then try again. Now speaking directly to you. Clojure has infinitely better support when you're working on Mac or Linux. The tooling, help provided, documentation, etc... It all favors Mac and Linux. Food for thought!

✔️ 1
👍 1
Ahmed Hassan18:06:35

Thanks. That makes sense. Dual boot seems better option, so I can set up a permanent working environment. As running VirtalBox would put extra load on laptop.

Ahmed Hassan18:06:06

I've used Ubuntu 16.04, Now I think it's better to move to 18.04.

mzavarella19:06:38

If you're going to use Ubuntu, in my opinion, it's always best to stick with the latest long term support (LTS) version, so yeah 18.04 is definitely the move. If your computer is struggling to run the OS because of low system resources (ram, cpu), then Xubuntu is a good choice too. It's the same Ubuntu system but with a much lighter desktop environment called XFCE. It's very quick on older and low resource computers

Felix Linker14:06:44

Is there a nice alternative to update which also receives the respective key as argument? Right now I have to fiddle around with reduce and have some redundancies in my code that I'd like to see get wiped out 😏

ghadi14:06:11

there isn't but it's straightfoward to make one @linkerfelix

Felix Linker14:06:01

Straight forward in the sense that you can use update or would you rely on reduce?

ghadi14:06:38

you can make your own variant of update

ghadi14:06:04

or something higher order that you feed to update or reduce

Felix Linker14:06:09

Ah, well. Yeah, it's plain simple. I always forget that update is not a map-type equivalent of map

gilmar20:06:36

Hello guys, I'm trying use coverage at clojure project but I can not do it. I'm using that lib () and this is the exception: (Caused by: java.lang.AssertionError: Assert failed: Cannot expand '[#Interceptor{:name } #Interceptor{:name :pedestal-api.content-negotiation/serialise-response}) Can someone help me?

gilmar20:06:27

@U050ECB92 I'm use command line to execute coverage: lein cloverage

ghadi20:06:10

make sure the project runs and compiles properly before running this tool

ghadi20:06:29

you're going to want to load the namespaces

ghadi20:06:36

with require or whatever

ghadi20:06:54

I suspect this error is a pedestal route error, unrelated to cloverage

gilmar20:06:58

Exception in thread "main" java.lang.AssertionError: Assert failed: Cannot expand '[#Interceptor{:name } #Interceptor{:name :pedestal-api.content-negotiation/serialise-response} #Interceptor{:name :io.pedestal.http.body-params/body-params} #Interceptor{:name :pedestal-api.request-params/common-body} {:name :route-swagger.interceptor/coerce-request, :enter #object[route_swagger.interceptor$coerce_request$fn__22876 0x5be38ef2 "[email protected]"]} {:name :route-swagger.interceptor/validate-response, :leave #object[route_swagger.interceptor$validate_response$fn__22882 0x7a3655b4 "[email protected]"]}]' as a route. Expected a verb map or path string, but found a class clojure.lang.PersistentVector instead false, compiling:(/tmp/form-init5604330725587642492.clj:1:73) at clojure.lang.Compiler.load(Compiler.java:7526) at clojure.lang.Compiler.loadFile(Compiler.java:7452) at clojure.main$load_script.invokeStatic(main.clj:278) at clojure.main$init_opt.invokeStatic(main.clj:280) at clojure.main$init_opt.invoke(main.clj:280) at clojure.main$initialize.invokeStatic(main.clj:311) at clojure.main$null_opt.invokeStatic(main.clj:345) at clojure.main$null_opt.invoke(main.clj:342) at clojure.main$main.invokeStatic(main.clj:424) at clojure.main$main.doInvoke(main.clj:387) at clojure.lang.RestFn.applyTo(RestFn.java:137) at clojure.lang.Var.applyTo(Var.java:702) at clojure.main.main(main.java:37) This is all exception:

Caused by: java.lang.AssertionError: Assert failed: Cannot expand '[#Interceptor{:name } #Interceptor{:name :pedestal-api.content-negotiation/serialise-response} #Interceptor{:name :io.pedestal.http.body-params/body-params} #Interceptor{:name :pedestal-api.request-params/common-body} {:name :route-swagger.interceptor/coerce-request, :enter #object[route_swagger.interceptor$coerce_request$fn__22876 0x5be38ef2 "[email protected]"]} {:name :route-swagger.interceptor/validate-response, :leave #object[route_swagger.interceptor$validate_response$fn__22882 0x7a3655b4 "[email protected]"]}]' as a route. Expected a verb map or path string, but found a class clojure.lang.PersistentVector instead
false

ghadi20:06:22

Well that tells you something

ghadi20:06:50

it's failing during load, no cloverage in the stack trace

ghadi20:06:30

make sure your pedestal routes expand properly

ghadi20:06:48

seems like you have something broken there > expected a verb map or path string, but found a class clojure.lang.PersistentVector instead

gilmar20:06:24

Thanks 🙂

gilmar20:06:59

A doubt. Running the project I have no route error :thinking_face:

ghadi20:06:20

check your square brackets

ghadi20:06:09

@linux.soares use *e to print the entire stacktrace. I suspect the problem is not in cloverage at all

ghadi20:06:03

(Throwable->map *e) if you're in some environment that prints exceptions in a weird way

ghadi20:06:31

if you're running from the command line and don't have a full stacktrace available, that's a naughty tool.

ghadi20:06:19

You might want to figure out how to call cloverage directly from a REPL if the stacktrace is absent or not useful

markx21:06:20

Hi all! I’m trying to implement this function, print-later that will wait for 5 seconds, then print something, like “abc”. And more importantly, if I call this function again, it will cancel the previous invocations, and so only prints once. For example,

(print-later) (print-later)     ;prints "abc" only once.

-----------------
(print-later)    
(Thread/sleep 6000)
(print-later)     
; this should print "abc" twice.
Now most importantly, is there a way to do this without introducing a global state?

hiredman21:06:46

of course not

hiredman21:06:47

I mean, you can quibble with definitions of global, but print-later is going to need some shared way to interact regardless of the scope it is in

markx21:06:32

well maybe not global state. I mean some state out of this function itself.

Nate Sutton21:06:17

you need something to hold onto that allows you to address the currently running print-later thread and tell it to cancel so you can start another

Nate Sutton21:06:11

you could use channels for this, where one is a timeout and you wait on the quit channel and the timeout channel. if the timeout is reached you print, if the other channel then you do nothing

andy.fingerhut21:06:16

This is probably in the area of quibbling about whether state is global or not, but you could have a let-bound symbol whose value is a stateful thing like an atom, and then defn a function inside of that let, which makes the stateful thing only accessible by calling that function. Example:

user=> (let [x (atom nil)]
  (defn read-x-return-old-and-new [val]
    (swap-vals! x (constantly val))))
#'user/read-x-return-old-and-new
user=> (read-x-return-old-and-new 8)
[nil 8]
user=> (read-x-return-old-and-new 20)
[8 20]
user=> (read-x-return-old-and-new -5)
[20 -5]

bronsa21:06:52

I had completely forgotten about swap-vals! :)

andy.fingerhut22:06:21

Me, too, until I looked up the atoms section in the cheat sheet.

andy.fingerhut22:06:34

I was pretty sure there was something that let you get the original value

bronsa22:06:33

❤️ that cheatsheet

andy.fingerhut22:06:30

Finally touching it up for Clojure 1.10, a few months later

bronsa22:06:51

yeah just saw the ticket

bronsa22:06:05

great work as always

markx22:06:19

This is pretty good for my usecase! Although I wonder if it’s bad practice to have defn in a let.

andy.fingerhut22:06:04

Depends upon what you mean by bad practice. I have seen it done, occasionally, when it is helpful. I would say it is no better or worse than making a function stateful, i.e. the same warnings and cautions apply.

alexmiller22:06:27

I'd say it's more bad than good. :) Just pull out the atom to a private var.

andy.fingerhut22:06:19

For better debuggability of the resulting system, I guess?

noisesmith23:06:58

yeah, data hiding is arguably an antipattern when most values are immutable anyway - misusing your atom if you've marked it private to the namespace is already hostile, and by closing over it you lose the access for debugging

noisesmith23:06:27

someone wrote a good article on this about four years back, maybe five. Maybe it was cgrand or stuarthalloway?

alexmiller01:06:10

avoiding top level effects seems like a good enough reason

markx22:06:19

Is there a way to extend a Java class and override some static variables? Using proxy?

alexmiller22:06:27

overriding static variables is not a thing

markx22:06:51

Ah yes. Thanks.

markx23:06:42

Yeah I was mostly hoping for something without a state outside the function. But still, thanks!

Nate Sutton22:06:07

@markx assuming you can hold onto a quit channel, that would work for what you want

Nate Sutton22:06:54

you can send true on the quit-ch to cancel, like (a/>!! quit-ch true)

Nate Sutton22:06:05

hate to fire and forget, but I gotta run