Fork me on GitHub
#clojure
<
2020-05-28
>
johnnyillinois00:05:43

What’s the “goto” strategy for requiring a namespace only for the side effects (defmethod...). If I put in the “ns” macro, tooling will try to remove it. So currently, I am calling the require function directly

potetm00:05:01

a. don’t use that tooling b. move the defmethods

potetm00:05:43

but that’s a pretty normal thing, so really just have your tooling skip that ns block

johnnyillinois00:05:22

Although, not exactly what i was hoping for

potetm00:05:51

I mean, you could issue an explicit require

potetm00:05:24

so (ns (:require …)) (require 'my-ns.with-methods))

potetm00:05:14

yeah okay, that’s option c 😄

😆 4
didibus02:05:21

Ya, I'd say that's bad tooling

didibus02:05:57

What clj-kondo did I think is the right thing. It will leave alone requires that have no alias and no refer

didibus02:05:11

> the convention that if there is no modifiers in the libspec then it is being required for side-effects

andy.fingerhut02:05:02

What require's are not done for side effects? That is, if you think of def or defn as having a side effect of creating a new Clojure var, then most require 's cause side effects, yes?

andy.fingerhut02:05:47

Side effects here means ... what exactly?

potetm02:05:35

“bash the memory location of the multimethod”?

potetm02:05:53

“and not declare any vars”

didibus02:05:01

It means that the namespace require is needed even though it is not refered anywhere in namesapce

andy.fingerhut02:05:06

For a reloaded style workflow where you want to require it again after editing the underlying source code files, I guess?

potetm02:05:31

absent reloaded, you want to register a bunch of methods on a multi

potetm02:05:35

just on startup

didibus02:05:40

So like when you don't actually use any var from the required namespace, but still need it to be required

potetm02:05:45

so you require a namespace with a bunch of registrations

andy.fingerhut02:05:28

What tooling tries to remove such a require (if you know)?

potetm02:05:39

yea, no idea

potetm02:05:50

im a tooling luddite

didibus02:05:23

borkdude was trying to come up with a metadata convention for tools, which I think would still be nice. There were proposals, and votes. The votes mentioned that people liked using the namespace :tooling for meta keys to instruct tooling. And for this use case, the vote was to have ^:tooling/keep as meta. But there was so much different opinions, that I think going with nothing and just assuming that no libspec = used for side effect really made sense

didibus02:05:38

Cider and Cursive do that

didibus02:05:51

When you ask to "clean the namespace"

didibus02:05:09

It removes unused requires and orders them

potetm02:05:16

gawd I don’t want to know what that button does

andy.fingerhut02:05:27

"clean the namespace" meaning "fix up an ns form in a file to remove unnecessary things, and add needed things, guessed by scanning the rest of the code later in the file"?

💯 4
potetm02:05:00

I doubt it adds needed things, but it probably sorts

didibus02:05:54

Well, cider has a command: cljr-add-missing-libspec

didibus02:05:26

Which will add missing requires. But it also has one to clean, which won't add missing, just remove unused, collapse duplicate, sort, etc.

didibus02:05:04

Its triggered automatically sometimes as well, like if you just type something/foo when you press space after it, if it finds a something/foo somewhere it'll auto add the require for it

potetm02:05:02

yeah cursive does that too, it just won’t do it when optimizing an ns

didibus02:05:19

So what does optimize do? If not removed unused?

potetm02:05:54

there is no optimize for clojure, it’s for java

potetm02:05:05

it sorts and removes unused iirc

didibus02:05:30

And some linters warn against "unused" requires as well.

andy.fingerhut02:05:09

Clojure/Java is certainly dynamic enough that such a tool seems guaranteed to do undesired things in some cases.

didibus02:05:16

Well, they don't all do the add what is missing. But they mostly do the remove what is not used

vemv02:05:22

> Cider and Cursive do that that's a correct and conservative thing to do, but there will be false negatives. It kinda assumes you use aliases for everything. Some teams don't alias every required ns

didibus02:05:00

Oh sorry, I mean that they both remove what they believe is unused. I actually don't think they follow that convention.n.

potetm02:05:19

@didibus what’s the button in cursive for this?

didibus02:05:50

I think they both support some custom meta to tell them no to remove things

didibus02:05:58

Hum... need to remember

potetm02:05:04

“optimize imports?”

potetm02:05:23

not seeing it

didibus02:05:59

Actually might be wrong about Cursive, I guess its just cider, which under the hood uses refactor-clj

didibus02:05:10

But I do think Cursive at least warns against unused requires?

didibus02:05:26

I thought Cursive had it as well

potetm02:05:41

“warns” - it shows that a symbol is unused (same as in a fn)

didibus02:05:41

I also think there is a lein plugin that does this.

potetm02:05:07

but if you have a dangling require w/ no alias, it treats it normally

didibus02:05:51

So then Cursive seem to follow this convention as well, which I think is the right way to go

didibus02:05:23

I feel its easy, because you can say: If you use a required var through a fully qualified access, that's a warn for using an alias or refer instead. And if you have a require with no alias or refer, and you don't have any fully qualified use of it, then you can assume its there for side effects.

didibus02:05:19

So maybe we just need a pr to clj-refactpor?

vemv02:05:09

it'd be an opinionated pr that would introduce false negatives there currently are false positives, which isn't a good thing either my personal approach to this problem is making the defmethod side-effect very explicit by making it part of a Component/Integrant start method. Which also solves the additional problem of changed defmethods not being code-reloaded

donotwant 4
johnnyillinois04:05:04

That’s what was proposed. However, for projects started without those conventions it may introduce new discussions

vemv05:05:48

Cool! In case it helps I have authored a very thin helper around multimethod definition: https://github.com/nedap/utils.modular#nedaputilsmodularapiadd-method The implementation is really minimal, one could trivially implement the same without an external library.

potetm02:05:37

What is the advantage of that?

didibus02:05:12

I mean, that's fair. But I'd rather a "clean ns" refactor leaves unused things behind, then remove things that then fail to compile or throw runtime errors

👍 4
vemv02:05:33

respected :)

