This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2018-01-24
Channels
- # arachne (3)
- # beginners (39)
- # boot (3)
- # cider (91)
- # cljs-dev (56)
- # cljsrn (4)
- # clojure (267)
- # clojure-dusseldorf (1)
- # clojure-estonia (1)
- # clojure-greece (2)
- # clojure-italy (6)
- # clojure-nl (2)
- # clojure-russia (18)
- # clojure-spec (27)
- # clojure-uk (136)
- # clojurescript (19)
- # core-async (2)
- # cursive (6)
- # datomic (17)
- # emacs (2)
- # fulcro (86)
- # graphql (4)
- # hoplon (13)
- # jobs-discuss (7)
- # jobs-rus (1)
- # keechma (34)
- # keyboards (7)
- # leiningen (5)
- # luminus (4)
- # lumo (8)
- # off-topic (13)
- # om (6)
- # onyx (26)
- # re-frame (22)
- # reagent (1)
- # reitit (2)
- # remote-jobs (8)
- # ring (3)
- # ring-swagger (5)
- # rum (8)
- # shadow-cljs (45)
- # specter (6)
- # unrepl (16)
- # yada (15)
any particular reason to use (int-array 1) instead of (int-array 0)? also I'd be tempted to bind the type of that in a def at the top level and just use the def
no, merely an irrational fear that (int-array 0) or (float-array 0) may be optimized away
you don't need the array, just the type
okay, so we have the following, which is great:
(do "** iknsn"
(def class-data
(let [ia (type (int-array 0))
fa (type (float-array 0))]
{:name 'my.pkg.Tensor
:fields [{:flags #{:public :static}, :name "VALUE", :type :int, :value 4}
{:flags #{:public :static}, :name "dims", :type :int, :value 0}
{:flags #{:public :static}, :name "shape", :type ia }
{:flags #{:public :static}, :name "offsets", :type ia }
{:flags #{:public :static}, :name "data", :type ia }]
:methods [{:flags #{:public}, :name "add", :desc [:int :int]
:emit [[:getstatic :this "VALUE" :int]
[:iload 1]
[:iadd]
[:ireturn]]}]
}))
(def result (insn/visit class-data))
(def class-object (insn/define class-data))
(-> class-object .newInstance (.add 17)))
(comment
21)
@qqq also, back-referencing the convo about specifying stack usage - remember that in the jvm you can't put variable size things (even arrays) in the stack itself, so calculating stack usage is trivial
you might need it if you are using a vm / architecture where you can put arbitrary data on the stack but the jvm isn't that flexible so you can let it calculate automatically
also, apparently the entikre operand stack is imaginary and gets compiled down to registers
well surely that's architecture specific?
(or I guess it could be every architecture that's worth paying attention to is register based so the distinction is academic?)
I haven't looked at JVM implementations in detail, but I think "operand stack is imaginary" might be overstating it. I would suspect that for bytecode that is actually interpreted (which by default in the Sun/Oracle JVM, all of it is until it decides to JIT some classes/methods into native machine instructions), it does actually have some explicit representation of the operand stack.
Is
currently down? I’m getting a 504 Gateway time-out
just browsing the URL to look something up.
I see the http://clojars.org home page from where I am. I had a bit of momentary confusion and a chuckle when I saw this large text near the top of the home page, wondering whether it was down: "Clojars is a dead easy community repository ..." (My brain first register the words "Clojars is dead")
I have a Java class (not a *.class file, but something in memory on the JVM). I want to disassemble it and see the raw jvm bytecode associated with this class. What library should I use ?
@qqq not sure you can, I've never seen one at least in my (too) many years of Java.
no.disassemble can disassemble the bytecode for a function to symbolic instructions, if it can do that I assume there's something that can do the same for a regular class
if you really mean the bytes and not symbolic instructions, surely you could just output the bytes of the class? (I assume there's some way to access that)
OK - yeah, no.disassemble would be your guy for that - and seeing what it does might be informative too https://github.com/gtrak/no.disassemble
are you using it as a plugin? it needs to inject stuff and replace a bunch of clojure stuff iirc
there's probably a trick to use it without lein, not sure how that works though
I completely ignore the docs that says:
HOWEVER, don't use it this way, let lein-nodissassemble's project middleware inject it for you.
{:plugins [[lein-nodisassemble "0.1.3"]]}
yeah - it's meta, it needs to change how clojure generates bytecode in order to work properly, which means it needs to change some stuff on startup (I'm fuzzy on the details but you've discovered the failure mode)
if it's changing the way clojure generates bytecode -- does it mean it won'g work with insn
interesting question, I wonder - you might have more luck with dumping the class with an ObjectOutputStream and examining that (or feeding it to the eclipse disassembler or whatever)
no.disassemble uses the eclipse disassembler API, but they also have a higher level tool inside the IDE - I assume IntelliJ IDea has something similar
cloc insn/src/
7 text files.
7 unique files.
0 files ignored.
v 1.72 T=0.03 s (223.7 files/s, 39525.8 lines/s)
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
Clojure 7 147 10 1080
-------------------------------------------------------------------------------
SUM: 7 147 10 1080
-------------------------------------------------------------------------------
only 1080 lines, is jgpc42
here on slack ?surely you can open some bytes in a program other than your primary editor for debugging purposes?
sure, but switching boot -> lein is going to be a pain, especially since emacs = running on laptop, boot = running on a remote machine exoosing a nrepl
emacs = running on laptop, boot = running on a remote machine exoosing a nrepl
That doesn't even make sense
But the gist is this: in order to disassembly JVM bytecode you need a instrumentation agent, basically a JVM plugin. That requires special command line options. Not a big problem
That's pretty much the same in lein vs boot
yes, but I'd have to port over my build.boot to whatever lein uses to make things work, and I've found it very difficult to get lein to do anything besides tweak a few configs
the command line option will be something like -agent:some-jar.jar
@tbaldridge: I'm confused, are you now talking about getting no.disasemble to work with boot ?
Either way is a valid option: switch to lein or use the -agent
stuff
That's all the no.disasemble plugin is doing: injecting the -agent
command line option
BOOT_JVM_OPTIONS="-javaagent:whatever.jar" boot repl
(is it -agent
or -javaagent
?)
(or put it in boot.properties
-- I can't remember if you can have a project-local .properties
file?)
Reasoning backward with 2020 hindsight, in theory, one could have looked up lein-nodisassemble, poked around the src files, saw https://github.com/gtrak/no.disassemble/blob/master/lein-nodisassemble/src/lein_nodisassemble/plugin.clj#L20 ... and then reailze "okay, all I have to do is add the -javaagent , and BAM")
The more I work with Boot vs Leiningen, the more I feel the latter used to obfuscate a lot of things that really could have been much simpler...
...and that was partly why we switched: writing plugins is a lot more work than just doing the "obvious" thing in Clojure code.
The downside to Boot is the lack of declarative support for tooling (particularly Cursive), but maybe adopting deps.edn
will help resolve that (my boot-tools-deps
is a work in progress since tools.deps
keeps changing!).
I'm trying to define a constructor "Tensor". First attempt shows I define a public member function Tensor (but not a constructor). Second example shows error when I try to name it my.dyn.Tensor Question: how do I begin debugging this? I'm not sure where to look.
;; we try to define constructor Tensor, and get outpt
(do "** insn"
(def class-data
(let [fa (type (float-array 0))]
{:name 'my.dyn.Tensor
:fields [{:flags #{:public :static}, :name "VALUE", :type :int, :value 4}
{:flags #{:public :static}, :name "data", :type fa }]
:methods [{:flags #{:public}, :name "Tensor", :desc [:int :int]
:emit [[:iload 1] [:ireturn]] }]}))
(def result (insn/visit class-data))
(def class-object (insn/define class-data))
(cr/reflect class-object))
(comment
{:bases #{java.lang.Object},
:flags #{:public :final},
:members
#{{:name my.dyn.Tensor,
:declaring-class my.dyn.Tensor,
:parameter-types [],
:exception-types [],
:flags #{:public}}
{:name VALUE,
:type int,
:declaring-class my.dyn.Tensor,
:flags #{:public :static}}
{:name Tensor,
:return-type int,
:declaring-class my.dyn.Tensor,
:parameter-types [int],
:exception-types [],
:flags #{:public}}
{:name data,
:type float<>,
:declaring-class my.dyn.Tensor,
:flags #{:public :static}}}})
;; okay fine, let us try to rename it my.dyn.Tensor
(do "** insn"
(def class-data
(let [fa (type (float-array 0))]
{:name 'my.dyn.Tensor
:fields [{:flags #{:public :static}, :name "VALUE", :type :int, :value 4}
{:flags #{:public :static}, :name "data", :type fa }]
:methods [{:flags #{:public}, :name "my.dyn.Tensor", :desc [:int :int]
:emit [[:iload 1] [:ireturn]] }]}))
(def result (insn/visit class-data))
(def class-object (insn/define class-data))
(cr/reflect class-object))
(comment
[1;31mjava.lang.ClassFormatError[m: [3mIllegal method name "my.dyn.Tensor" in class my/dyn/Tensor[m
[1;31mclojure.lang.Compiler$CompilerException[m: [3mjava.lang.ClassFormatError: Illegal method name "my.dyn.Tensor" in class my/dyn/Tensor, compiling:(NO_SOURCE_FILE:70:23)[m)
@qqq Remind me, where do I find the insn
project?
I'm grepping through the source for the word "constr" right now, there's a file src/insn/core.clj which seems to contai nall the important bits
For the exception when you try to name the method my.dyn.Tensor
-- I'm not surprised since that's an illegal method name. I'll go read about insn
and see if I can help...
Great! This looks like progress: It appears the correct name is ":init" and I needc to call super or init or something:
;; we try to define constructor Tensor, and get outpt
(do "** insn"
(def class-data
(let [fa (type (float-array 0))]
{:name 'my.dyn.Tensor
:fields [{:flags #{:public :static}, :name "VALUE", :type :int, :value 4}
{:flags #{:public :static}, :name "data", :type fa }]
:methods [{:flags #{:public},
:name :init
:desc [:int :int],
:emit [[:return]] }]}))
(def result (insn/visit class-data))
(def class-object (insn/define class-data))
(cr/reflect class-object))
(comment
[1;31mjava.lang.VerifyError[m: [3mConstructor must call super() or this() before return
Exception Details:
Location:
my/dyn/Tensor.<init>(II)V @0: return
Reason:
Error exists in the bytecode
Bytecode:
0x0000000: b1
[m)
Yup, was just about to say :init
is the constructor and :clinit
is the class initializer (`static` stuff I assume)
Here's an example in the test suite https://github.com/jgpc42/insn/blob/121b0ec8ecd98523daa8f068d4684446b2c5c93c/test/insn/core_test.clj#L45
[:invokespecial :super :init [:void]]
That test file provides a lot of insight into the stuff you can do ... it looks really cool!
I looked through all the test files ... twice ... I just didn't draw the mental connection of :init == constructor 🙂
Ironically, I anticipated it because in CFML, when you construct a Java object, you call init()
to invoke the constructor...
Yes, and the components -- classes -- in CFML have a method called init()
as their constructor:
component {
function init( arg ) {
variables.arg = arg;
return this;
}
}
and then var obj = new MyComponent( 42 );
while you'r ehere: clinit = stuff for initializing STATIS MEMBERS of class? init = stuff for initializing non-static members of object/class when created? so clinit = called once per class; init = called once per new object created ?
Yup, sounds right.
Doesn't Java have a static { ... }
code block these days for class-level initialization?
It has always had that
It also has { ... }
for instance-level initialization, although that’s rarely used
"The static initializer block is a very interesting item in Java that unknown by most of the Java novice community; it is glossed over in Java books but none of the books really go into any sort of dept." [sic] -- http://www.engfers.com/code/static-initializer-block/
(it's been so long since I wrote Java)
dunno, you're talking to someone that would rather use clojure to generate jvm bytecode than write java 🙂
"The static initializer block is a very interesting item in Java that unknown by most of the Java novice community; it is glossed over in Java books but none of the books really go into any sort of dept." [sic] -- http://www.engfers.com/code/static-initializer-block/
I've only started to see that in code recently so I assumed it was relatively new -- TIL!
I can't recall ever seeing instance initialization blocks (although they've been there since Java 1.1 it seems).
Most people just use field initializers or constructors but there are some concurrency cases where they come in handy
you probably saw it without noticing, since it's sometimes disguised as "double brace initialization"
Well that’s an abomination, esp if you know Clojure
regarding my problem from yesterday, it seems that cider-nrepl 0.17.0-SNAPSHOT is broken, I had to go back to 0.16.0
Quoting wikipedia:
The JVM operates on primitive values (integers and floating-point numbers) and references. The JVM is fundamentally a 32-bit machine. long and double types,
How is this possible? If refs are only 32-bits, how can JVM address so much memory?Pointer compression
https://docs.oracle.com/javase/7/docs/technotes/guides/vm/performance-enhancements-7.html
@qqq ^^
And refs can be 64 bits in some jvm modes
in other words: pointer compression is nifty but not actually needed to reconcile the statement @qqq’s quote from wikipedia. you could do 64bit pointers in the jvm.
For local varaibles, int, float take up 1 cell, long, double take up 2 cells; object references, must somewhere in spec, be stated how many cells they take up
For whatever reason, I can't dig this up in https://docs.oracle.com/javase/specs/jvms/se8/html/index.html
it’s been a while since i dug into the jvm spec, but what my hazy memory from 15 years ago is that it is not defined because you can’t actually look at a pointer in the jvm or observe its properties (except when you interface with native code). but maybe i’m misremembering.
the whole point of the jvm was statically verifiable type safety (which is also why it is stack based instead of register based). that’s what i was half remembering. but i could be wrong.
@qqq I’m not sure that’s specified. On 64-bit arch, if you have compressed OOPS on an object reference is 32 bits, otherwise it’s 64.
the JVM needs to figure out how much space for the 'locals' of a class/function, so surely there must be a mapping of "a pointer/reference is X bytes" right? if not, I'm misunderstanding something fundamental
Well, I guess the size is fixed at startup based on JVM args, but it’s not constant across architectures or JVM flag combos.
iload/istore are BYTECODE, so it must be determined at the ".java -> .class" compilation process right?
My memory on this is hazy, but aren’t they slot numbers, where the slot size can vary?
if they're just 'slot numbers', why does this happen
int a; // slot 0
long b; // spans 1 & 2, but iload/istore 1
int c; // iload/istore 3
sorry if I seem too agressive, attacking the mental model, not attacking the person 🙂 // been trying to figure this out for a while now
my mental model is: "if int/float are 1 slolt, and long/double are 2 slots", then 1 slot has to be 32 bits
and if all of this is determined at ".java -> .class" compile time, then this has to be somewhere in the specs
> At any point in time, an operand stack has an associated depth, where a value of type long or double contributes two units to the depth and a value of any other type contributes one unit.
this might help too: https://stackoverflow.com/questions/39226918/why-each-slot-of-the-local-variable-array-in-a-stack-frame-is-of-4-bytes-and-no
According to that, an object reference is one unit, but I don’t know how that works.
> The number of method parameters is limited to 255 by the definition of a method descriptor (§4.3.3), where the limit includes one unit for this in the case of instance or interface method invocations.
but surely, there are JVMs that have more than 2^32 = 4,000,000,000 objects? 🙂 there are azul system machines with TBs of RAM right?
> A single local variable can hold a value of type boolean, byte, char, short, int, float, reference, or returnAddress. A pair of local variables can hold a value of type long or double.
A single local variable can hold a value of type boolean, byte, char, short, int, float, reference, or returnAddress.
3. This raises more questions, i.e. what if > 2^32 objects, but that can be resolved another day.
I have this code
(defmulti address-type (fn [a] (:address/use-delivery-address a)))
(defmethod address-type true [_]
"true!")
(defmethod address-type false [_]
"false!")
(address-type {:address/use-delivery-address false}) ;returns "false!" as expected
which works as expected. If I want to change how my “defmulti” works for this type by evaluating it again, it doesn’t seem to “overwrite” it.
(defmulti address-type (fn [a] (:address/something-else a)))
(address-type {:address/something-else false}) ; expect to still retunr "false!" but instead it throws an exception
WHY? 🙂you need to recompile all the forms that make address-type
, @karl.jakob.lind; you’re defmulti and defmethod are actually mutative calls to an in-memory dispatch database
e.g. call (methods address-type)
to query that database
a handy trick to resolve this sort of thing is to provide a default defmethod impl that logs its args; then you can inspect dispatch failures by seeing the actual data it’s inspecting
the specific problem here is that defmulti has defonce semantics - you need to explicitly destroy the old multimethod before the new definition will compile
I don’t know if you resolved this, or if you know this already, but in order to redefine a defmethod
it doesn’t suffice to reload the code, you have to destroy the old defmethod. This can be done by destroying the var via ns-unmap
or by turning it into something that isn’t a multimethod eg. (def my-multi nil)
- after that a reload actually works
Ok I am stuck again. the args of the default method didnt help me. it just shows what I expected: the data I send in
I think it’s helpful to remember that as far as Java is concerned, a Jar does not contain file objects. A file is strictly and specifically something you access via OS filesystem APIs, and is a specific instance of a thing you can read from given a path or name (URI). A URI can be a thing inside a jar, a URL indicating something you access via HTTP, an object served via FTP, or a File on disk.
@doglooksgood maybe https://github.com/JoelSanchez/ventas/blob/master/src/clj/ventas/utils/jar.clj will help (and the link to stackoverflow there)
@joelsanchez that works, thanks again.
has anyone come across a clojure library to handle barfing / slurping of s-expressions?
or a similar library to group infix boolean logic like so: (A and B or C and D)
=> (or (and A B) (and C D))
How do I define reader conditionals inside a macro? Say I have a macro that I use from both clj and cljs and want the macro to compile to js/Error
in cljs and Exception
in clj.
(do "** breaks"
(def class-data
(let [ia (type (int-array 0))]
{:name 'my.dyn.Tensor
:fields [{:flags #{:public :static}, :name "shape", :type ia } ]
:methods [{:flags #{:public}, :name :init, :desc [ia],
:emit [[:aload 0]
[:invokespecial :super :init [:void]]
[:return]]}]}))
(def class-object (insn/define class-data))
(.newInstance class-object (int-array [1 2 3])))
(comment
[1;31mjava.lang.IllegalArgumentException[m: [3mNo matching method found: newInstance for class java.lang.Class[m)
(do "** works"
(def class-data
(let [ia (type (int-array 0))]
{:name 'my.dyn.Tensor
:fields [{:flags #{:public :static}, :name "shape", :type ia } ]
:methods [{:flags #{:public}, :name :init, :desc [],
:emit [[:aload 0]
[:invokespecial :super :init [:void]]
[:return]]}]}))
(def class-object (insn/define class-data))
(.newInstance class-object ))
How do I debug this? The main difference is: :desc [ia]
vs :desc []
@qqq does this help? https://stackoverflow.com/a/3574228/727666
Is there an official channel for zprint help requests? I'd rather avoid creating noise issue tickets in github for what's likely clarification rather than actual issues.
Cool. Well, there are a couple of things I'd like to format differently, in particular ns
declarations (which I like to do in the style Stuart Sierra outlined here: https://stuartsierra.com/2016/clojure-how-to-ns.html), and protocols. I haven't figured out a way to get these to format as I'd like without stomping on the other function formats. I think if there were more :list
options available I could figure out a way to do it.
I try not to get into bikeshedding territory, but code format is pretty much by definition bikeshedding.
And hacking into the internals of zprint might be yakshedding, a term a friend of mine recently introduced me to.
issues will appear in google results though. it may actually benefit more people to be on github
Looking for some opinions. We have an internal framework that we use to build our Component system; it's an EDN file that establishes what components to build (as fully qualified function names, typically for map->Record constructor functions) plus configuration and references. We hope to open source it in the near future. This approach has a lot of benefits, but one drawback; we end up with a lot of long, namespaced symbols. But because data is data, we have the option to use shorter symbols in the EDN file, but expand them to fully qualified symbols before passing them to the library to be instantiated. I came up with an approach based on EDN readers:
:my-service {:sc/create-fn #g auth/AuthService}
is expanded by the reader into the desired output:
:my-service {:sc/create-fn com.walmarts.example.auth/map->AuthService}
A co-worker has another option, where we use an alternative key instead:
:my-service {:ex/create-fn auth/AuthService}
... and we scan for :ex/create-fn and replace with the :sc/create-fn key, and the fully qualified function name.
I'm wavering between the two solutions. The EDN reader solution involves settting up the readers before reading the EDN, but then the rest of the pipeline is the same. The co-worker's solution involves reading normally, then finding and converting the keys before continuing with the rest of the pipeline.
Thoughts?@hlship another option would be to somehow tie into the reader. What you're talking about here is a lot like what happens with keywords and the reader.
clojure
(require '[clojure.core :as c])
::c/+
the c/
gets expanded at read-time into clojure.core
I'd be tempted to turn the symbols into keywords, and add a hook for referring new namespaces
One thing that makes me hesitate with both of your suggestions is that they require custom logic for interpreting symbols. Logic that already exists in Clojure.
To be clear, this is an .edn
file, that's read in as data, then used to build a Component system.
actually a backtick would work for symbols as well
{:sc/create-fn `auth/AuthService}
(clojure.edn/read-string "foo")
=> foo
(clojure.edn/read-string "`foo")
java.lang.RuntimeException: Invalid leading character: `
Ah I see
Typically, some or all of the EDN files (which are deep merged together) will have runtime data from a container, or just be specific to the deployment environment (qa vs prod, etc.).
So that's the next question, does it "have" to be EDN? Something I haven't rectified in my own mind yet is why/when .edn is prefered over .clj files. Clojure code being data and all that.
As time progresses these EDN based data DSLs take on more and more logic, until they (poorly) implement a subset of Clojure 🙂
One of the things I like about this approach is that initial REPL startup is faster: we're loading a lot less code; in our main namespace; most code gets loaded by the library when it resolves those fully qualified symbols.
But at any rate, I like the #g
approach the best of your two solutions because that cleanly abstracts all the symbol interpretation logic into one place in the code.
@hlship sure, but nothing stops you from calling (load-file "my-config.clj")
from within your app.
Or some eval based solution.
Nothing requires the use of Component either, until you try to build an effective development work cycle without it. 🙂
Right, but what I'm saying is what's the use-case for EDN here? If you're looking for external configuration of the system, you can do that with runtime loading a .clj file, and you won't artificially restrict the power of your configurations.
@hlship https://github.com/weavejester/integrant might be helpful
I've worked on several systems with EDN based configs like this and there's always confusion about what works and what doesn't and how you programmatically create the configs. All this goes away if you have Clojure available in the configuration language.
One thing to be cautious about there, of course, is that as soon as you use the Clojure reader rather than the EDN reader, you may open yourself up to arbitrary code execution. Sharp-equals club, I think it has been called? See notes on last example here (more for benefit of those new to this than for you @tbaldridge) http://clojuredocs.org/clojure.core/read
True, but how often do you read arbitrary configs from the outside world?
This is no different than saying that the JVM's ability to load .jar files is exposure arbitrary code execution.
Our library takes ideas of integrant, and elsewhere, but layers on top of Component, something we're committed to at a large scale.
But the thing I'm working on it standalone, and though I will continue to use Component, the other library could be excised in favor of the code-as-configuration approach.
I think I see this in a similar way to @tbaldridge, all the apps I've ever worked on use some kind of edn map for configuration, and at some point gain some kind of system for merging maps, so you have overlays or inheritance or something else, all of which are ways to move what could be explicit conditionals in code to implicit conditionals in the configuration "runtime"
so after spending some time poking at various configuration libraries and thinking about bootstrapping component, my crazy opinion is use a rules engine for configuration and bootstrapping
@tbaldridge I hear a lot of mantra about data > code, maybe that's why people try to default to that
regarding the original question -- I prefer a symbol for the value, not a tagged value. Once you validate config it's easy to walk all the :sc/create-fn 's and resolve them
When I have complex config needs (that I can't avoid), I tend to make a thing very much like debian conf files except EDN or JSON 20-defaults.conf
this is what I mean, your "simple" configuration system is actually hiding a complex set of conditionals
(no offsense @hlship) but it seems like every organization has their riff on this sort of configuration framework
and pretty much all of them are merging maps, with their own particular set of rules for the merging
almost all of them fail to consider validating input before the app boots up, often leading to a zombie state
I'm guilty of creating the one we use at World Singles. I reviewed a whole bunch of them and none of them did everything we needed.
Immuconf is an interesting approach to overrides at least, but it's still just a map merge.
(Well, I'm guilty of creating two different ones in fact -- the new one is a big improvement over the old one 🙂 )
databases of facts (maps) + rules (strategy for merging maps) = rules engine (configuration system)
(ours has the ability to provide custom value merging functions -- although we don't use it at the moment)
once you start trying to deal with things like "read config value x from the node data on aws or from the environment variable X if not on aws" the map merge stuff is just kind of a joke
I consciously avoid having the config files be sentient about where to get config data. I usually have something (a "driver") that grabs from S3, or a Kubernetes ConfigMap, filesystem path, etc. and gives fully formed data into the "merge" process
funny you should say that, the last config system prototype I wrote looked just like that
but it doesn't pull from everywhere for a given app, usually one or two different places max
sort of a 12-factor or dependency injection lesson - don't give me the recipe for the thing, just gimme the thing
We went fully dynamic. Put it all in Datomic w/ a single edn file that provided the Datomic URI.
(There were particular needs that pushed us that way. Probably not a good starting approach.)
that is the other thing, and some point someone will say "gee, I wish configuration was a dynamic thing we could update on the fly"
Hey guys, whats is the best and most simple webframework to make http apis in clojure?
vase looks nice: https://www.youtube.com/watch?v=_Cf-STRvFy8
You can use duct (https://github.com/duct-framework/duct), a post about it (https://www.booleanknot.com/blog/2017/05/09/advancing-duct.html)
Ring is about as simple as it gets; I like bidi better as a router though, since it doesn’t involve singleton route declarations.
bidi is very nice, used it a bit