Fork me on GitHub
#clojure
<
2019-01-01
>
caleb.macdonaldblack01:01:38

I'm confused about when to use what style of namespaced keys. I know how they work, I just don't know what is best from a maintainability standpoint. For example is :user/email a good namespaced key? Or should it be namespaced to the company such as com.company.user/email? Or perhaps namespaced to the project com.company.project.user/email or even a project file namespace such as com.company.project.spec.user/email. Should any way be used exclusively, or are there use cases where either style might be used? I'm thinking that :user/email still might result in naming collisions if two systems deal with user in a different way. I'm hoping to figure out an optimal solution for systems spanning many projects in an organisation.

caleb.macdonaldblack01:01:06

I think I remember a Rich Hickey talk on this but I don't remember which one

hiredman01:01:49

project level is pretty good, I don't care very much for using the names of code namespaces for keyword namespaces

hiredman01:01:47

I think a good rule of thumb is to imagine all your data that exists in memory exists in a sql database, the namespaces would be the table names and the names would be the column names

hiredman01:01:46

and then imagine everyone's data is in the same global table space, and what would you need to name your tables to avoid collisions

caleb.macdonaldblack01:01:47

When you say everyone's data, are you referring to company level namespace like com.company.user/email?

hiredman01:01:29

that would be one possible solution to that, prefixing all your table names with some string that you "own"

hiredman01:01:51

another solution would be using uuids, which would work but are not very friendly

seancorfield02:01:37

Another factor to consider @caleb.macdonaldblack is how "global" your data is going to be. Is this just a map of data that will exist inside your company? Inside just this project? How unique does the name need to be?

seancorfield02:01:25

If you produce it from a library you put out there for everyone to use, it needs to be "more unique" so that it can't conflict with any similar data used by any of the users of your library.

didibus02:01:45

I know I've used ::

didibus02:01:03

And the issue was that it coupled our key names with the namespace

didibus02:01:42

So I'd say be careful about that

caleb.macdonaldblack02:01:52

Right so it looks like there is no real convention around this. And I should just go with what makes the most sense given the factors.

caleb.macdonaldblack02:01:37

Some examples: If I need a db uri for my users db in my company I could namespace like this :db.users/uri. I wouldn't want to use :db/uri as I might have many databases in my company. I also didn't namespace to the company because this key will be accessed only within the company And I could use :user/email if users in my company are global among all projects. But if my company had two products I would namespace the user attributes accordingly such as :project1.user/email & :project2.user/email but only if some of the code was shared in any way.

caleb.macdonaldblack02:01:02

That's where my thinking is after considering the feedback

jumpnbrownweasel03:01:33

This issue of keyword namespaces is very confusing to me. What advantage is there to making the keyword completely unique across, for example, the organization? In other words, what harm is there in using :user/email for the email column in two different DBs in an organization? Is the keyword alone really used to determine which table an attribute belongs to?

jumpnbrownweasel03:01:23

Maybe :person/email would be an even better example.

caleb.macdonaldblack03:01:52

@mark540 If there was 2 databases for users (employees, clients) then the business logic for dealing with the two types of users would probably be different. And the data is stored is two different places. To me this because of the 2 datasources, I would think to namespace to two separate namespaces. The namespace gives me the ability to inspect my data and decide if I want to do something with a client or an employee. And I can tell the difference because of the namespace. If there was no difference, then there would be no reason to store client & employee separately or differently (with some technical exceptions such as security). Because they're essentially the same I would namespace them the same. I'm still working all this out but that's my thinking.

jumpnbrownweasel03:01:17

I understand what you're saying. I'm just surprised that there wouldn't always be context available (client vs employee) and that the keyword alone must be unique to get the context.

caleb.macdonaldblack03:01:59