didibus02:05:33

There are other cases for side effecting require. More so in ClojureScript, where you need to require a JS lib which some component you use depends on

vemv02:05:39

yeah or top-level clojure.core/extend* calls

potetm02:05:57

Is that an answer to my question? (I genuinely cannot tell.)

didibus02:05:08

Some people were saying to just put those side-effect require outside the ns, like using (require ...). But in ClojureScript that is not allowed. And that breaks other tooling assumptions. Like tools for auto-complete that look to the ns form to know what the namespace depends on

✔️ 4
potetm02:05:06

for example tools.namespace if memory serves

potetm02:05:36

(It’s fine to ignore my q btw. No need to explain if you don’t want.)

vemv02:05:20

@potetm nope > What is the advantage of that? by placing side-effects in start you avoid cleanup false postives and ensure the latest code definition is the one that gets loaded

potetm02:05:04

“avoid cleanup false positives”?

potetm02:05:48

ah you call remove-all-methods on unload?

potetm02:05:00

or remove-method

vemv02:05:30

not necessary, but also can be done for extra strictness

potetm02:05:52

then what does “avoid cleanup false positives mean”?

potetm02:05:26

eh, you can get that exact behavior and more from tools.namespace, but it makes some sense

vemv02:05:16

no, t.n isn't smart enough to analyze defmethods, extend* calls etc. It just builds a dep tree out of requires

potetm02:05:20

Well the alternative option is to put it in your ns declaration and use t.n

vemv02:05:03

yes, but that's not gonna prevent avoid cleanup false positives (tooling that cleans up unused requires)

didibus02:05:31

I guess maybe one could use load-namespace in cljs and load in clojure to just load for side effect. Hum... That might be a good option as well. The intent is there too, we just want it loaded, its not that we require it explicitly.

didibus02:05:30

Well, at that point, we're just back at a require without a libspec. I mean, nobody would know if the load is needed or not. So we would just trust you really need it to be loaded. Same with a require without libspec.

didibus02:05:36

And that's why I think that's the best otion. Even if we tag with metadata, like ^:tooling/keep, nothing guarantees that it is truly needed. Maybe someone had added the meta a year ago, and since its no longer needed, but it lingers.

potetm02:05:40

In my mind, there are roughly two phases in my clj program: 1. building program state (def vars, register multimethods, etc), 2. execution That’s not strictly true (for good reason), but most programs would do fine to make that distinction.

potetm02:05:06

I prefer the phases to not overlap.

didibus02:05:01

Ya I agree. Keeping it all in ns is definitly more readable. I mean, if its easier for the tools to understand, so is it for humans

