Fork me on GitHub

That sounds like an unpleasant API to me -- why not make the API case insensitive (by, say, converting all the parameter names to lower case).


well, the problem is that pedestal will get the key value pairs and then create a map for me, in my situation I would have to do something like foreach (keyword ( lower-case ( name ( item and that just seems so much processing for really not much use


Middleware would do it all in one place and you'd have lowercase keys by the time anything reached your code.


(I think my default stance on this would be to just say "all query/form parameters must be lowercase" and ignore mixed/upper-case stuff -- but if you wanted more flexibility for your clients, be case-insensitive and convert everything to lowercase)


@seancorfield do you mean say it in the documentation?


In pedestal you can define an interceptor that normalizes the param map(s); you can inject that interceptor into the list of interceptors for your service, so that it is available/executed for all routes


Thank you, I’m looking into doing just that… It’s a bit difficult since I am new to Clojure and Pedestal


Let me know if you need any specific help with the pedestal interceptor. If you have a service map set up already, it might be as simple as (update service :io.pedestal.http/interceptors conj <your-interceptor here>)


Can get more help in #pedestal as well


@ariel.silverman Most APIs that I've used seem to document what parameters they accept and also seem to ignore any additional parameters you send them. I can't say how many are case-sensitive vs case-insensitive because I've always gone by the documentation and used the exact same parameter name they provide in the documentation.


Hence my default stance: document exactly what you accept, ignore everything else. Make an API case-insensitive if you want to -- a conscious decision -- but document that, if you do. Another thing to consider is what happens if you repeat a parameter: id=1&id=2 -- which will produce the equivalent of {:id [1 2]} in quite a few web stacks I've used (although some will silently overwrite repeated parameters and just give you {:id 2}.


(and you may be at the mercy of Pedestal's param processing there)


@seancorfield thank you very much for your very comprehensive and thoughtful answer :thumbsup: I will take your recommendation then, I will also look into what both you and @dadair said about having an interceptor that makes the params case-insensitive. Have a great weekend both!


@ariel.silverman another thing that I find to be a good idea is: if you want your API to ignore extraneous parameters, look for a ?strict=true parameter that causes your server to return a 400 error on unknown query params. This can make developing an API client significantly easier, since you'll instantly be able to see the mistake you made instead of having to debug why you aren't getting the results you expected. That way if you do want to go the case-sensitive-and-ignore-mismatches route, while developing the client you can have it yell at you if you make a case mistake that wold instead cause a silent error in non-strict mode

👍 4

hey guys, just finished Clojure for the Brave and True, now i'm looking forward for web development with Clojure. I was planning to read now 'Web Development with Clojure 2d edition' . And i've seen people recommending it, or start with compojure and ring and build things on top of those. However my situation is: i have some experience with web development in front-end, have built some simple apps, but never touched at backend stuff. my question is, what would be better to me, start with the book and stopping where needed to grasp new concepts with other material (i assume that this book expects you to have some knowledge on back end and just show how to apply in clojure right?) or just try to mess with ring and compojure by looking for some guides and examples ?


also, seen people recommending reading Clojure Applied before web development with clojure


@cybersapiens97 If you have some frontend experience, I'd say go with "Web Development" and continue to learn the backend stuff as needed (Brave was all backend, right?). I get the impression Clojure Applied is both more advanced and more backend-focused.

💯 4

Is clojure1.10.0-beta4 compatible with Java 9/10/11?

Alex Miller (Clojure team)14:10:47

If you find anything find 1.10.0-beta4 that isn’t, please raise a flag!


Is there any nifty shortcut to change the repl namespace to the current open file in emacs cider?


What is the clojurian alternative to iterators in other languages like rust? Is the closest thing using a transducer?


@adamkowalski Iterators are inherently stateful -- which is counter to how Clojure works really.


Well but I mean more of the concept of being able to iterate over a collection without producing intermediate collections


Like in Rust/Julia/JS you can have map defined so that it doesn’t take a list and produce a list, but instead takes elements one by one, transforms them, and spits them out


In JS/Python they call them generators though


Ah, OK. Well, then transducers are the closest in that respect.


Okay cool, and is that all based on protocols?


I’m wrapping tensorflow js, but I would still like to use all the clojure combinators with them


A transducer is a regular function.


But so when processing dataset for ML you typically need a data pipeline, where you partition your dataset into batches, maybe map over them to scale things down/normalize the vales, and then run them through your model to train it


Can I make map/partition work on new datatypes?


Sequence functions work on anything that is seqable?


Mostly they call seq on their arguments to produce something that is seq? (implements ISeq)


If you want something to be reducible efficiently, you need to implement IReduceInit.


i.e., make the collection able to "reduce itself".


@adamkowalski If you do (source transduce) you can see it will call the internal .reduce method of anything that implements IReduceInit, or else it will use coll-reduce on it (which is based on a protocol you can extend).


Mostly the secret sauce in transducers is that you can compose the transformations, so that everything can be done in a single reduction pass over the collection.


Perfect! Thanks for all the tips, the only thing I’m concerned about is when you say that it must be a seq. Is that just a logical sequence in the sense that you can traverse over the collection? Or is it an actual linked list with the overhead of all the pointer indirection? I should be able to create a defrecord tensor or something and then just provide a next method on it?


Thanks for the tip! One last thing, Is there any way to extend the core +,*,-,/ methods to work on new types? Or would I have to make a new protocol for numeric like things and then shadow the built in operators? I want to be able to multiply matrices by doing ```(* A B)```


I believe for performance reasons those don't have a protocol - you have to shadow. There are examples of doing this in existing matrix math libraries though


Oh I see, is that because protocols are more like abstract base classes? Theres no way to do static polymorphism?


well polymorphism also isn't free and those functions are used liberally in clojure.core iirc


this came up a few weeks ago either here or in #clojure so I'm partly parroting what I remember being discussed then


as for transducers, writing custom transducer functions (which are also defined as sequence functions when for an arity that includes col) isn't too difficult - I suggest looking at (source map) and (source filter) as they make for clean examples of two more common usecases. take is helpful as well since it's an example of a stateful transducer using volatile!


How does the built in + work then? Is it just a macro which at compile time switches the implementation based on the type?


And thanks for the tip, I will look at the sources!


You can also (source +) 😉


stuff that fundamental usually uses java stuff in clojure.lang, in this case clojure.lang.Numbers


hey people, there is any way to transform this function

(defn slugify
      (clojure.string/trim string)) #" " "-"))

(slugify " I will be a url slug   ") ;; "i-will-be-a-url-slug"
...into a comp function? Something like this
(defn slugify
  (comp clojure.string/replace
But the replace function needs the #" " "-" parameters.


replace clojure.string/replace with #(clojure.string/replace % #" " "-")


@dadair I tried it

(defn slugify
  (comp #(clojure.string/replace % #" " "-")

(slugify " I will be a url slug   ") ;; " I will be a url slug   "


You need to call the comp-ified function with the arg


((comp inc inc) 1)


Not (comp inc inc 1)


Your parens are off


You are returning “string” as the last form


Oh thanks!


(defn slugify
  ((comp #(clojure.string/replace % #" " "-")


This works now! 🙂


Hi all! I have a question about atoms and swap!. (I posted to StackOverflow, but a colleague suggested I might get better answers here.) In particular, I'm trying to understand when exactly swap! will call its "updater procedure" argument multiple times. Here's a contrived example:

(def c (clojure.lang.Atom. [nil nil]))
(swap! c 
   (fn [[x y]] 
       ["done", (second (swap! c (fn [[x y]] [x y])))]))
Based on my understanding, this is what should happen when this code is run: 1. Clojure unboxes c to find [nil nil], and passes it to the outer fn. 2. The outer fn calls swap!, which unboxes c to find [nil nil], and passes this value to the inner fn. 3. The inner fn returns [nil nil]. The inner call to swap! swaps this in for the new value of c. 4. The outer fn returns the value ["done" nil]. 5. The outer swap! tries to compare-and-set!, and sees that the current value of c, [nil nil], is the same as the old value, [nil nil], so it succeeds, swapping in ["done" nil]. But the outer swap! actually never succeeds, and keeps trying forever. I know the code above is ugly / should be avoided; I'm just trying to understand what's missing from my mental model. Why does swap! keep trying again?


I've noticed that if I replace (fn [[x y]] [x y]) with identity or (fn [x] x), it works -- so I have a feeling that there's something I'm not seeing about the equality of input & output to (fn [[x y]] [x y]). Maybe compare-and-set! is using a stricter notion of equality than =?


@lew.alexander.k you seem to be confused about a number of things -- 1- (fn [x] x) is the same as identity, and it's the same as (fn [[x y]] [x y]) for 2-element vectors (which is your case) 2- swap! performs a compare-and-swap! in a loop, and keeps retrying until it succeeds. Since you're mutating c within every swap!, that'll never happen and swap! will keep retrying -- the docstring of swap! is explicit about f having to be side-effect free exactly for this reason


Thanks @bronsa! Two follow-ups: 1. Empirically, changing out (fn [x] x) does lead to a different result in this case. (The compare-and-set! succeeds, and the entire call returns ["done" nil] as I'd expect.) This is what confused me: I thought identity and (fn [x] x) and (fn [[x y]] [x y]) should all behave identically. Any idea why they wouldn't? 2. I guess I am wondering what counts as "mutating c". c is being swap!d to the same value as it had before -- is that mutation? If I explicitly write (fn [[x y]] [nil nil]) instead of (fn [[x y]] [x y]) in the inner swap!, the compare-and-set! succeeds. I am confused as to why one of these counts as mutation and the other doesn't.


@lew.alexander.k it shouldn't - it's implemented in java but I believe it's going to be just looking at object equality - IE the same as identical?. So I would expect the actual identity function or (fn [x] x) to work, but not [nil nil] or [[x y]] [x y]


hmm but it does work


@lew.alexander.k ohh I get it, it's going through 2 loops - it loops twice if you use [nil nil] - it's the outer loop that repeats after the inner loop sets the value. But on the second repetition of the inner loop, something in the engine is probably using the fact that you used a literal to inline and save a reference to it to save work, which is making identical? pass. It will fail again if you replace [nil nil] with (vector nil nil).