Yea that's something I've though about also. For example the namespaces seem redundant for this data structure {:orgs [{:org/employees [{:employee/paychecks [{:paycheck/id 1}]}]}]} because each level up contains the context. Perhaps the namespace allows you to avoid passing in the context or more information than that is needed. For a paycheck/id=1 instead of giving {:employee/paychecks [{:paycheck/id 1}, you just give {:paycheck/id 1} and the namespace allows you to determine the context.

jumpnbrownweasel03:01:19

I think you must be right.

jumpnbrownweasel03:01:32

But it would be interesting to know what specific benefits others have found for the unique keywords.

caleb.macdonaldblack03:01:35

Yea I'd like to know also. I've never written code like and I'd like to know if others how found it practical for production use

caleb.macdonaldblack03:01:32

The most practical scenarios I can think of is dynamic spec validation. It could just lookup the spec for all its keys and avoid knowing any context apart from the keys

caleb.macdonaldblack03:01:12

I've never done that though so I'm not sure how if at all it would work

caleb.macdonaldblack03:01:40

Or even if it's a good idea. I feel like you would want to explicitly specify what spec you're validating against

jumpnbrownweasel03:01:44

OTOH, a spec for :person/email would probably work for email addresses in all tables.

caleb.macdonaldblack03:01:10

However you could still do that with unique keys and some mapping.

caleb.macdonaldblack03:01:44

But your thinking raises another question for me. So far I've assumed that the namespace of keys in a map would be the same. Are there any use cases for them to be different? Perhaps what your suggesting might also be a correct way of doing this. {:person/id "123" :person/name "Caleb" :employee/number "001"}

caleb.macdonaldblack03:01:50

As long as there would be no way that the information stored in the shared keys would need to be handled differently I would think

jumpnbrownweasel03:01:17

Yes, I've wondered about that. But I don't have enough experience in Clojure to say. Someone with more experience will probably chime in. I can admit that unique keywords give you the most flexibility for treating them differently, if necessary, just don't know specifically when it's necessary.

caleb.macdonaldblack03:01:02

Yea I agree. How far is too far. Perhaps there are times when you would use them and times when you wouldn't.

seancorfield03:01:57

When you're looking at nested data structures, remember that sometimes you'll just be passing part of it around -- so you don't always have the parental context.

seancorfield03:01:39

Qualified keys are designed to "avoid conflicts" and to "uniquely identify an entity in a given context". So they really are about context.

seancorfield03:01:43

For example, at work, we have a billing subsystem and we generally have all the keys in maps associated with billing qualified with wsbilling. The context we need to convey is that "this data all belongs to billing", so the maps within that system tend to have keys like :wsbilling/member-id, :wsbilling/initial-amount and so on -- unique within the context of billing and the systems that it interacts with.

seancorfield03:01:57

Another part of the system deals with multiple "layers" of domain logic so it uses qualifiers that identify the different layers.

caleb.macdonaldblack03:01:00

Right that makes sense. And expanding upon mixing namespaces in a map as I previously suggested. Looking back on work with datomic, your entities wouldn't contain maps with mixed namespaced keys. They would nest them under a ref.

seancorfield03:01:11

Some of these qualifiers match actual namespaces, some don't.

caleb.macdonaldblack03:01:30

So perhaps the second nesting would be better than mixing namespaces

seancorfield03:01:53

The idea is you use whatever qualifiers make sense for your data in your context. Making these "as unique as they need to be".

caleb.macdonaldblack04:01:00

@seancorfield And would the use of qualifiers matching actual namespaces be almost exclusively used for keywords private to the namespace? Are there other reasons you might do this?

seancorfield04:01:04

(which is why there are no specific rules of thumb about this whole thing 🙂 )

caleb.macdonaldblack04:01:06

Yea Clojure is very interesting in that regard. It's like the complete opposite of something opinionated like ruby on rails. Everyone sort of does what works for them. It does make it a little difficult if you're new however as its hard to copy what other people do because everyone does it so differently. I imagine it's an experienced developers dream though as they can do everything exactly how they like it.

seancorfield04:01:31

It means you need to understand more of the abstractions and concepts, yes.

Robert A. Randolph04:01:50

@caleb.macdonaldblack It's more so that people program with abstractions, and the concrete code that comes out varies

jumpnbrownweasel04:01:19

Thanks Sean, I see your point about nested data structures. And it is good to know that there is no standard convention. Another question: If you have both :employee/email and :client/email, don't you sometimes want to treat all emails the same or similarly, and in that case do you use the keyword name to identify it as an email? Just curious if you'd encountered that situation.

caleb.macdonaldblack04:01:56

@audiolabs That blows my mind a little bit. When deciding on how to implement something I've usually started by figuring out what worked for me in the past or finding out what other people have done. Pretty much just lots of practice. Perhaps to improve myself I should spend some more time reading up on the theory.

Robert A. Randolph04:01:20

@caleb.macdonaldblack It's not so much about theory, as much as it's about understanding 'what needs to happen'

seancorfield04:01:38

@mark540 If you've named them :employee/email and :client/email it'll generally be because you want to be able to tell them apart in a particular context. If you want to do something email-generic, you can pass either value to a function that just takes "an email address" or you can construct a new map containing the bits you might want under more generic names.

seancorfield04:01:12

When you get into specs, you also have to consider whether these fields have the same underlying spec or not.

✔️ 5
Robert A. Randolph04:01:30

@caleb.macdonaldblack I find that when programming in Lisps, especially clojure, I rarely need to think about writing code. I spend most of my time thinking about architecture or specification. I find this also leads to better applications.

seancorfield04:01:52

For example, your spec for :employee/email might be a regex that has a fixed @ domain name part (for your company).

seancorfield04:01:09

@caleb.macdonaldblack Clojure certainly rewards thinking at a higher level of abstraction a lot of the time... and can punish thinking at a lower level sometimes 🙂

Robert A. Randolph04:01:15

@seancorfield I feel like that's a bit unfair, because the flexibility of the language allows you to do far-reaching optimizations with lower-level code changes. Perhaps more accurate to say that Clojure punishes premature optimization more than other languages?

jumpnbrownweasel04:01:28

My takeaway from this conversation is that I am free to use keyword namespaces for whatever purpose is most suited to my app, which turns the focus back to the app data model. It's a nice freedom. Thanks everyone for the comments and help.

✔️ 5
caleb.macdonaldblack04:01:43

Likewise thank you for the help

seancorfield04:01:57

@audiolabs OK, yeah, that's what I was reaching for really.

seancorfield04:01:31

But also if you try to dive in and think about problems the way you might in other languages -- where mutable variables and loops are the basic building blocks -- you'll find Clojure working against you.

seancorfield04:01:09

So you often have to step back and think in terms of collections rather than elements, and abstractions rather than implementation, in order to get the "shape" of a Clojure solution.

✔️ 15
kulminaator10:01:37

wondering which is more intuitive for people to read, dispatching async work in future or async/thread , any opinions ? i don't really care for results that much but i need to dispatch a bunch of i/o bound threads and wait for all of them to finish

kulminaator10:01:09

and count of threads is likely to be bigger than core count because i/o has latency 😞

kulminaator10:01:54

it appears they both easily scale beyond the count of cores available

awb9913:01:30

I am doing some timeseries analytics with clojure, and I have noticed some strange performance issues in loading data. I load timeseries from CSV files (size 500KB each), and I find that slurping data in takes 10ms, and then the CSV parsing takes 100ms (I parse timestamp, and floats). I compared this to incanter csv load (which takes 300ms, so worse). I used taoensso.tufte for performance tracking. I wonder if there are any ideashow to improve performance of such stimple things? If reading from disk takes 10ms, then the parsing should be perhaps another 10ms, but not longer, as disks typically are the bottomleg. I spend a lot of time just to get simple benchmarks, because I had to remove all the lazyness via "doall". Any ideas on how to speed Clojure up? Any tricks or recommendations?

kulminaator13:01:59

did you measure the timings before and after jit got kicked in as well ?

awb9913:01:28

I assume the Java JIT runs the first time a function is called.

awb9913:01:37

I ran my performance numbers many times.

awb9913:01:42

So I dont think this is the culprit.

kulminaator13:01:03

they dont get compiled on the first time

kulminaator13:01:17

that number is either in hundreds or thousands before java kicks it's jit into motion

kulminaator13:01:29

but if you had files of 500kb then yes it should have kicked in at some point

kulminaator13:01:52

can you try to isolate the slowness part by excluding parts of parsing ?

kulminaator13:01:07

date parsing does sound a little bit of annoying , so i would try to avoid that at first

awb9913:01:08

I now track everything

kulminaator13:01:27

if it turns out to be the slowpoint you'll figure out what to optimize

awb9913:01:29

But I still miss about 50% of the total time.

kulminaator13:01:58

how many rows do you have in those 500kb ? and how many columns ?

awb9913:01:53

sorry, I could not post to this thread my performance snippet.

awb9913:01:01

What is really bizarre that the slurping is so fast,

awb9913:01:11

and the parsing so slow.

awb9913:01:27

It is order of magnitudes opposite how it should be.

awb9913:01:23

this is the source

kulminaator13:01:31

i tried this over here

kulminaator13:01:48

generating a csv file with

for((i=0; i < 25000; i++)); do echo "`date -Iseconds`,$i.25,\"anytext\""; done > somecsv.csv

kulminaator13:01:37

and then tried this code to read the result:

(defn do-parsing[]
  (let [data (slurp "somecsv.csv")]
    (last (csv/read-csv data))))

(defn time-parsing []
  (time (do-parsing)))

; (time-parsing)

kulminaator13:01:00

indeed seems like feeding it into the read-csv added 200ms of execution time

awb9913:01:47

bizarre, isnt it?

awb9913:01:03

I thought that perhaps the clojure csv library is somehow not performance optimized,

awb9913:01:08

So this is why I tried incanter.

awb9913:01:11

But incanter is even slower.

kulminaator13:01:33

i would rather look for a pure java csv parser and wrap it into something comfy

kulminaator13:01:49

it looks to me that already splitting the lines apart by linebreaks is slower than slurping it from the disk

kulminaator13:01:37

i tried to parallelize the work

(defn do-parallel-parsing[]
  (let [data (slurp "somecsv.csv")
        rows (clojure.string/split-lines data)
        parsed-rows (pmap csv/read-csv rows)]
    (last parsed-rows)))

(defn time-parallel-parsing []
  (time (do-parallel-parsing)))

; (time-parallel-parsing)

kulminaator13:01:49

and that was a bit faster but still slow by all means 🙂

kulminaator13:01:17

and had a chance of introducing bugs due to linebreaks within cell values

awb9913:01:40

perhaps nobody every uses csv files in clojure

awb9913:01:45

so this might be the reason.

kulminaator13:01:57

but even the split lines here took the execution time to 50ms already before any csv action

awb9913:01:45

line splitting can be a performance cost,

kulminaator13:01:46

people use csv for sure 🙂 but yeah probably they don't go after the milliseconds but have more slowness somewhere else

awb9913:01:48

if it is not done correctly.

awb9913:01:03

because one has to create thousands of sub strings

awb9913:01:07

and this is expensive.

awb9913:01:30

my thinking is,

awb9913:01:44

that csv parsing should be a fraction of the time it takes to read it from disk.

awb9913:01:57

I have written a library in c# for .net

awb9913:01:01

that had that performance.

awb9913:01:08

so something is really wrong here.

awb9913:01:12

But I have no clue what.

awb9913:01:38

I am too much novice of clojure to find that out.

kulminaator13:01:31

i tried to use java's own string splitting .. yes it's in a class of it's own

awb9913:01:54

When I wrote my c# csv parser,

kulminaator13:01:57

(defn do-parallel-parsing[]
  (let [data (slurp "somecsv.csv")
        rows (into [] (.split data "\n"))
        parsed-rows (pmap csv/read-csv rows)]
    (last parsed-rows)))

kulminaator13:01:03

just like that

awb9913:01:05

I bascically created my own string class,

kulminaator13:01:14

my example went from 100ms to 50-60 range

awb9913:01:14

that allowed you to take sub-strings.

awb9913:01:31

so I have avoided all the senseless copying of strings.

awb9913:01:34

and in the parsing,

kulminaator13:01:40

but now my son is requesting my attention ... good luck (and yeah. get an efficient java lib for the heavy loading)

awb9913:01:41

I wrote my own parser,

awb9913:01:50

by reading from my own datastructure

awb9914:01:04

Thanks a lot kulminaator!

awb9914:01:14

Happy new year!

kulminaator17:01:26

btw. after giving the jvm a good chance at jit compiling the whole thing i reverted the code pretty much to original

kulminaator17:01:03

looks like the repl is just getting into my way here 🙂

kulminaator17:01:12

with code like this

(ns silly-csv-parser.core
  (:require [clojure.data.csv :as csv])
  (:gen-class))

(defn do-parsing[]
  (let [data (slurp "somecsv.csv")]
    (last (csv/read-csv data))))

(defn time-parsing [_]
  (time (do-parsing)))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (mapv time-parsing (range 1 25)))

kulminaator17:01:30

running the (time (do-parsing)) from the repl was always like 200ms

kulminaator17:01:42

however , lein uberjar and then running that jar was a wastly different result

kulminaator17:01:17

$ lein uberjar
Compiling silly-csv-parser.core
$ java -jar target/uberjar/silly-csv-parser-0.1.0-SNAPSHOT-standalone.jar 
"Elapsed time: 301.065242 msecs"
"Elapsed time: 66.629456 msecs"
"Elapsed time: 112.770513 msecs"
"Elapsed time: 46.826564 msecs"
"Elapsed time: 54.771694 msecs"
"Elapsed time: 47.163652 msecs"
"Elapsed time: 45.761238 msecs"
"Elapsed time: 46.835782 msecs"
"Elapsed time: 49.504536 msecs"
"Elapsed time: 37.032187 msecs"
"Elapsed time: 37.907508 msecs"
"Elapsed time: 44.113467 msecs"
"Elapsed time: 34.527459 msecs"
"Elapsed time: 34.381777 msecs"
"Elapsed time: 35.217125 msecs"
"Elapsed time: 34.771945 msecs"
"Elapsed time: 40.187299 msecs"
"Elapsed time: 34.952462 msecs"
"Elapsed time: 35.637608 msecs"
"Elapsed time: 34.520076 msecs"
"Elapsed time: 35.482058 msecs"
"Elapsed time: 34.636292 msecs"
"Elapsed time: 35.011732 msecs"
"Elapsed time: 35.886221 msecs"

kulminaator17:01:50

you can see pretty well where the jit and proper memory management seem to kick in 🙂

awb9907:01:11

I thought that the REPL compiles the same way how it would be compiled to a JAR. I don't get it...

awb9907:01:05

But thank you so much! I honestly would never have believed that such great differences possibly could exist between repl and jar!! 👍❤️

awb9913:01:08

5000 rows, 6 cols

hmaurer13:01:58

Hi! I am in a bit of a pickle. I would like to use generative testing as part of my test suit and to this end wrote the following test:

(deftest generative
  (is (successful? (stest/check `term->ast))))
When running this, however, I get the following error:
Exception: java.util.concurrent.ExecutionException: Syntax error compiling at (clojure/test/check/clojure_test.cljc:95:1).
...
This only happens if the assertion fails; if it passes everything is fine. Any idea why?

seancorfield17:01:05

That exception comes from defspec in clojure.test.check.clojure-test -- which doesn't match the code you shared @U5ZAJ15P0

hmaurer19:01:15

@seancorfield I am most definitely running deftest; I just tried it again after clearing the cache :thinking_face:

seancorfield19:01:53

What is the rest of the stack trace?

hmaurer19:01:51

perhaps the fact that I am using kaocha as a test runner is the issue :thinking_face:

hmaurer19:01:50

nah it’s not; I am getting the same error with vanilla lein test

seancorfield20:01:11

There is a lot of machinery in that stack trace beyond clojure.test and I see you're also using respeced.test$successful_QMARK_.invokeStatic (test.cljc:104) which seems to be what is actually leading to the error here...

hmaurer20:01:54

@seancorfield problem solved in the end; it was an issue with lein test monkey-patching clojure.test :face_with_rolling_eyes:

seancorfield20:01:36

It's not compatible with respeced?

hmaurer20:01:07

@seancorfield https://dev.clojure.org/jira/browse/TCHECK-113 if you are interested (thanks to @U04V15CAJ for finding this). The issue is tangential to respeced.

hmaurer20:01:35

Also test.check v0.10.0-alpha3 doesn’t seem to have the issue

borkdude20:01:05

respeced has nothing to do with it, it’s a clojure.test.check + lein issue

hmaurer20:01:20

ah yes, tangential wasn’t the right choice of word; I meant they were unrelated 🙂

borkdude20:01:01

well, they are related, so I get it. but the problem is really with lein’s monkey patching I guess

seancorfield20:01:03

@U04V15CAJ Cool. Was just curious when I saw it in the stacktrace -- I hadn't looked at the code.

hmaurer20:01:35

@U04V15CAJ do you use test.check v`0.10.0` yourself?

borkdude20:01:53

no, but I haven’t used lein for testing for a long while

seancorfield20:01:28

Lein's monkey-patching has caused problems with other tooling in the past (and, lately, ~/.lein/profiles.clj seems to be the bane of every beginner's life).

seancorfield20:01:03

(also haven't used Leiningen for a long time -- switched to Boot three years ago and switched to clj/`deps.edn` this year)

borkdude20:01:33

I’m going along the same path. For work we’re using boot still, but for newer personal projects I tend to choose TADA

seancorfield20:01:30

We were very heavy Boot users until this year. We just started having too many problems with it 😞

borkdude20:01:42

being able to use git and local deps really makes a difference

borkdude20:01:26

we don’t have many problems with it at work, so no need to migrate for now.

hmaurer20:01:13

@seancorfield I wanted to use Boot but there was a nice template project for Fulcro + shadow-cljs with lein

hmaurer21:01:54

@seancorfield what do you use now? deps.edn?

seancorfield21:01:32

See 14 minutes ago in this thread 🙂 "(also haven't used Leiningen for a long time -- switched to Boot three years ago and switched to clj/deps.edn this year)"

seancorfield21:01:46

So we run tests with Cognitect's test-runner

borkdude21:01:02

I use that one also, if I’m not using my own test runner script.

Kari Marttila14:01:15

I really love Clojure and Clojure REPL is the most productive environment I have ever used. Now I encountered one puzzle with Clojure REPL that I can't figure out how to do it. I needed to create a couple of gen-classes for a Java API I'm using via Clojure/Java interop. Before those gen-classes using Clojure REPL was a breeze. If I wanted to refresh all namespaces in Clojure REPL I just called function (do (require '[clojure.tools.namespace.repl :refer [refresh]]) (refresh)) ... in Clojure REPL. But after implementing those gen-classes I get error: "namespace 'mygenclass' not found after loading 'mygenclass". I was wondering if there is some standard procedure or best practice with gen-classes I have missed?

phill15:01:04

@kari.marttila By the way, which REPL are you using? Are you using :gen-class in the ns declaration or a free-standing (gen-class)? Anyway, I do not know c.t.n.repl, but I sometimes see that error message, in Cider, after trying to reload a ns that has errors in it OR trying to reload a ns whose name does not correspond to its filename.

Kari Marttila15:01:25

I'm using Cursive REPL with IntelliJ IDEA. I really love Cursive. I have configured all kinds of hotkeys for IntelliJ/Cursive and using those hotkeys I can jump back and forth between editor and REPL, send S-tokens from editor to REPL etc. I really love it. This refresh issue with gen-class is the only thing that causes a bit of nuisance.

Kari Marttila15:01:25

I'm using :gen-class in the ns declaration. Example in comment thread.

Kari Marttila15:01:42

Example: (ns simpleserver.util.azuregenclass.session (:import (com.microsoft.azure.storage.table TableServiceEntity)) (:gen-class :extends com.microsoft.azure.storage.table.TableServiceEntity :constructors {[] []} :init init :prefix "bean-" :state state)) (defn bean-init ([] ))

schmee16:01:37

IIRC, this is related to https://dev.clojure.org/jira/browse/CLJ-2343, and would be fixed if that patch was applied

schmee16:01:47

Nicola would know for sure

Kari Marttila17:01:02

Ok. Thanks for info. Let's hope that patch will fix this issue some day. 🙂

viesti18:01:29

went and upvoted the issue, since I happened to stumble on genclassing lately too

viesti18:01:44

more upvotes might help 🙂

Kari Marttila18:01:02

I signed in to Clojure JIRA and upvoted as well. 🙂

rutledgepaulv19:01:35

could anyone provide some pointers on a macro I’m trying to write? I want a function or macro that I can call with a map of let bindings (symbols to values) and then execute some form in the body of that let. I have it working for some cases but not all.

hiredman19:01:15

you cannot do that in the general case

hiredman19:01:02

you are trying to build a structure at compile time that depends on a value at runtime

rutledgepaulv19:01:59

hm is there no way to achieve the same goal? eval some code with different values for particular symbols as supplied by some map?

hiredman19:01:04

you're existing implementation is also broken for the case were it "works" because it ends up double evaling

hiredman19:01:22

what you are describing is a function

hiredman19:01:44

code parameterized by different arguments

rutledgepaulv19:01:16

lol. yeah in abstract I guess. in this case I’m taking code as input and want to replace the meaning of some things using data. Should I just be walking the form and making replacements instead of trying to keep the same symbols but assign different values than what they’d otherwise resolve to?

rutledgepaulv19:01:34

I guess maybe that’s better

hiredman19:01:17

https://github.com/ztellman/riddley is a library that might be helpful (I've never used it, I would be more inclined to write an interpreter for a dsl then to try and transform clojure expressions and call eval on them)

rutledgepaulv19:01:08

thanks, I’ll take a look