didibus02:05:29

The only time I've used a require outside is: 1. repl, 2. conditional require

vemv02:05:57

@potetm it's a fair thing to think, but clojure is quite runtime-oriented defmethod doesn't trigger any compiler magic. It's just a java call to .addMethod https://github.com/clojure/clojure/blob/38bafca9e76cd6625d8dce5fb6d16b87845c8b9d/src/clj/clojure/core.clj#L1783 . i.e. like any other java interop you might use in your programs Placing it in a non-conventional place like Component/Integrant start makes this runtime-orientation very evident

potetm02:05:22

> That’s not strictly true (for good reason), but most programs would do fine to make that distinction.

potetm02:05:34

(I know how defmethod works, fwiw)

potetm02:05:25

were that strictly true, we wouldn’t have a REPL!

👀 4
potetm02:05:41

so yeah, not a idealist here

didibus02:05:14

Ya, I think to some extent, ClojureScript shows us you can get pretty far without all the dynamism. So for example, it doesn't let you require things arbitrarily at runtime, and it has no vars. Well... okay, its actually really annoying that it has no Var 😛

didibus02:05:37

And ClojureScript REPLs are quite lacking as well

solf02:05:26

By any chance, does lein uberjar ignore resource files that start with .? It doesn't seem to pick up what I have in resources/.s3-conf/files.edn

seancorfield03:05:23

@dromar56 That does appear to be the case, based on a quick test locally.

solf03:05:31

Thanks for confirming. It seems like something that should be configurable in lein, but I can't find where, I'll continue looking

seancorfield03:05:41

Yup, was just about to paste that link -- sorry, got distracted by feeding the cats. So you can override that in project.clj

seancorfield03:05:16

(not sure what the effect of not excluding /. will be, assuming that's what it is trying to exclude)

didibus07:05:22

If I wanted to publish a Clojure lib, I'm trying to figure out what's the way to go. In Clojar, can you just choose what organization to use? Some libs don't seem to have one at all, what's the deal with this?

hindol08:05:10

In Clojars, you can reserve as many prefixes as you want. A prefix is yours as soon as you publish some prefix/lib with it. You cannot take someone else's prefix. None can take your prefix.

hindol08:05:34

The prefix can be the org name you want.

didibus08:05:22

Hum I see. Cause I'm thinking I want to follow Rich Hickey's advice, and use a DNS I own as my namespace. So it be my blog.project, and thus my org for my lib would also be blog/project

didibus08:05:48

But this would not match with my github account, and thus my github repo would be didibus/project

didibus08:05:03

Also my current Clojar account is under didibus

didibus08:05:28

So wanted to know if I could publish using my account under a different prefix

didibus08:05:25

So it seems I can, so Clojar is good. Now there's just github, I wonder if I should create an organization, and then I can add my account as admin to it, and effectively then use my account to manage blog/project repos ?

hindol08:05:16

Not answering the Github question, but in Clojars you can follow Rich's advice and do com.github.didibus/blog (for example). There is no check that verifies the prefix belongs to you. As long as no one claimed it before you, you are free to claim it.

didibus08:05:18

Right, I mean, I don't want to use my github alias. I'd like to use my own DNS I own, which is rubberducking (which is the dns I own for my blog)

hindol08:05:25

There is absolutely zero relation between what you claim in Clojars and how you manage your repos.

hindol08:05:51

Yeah, you can do that.

👍 4
didibus08:05:34

so I'd like: rubberducking/poject in clojar, but managed by my didibus account. And ideally, I'd want rubberducking/project as the repo in gtihub as well, still managed by my didibus account

didibus08:05:41

hum... I guess you're right about the com, if I don't put that as well, its not really a DNS I own, since it could be http://rubberducking.org etc

didibus08:05:26

I guess it should be com.rubberducking/project

hindol08:05:39

Yeah, that will work. Clojars takes the prefix from POM's groupId or the prefix in deps.edn, project.clj etc.

didibus08:05:31

I guess it doesn't matter if the repo in github is under a different name. In theory, a project repo can change place, like I could switch it to gitlab in the future, etc.

didibus08:05:53

But deps.edn git dependencies make me wonder that

didibus08:05:07

If someone took a git dependency on it, then it gets a bit weird

hindol08:05:20

I have seen people use me.rubberducking style as well. A little shorter.

didibus08:05:06

I'll do some reading on github organization. Man naming is hard 😛

didibus08:05:13

thx for the help

didibus08:05:04

Though I guess com.github.didibus/project wouldn't be that bad either. :thinking_face:

hindol08:05:50

No problem. Github orgs are easy too. Just create one. Mostly when you'd like to dissociate the project from your name (to give a sense that the project is not owned by you).

hindol08:05:48

Yeah, I went with com.github.hindol as I own it permanently. I won't delete my Github account ever.

hindol08:05:02

I think it is a sane choice.

hindol08:05:15

I name the Clojure namespaces whatever I want and the user has freedom to alias it. A long name only in deps.edn is not so bad. In fact it is good.

didibus08:05:43

Ya, I think I was thinking my DNS is like an even more reserved name, since I actually paid for it 😛 But its not like I expect github to go away anytime soon.

didibus08:05:40

I was thnking my namespaces would also be the same, though I guess I could drop the com.github.

didibus08:05:23

For namespace its just the "risk of clash".

hindol08:05:47

I never include my username in the namespace name. I would just name the namespace blog.core etc.

hindol08:05:48

There is the risk of clash, that's why people dream up unique names like reitit, sieppari, yada...

didibus08:05:54

hum.. but then you need to find a unique project name no?

didibus08:05:35

This is where I fail 😛 Mine is just called web-repl

hindol08:05:36

Yeah, 😛

didibus08:05:35

Feel its pretty generic, and I don't know, could clash one day. Maybe I'm too worried about it

hindol08:05:46

Shorter namespaces are a tad more pleasant to use. But whatever.

didibus08:05:37

Ya, but, I don't know, I feel like I never really type it anyways, it auto-completes, or I copy/paste it

didibus08:05:11

What I'm thinking is if one day it does clash, as a user, you're just screwed, like, there's absolutly no workaround

didibus08:05:14

I think that's where I like the idea of having an "organization"

didibus08:05:06

So say rubberducking is my "org". All my projects would be rubberducking.bla. Or I guess I could come up even with some new name for it. Like what the funcool guy does

👍 4
didibus08:05:05

I think lambdaisland does that as well

seancorfield15:05:44

This is why clj-new encourages projects to be called username/project or orgname/project (where username or orgname are from GitHub) and it puts those in pom.xml as the group/artifact name and it creates src/username/project.clj or src/orgname/project.clj as the main namespace file 🙂

didibus18:05:28

Ya, it make sense. But after further looking. It seems neither lambdaisland nor funcool putbtheirnorg name in the namespace

didibus18:05:38

Only in Clojar and github

didibus18:05:53

But I still think your default is sane

Drew Verlee17:05:02

When working with clojure i find it easiest to debug with a mixture of things. One i method i come back to a lot is just using atom to capture state. This is great except in practice i do all the fiddling right in the ns with the code. Which can get distracting and messy sometimes. I'm trying to think of an alternative method.

noisesmith17:05:16

I combine the atom for tracking (eg. vector which I use swap! to conj to), with tap> so that I am only adding tap calls in the target ns

noisesmith17:05:11

and all the debugging defs can be in user or whatever

noisesmith17:05:40

another useful idiom for this is using a map, with a namespaced keyword matching the function being debugged

noisesmith17:05:28

(defn foo [& args] (tap> {:context ::foo :args args ... ...}) ...)

phronmophobic17:05:19

is there a good resource covering tap> ?the docs seems sparse, https://clojuredocs.org/clojure.core/tap%3E

noisesmith17:05:32

I think regarding "resource", all you need is: use tap> to offer data use add-tap to register a function that should consume that data (async)

phronmophobic17:05:23

sweet. I’ve done my own adhoc versions of this before.

phronmophobic17:05:25

this seems better

phronmophobic17:05:11

are there any libraries that build on top of this?

noisesmith17:05:49

I know rebl can integrate

noisesmith17:05:08

I hope that every debugging / logging / inspection lib would integrate going forward

phronmophobic17:05:36

yea, seems super useful

noisesmith17:05:56

a funny side effect of this is that I can end up with dangling tap> calls in my namespace if I'm not careful, but it's a no-op if no function is registered to consume it, so it's less of a problem than println which has the same issue

phronmophobic17:05:41

I can’t count the number of times I’ve written some variant of: > (def my-log (atom [])) > (defn log! [val] > (swap! my-log conj val))

noisesmith17:05:47

right - now that's in my user.clj in my home dir, and I can just register it with add-tap

noisesmith17:05:40

this reminds me I should make a fireplace shortcut to load-file ~/user.clj rather than relying on classpath stuff

phronmophobic17:05:57

my ongoing project has been a library for desktop gui development in clj. building dev/debug tools for clojure seems like a good place to showcase what’s possible. thanks for tap> tip!

noisesmith18:05:37

the one thing I would have done differently would be to have tap> return the thing you pass to it, the workaround being (doto x tap>) of course

👍 4
seancorfield18:05:09

tap> returns true if it succeeds and false if it fails -- which I believe only happens if the buffer it uses is full behind the scenes?

seancorfield18:05:39

(according to the source, yes, there's a limited size queue behind it)

seancorfield18:05:47

(java.util.concurrent.ArrayBlockingQueue. 1024)

seancorfield18:05:31

The "overhead" of calling tap> if no one is listening is to start a daemon thread that sits there trying to read from that queue.

phronmophobic17:05:35

and my duckduckgo-fu is failing me

andy.fingerhut17:05:41

There is a little bit more in the changes in Clojure 1.10.0 release notes here: https://github.com/clojure/clojure/blob/master/changes.md#23-tap

👍 4
phronmophobic17:05:13

it’s been a while since I’ve checked the change list. lots of other goodies in here too

andy.fingerhut17:05:14

I added a link to that to the http://ClojureDocs.org page for tap>, and see also links between tap> add-tap remove-tap. 2 minutes, and done! (with that minimal level of info, at least, not actually in-depth docs)

metal 4
thanks2 4
noisesmith17:05:08

I think a real gotcha here is that the amount of improvement / utility offered is massive, compared to the utter simplicity of using them, so you expect more doc than you actually need :D

noisesmith17:05:13

it's very simple, the most useful thing is it decouples offering data to debug from the code doing the debugging

hiredman17:05:15

I've gotten into the habit of asserting what I want the state to be instead of capturing the state

noisesmith17:05:23

all you need is tap>, add-tap, remove-tap

lukasz18:05:39

I know I can mark functions as deprecated, but can protocols include deprecation info? Something like this

(defprotocol Foo 
  (test ^:deprecated [args...] "doc string"))

lukasz18:05:30

(there's nothing in the doc string for defprotocol)

Alex Miller (Clojure team)19:05:12

you can put meta on the test and that will end up in the var

🙌 4
lukasz21:05:42

Thank you!

Alex Miller (Clojure team)19:05:45

(defprotocol Foo (^:deprecated test [args...] "doc string"))
(meta #'test)

Simon19:05:19

Hi! When using git coordinates as a dependency, why is it only possible to select a specific sha? When I have a shared library that is also being actively developed, it would be nice to just set a branch-name instead of a sha. Is there a reason for not being able to use any kind of reference to a commit?

andy.fingerhut19:05:01

A branch name is not a reference to a specific commit, is it?

Simon19:05:24

It is a reference to a specific commit. It is just a reference that changes over time. I don't see why that is a problem.

Alex Miller (Clojure team)20:05:23

also, because it moves, it does not give you a reproducible build and there are security and other implications to that

Alex Miller (Clojure team)20:05:52

like someone changing their branch to point to something that mines bitcoin

Alex Miller (Clojure team)20:05:10

all that said, the dev scenario is one where this would be useful and I happen to be in the same place right now so I've been thinking about ways to make this work with some kind of guards around it

Simon20:05:21

I understand but maven-dependencies does not protect against that either, right?

Alex Miller (Clojure team)20:05:34

maven doesn't have git dependencies

Simon20:05:52

And yes, of course only in dev time.

ghadi20:05:12

basically SHAs are values and maven versions, git tags, git branches, are all references

Simon20:05:14

But maven dependencies does not give you reproducible builds

Alex Miller (Clojure team)20:05:14

maven artifact versions don't change out from under you - they are immutable

Alex Miller (Clojure team)20:05:31

well that's due to version selection, which is an independent problem

Simon20:05:34

Not if they are SNAPSHOTs

ghadi20:05:51

(though you would draw a lot of ire from users if you re-cut different code with the same maven version or git tag)

Alex Miller (Clojure team)20:05:05

there are a couple of exceptions, snapshots (which also break clj's cache) and special virtual versions like RELEASE or LATEST

Alex Miller (Clojure team)20:05:21

(which deps does not explicitly support and Maven has been phasing out)

Alex Miller (Clojure team)20:05:40

but Maven does not allow you to, for example, deploy an artifact based on snapshots

Alex Miller (Clojure team)20:05:49

so there are some guards there

Simon20:05:07

Yes, all I am saying is that these problems are not really specific to git dependencies

andy.fingerhut20:05:43

Is it fair to say that a design goal of tools deps is to avoid these problems?

Alex Miller (Clojure team)20:05:45

it's an area that does not yet have good solutions in clj

dominicm20:05:04

I've not had a case of this yet where a local root wasn't better.

Alex Miller (Clojure team)20:05:22

we just haven't tried to solve them yet

Simon20:05:58

@U09LZR36F Will try that, thanks!

Alex Miller (Clojure team)20:05:27

local deps have their own caveats (deps don't see changes to transitive local deps.edn files, so you need to -Sforce a classpath update), but yes that is an option

dominicm20:05:32

@U013G4ZKQTW to be honest, it's saved me a few times where I've forgotten a project is using it.

dominicm20:05:26

@U064X3EF3 for sure. But I think that feels more like a bug, vs not supporting mutable artefact references which is in many ways an anti feature.

Simon20:05:30

Thank you all for your answers! I still think there is a use case for this 🙂

Simon20:05:07

@U064X3EF3 Would it be possible to keep caching git deps on SHA and then when resolving a git dependency we turn the reference into a sha and look for that in the cache?

hiredman20:05:05

there are a few different caches involved, the important one being a cache of the built classpath that is based on hashing the deps.edn file, clj can start quickly if it doesn't need to compute a new classpath

hiredman20:05:59

a feature that relies on parsing the deps.edn file already busts that most pivotal cache

Alex Miller (Clojure team)20:05:22

even that is probably solveable but this is all just solution space wankery. needs to be unrolled all the way back to the problem for better thinking, which is not something I have time to do right now

Kyle Brodie21:05:14

Is it possible to make a transducer that composes with other transducers but lets them work on just the v for pair values [k v] ? I keep writing (map (fn [[k v]] [k (my-f v)])) where map could be map, mapcat, filter, remove, etc

Kyle Brodie19:05:58

Example usage. Looks like I'll have to do something like map-longest so first returning 3 elements and second returning 2 elements produces 3 pairs instead of 2

noisesmith19:05:26

isn't that more verbose than the direct implementation via mapcat would be?

Kyle Brodie20:05:22

Yeah, I'm going to break it down into multiple transducers / functions. (mapcat (fn [[a b]] [a (repeat b 3)]) [[1 2]]) would produce [a b b b] but I want [[a b] [a b] [a b]]

noisesmith21:05:27

a helper for that is pretty easy to write

(defn update-val [f] (fn [entry] [(key entry) (f (val entry))]))

noisesmith21:05:01

so you end up with (map (update-val my-f))

hiredman21:05:23

doesn't work for filter or remove though

noisesmith21:05:54

true, and I don't even know what you'd expect mapcat to do here

noisesmith21:05:32

for filter / remove all you need is (comp my-f val)

Kyle Brodie21:05:21

mapcat would be like (map vector (repeat k) (f v)) The entries are 2 element vectors so key and val don't work

João Acabado21:05:11

hi everyone, Lisp was my first language and I'm trying to learn Clojure. Glad to find this community!

👋 12
noisesmith21:05:29

@kyle OK - that change is easy enough to adapt, but back to the original question no there's nothing built in like this

noisesmith21:05:12

for vectors you could also consider an idiom like #(update % 1 my-f)

😯 4
Kyle Brodie21:05:10

Okay, I think I'll write some higher order functions and still use map/mapcat/filter so something like (filter (value-pred my-pred)) and (map (transform-value my-f))

💯 4
jaide22:05:41

General interceptor question: Is it recommended\preferred to lay out the entire stack of of interceptors and opt out as errors or branches are encoutered or is it considered better to have them conditionally add to the queue?

jaide22:05:24

Hmm... nevermind. After writing it out I think option A is much better. Otherwise, it'd be like using -> on one form and expecting that to chain the next one and keep doing that. It would work, but not be composable or paint a clear picture of the actual pipeline.