Fork me on GitHub
#beginners
<
2018-10-27
>
seancorfield00:10:28

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).

ariel.silverman00:10:36

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

seancorfield00:10:46

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

seancorfield00:10:35

(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)

ariel.silverman00:10:04

@seancorfield do you mean say it in the documentation?

dadair00:10:57

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

ariel.silverman00:10:14

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

dadair00:10:29

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>)

dadair00:10:03

Can get more help in #pedestal as well

seancorfield00:10:50

@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.

seancorfield00:10:57

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}.

seancorfield00:10:53

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

ariel.silverman00:10:54

@seancorfield thank you very much for your very comprehensive and thoughtful answer 👍 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!

jesse.wertheim01:10:05

@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

cybersapiens9704:10:18

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 ?

cybersapiens9704:10:03

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

seancorfield05:10:54

@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.

manas.marthi13:10:24

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

alexmiller14:10:47

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

jarvinenemil17:10:53

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

adamkowalski19:10:43

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

seancorfield19:10:09

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

adamkowalski19:10:50

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

adamkowalski19:10:33

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

adamkowalski19:10:49

In JS/Python they call them generators though

seancorfield19:10:01

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

adamkowalski19:10:24

Okay cool, and is that all based on protocols?

adamkowalski19:10:57

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

seancorfield19:10:58

A transducer is a regular function.

adamkowalski19:10:04

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

adamkowalski19:10:20

Can I make map/partition work on new datatypes?

seancorfield19:10:49

Sequence functions work on anything that is seqable?

seancorfield19:10:23

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

seancorfield19:10:03

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

seancorfield19:10:50

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

seancorfield19:10:46

@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).

seancorfield19:10:52

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.

adamkowalski20:10:44

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?

jesse.wertheim20:10:27

@adamkowalski seqable? and therefore seq check for a whole bunch of things https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/RT.java#L534-L563

adamkowalski20:10:05

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)```

jesse.wertheim20:10:53

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

adamkowalski20:10:48

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

jesse.wertheim20:10:12

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

jesse.wertheim20:10:59

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

jesse.wertheim20:10:23

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!

adamkowalski20:10:42

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

adamkowalski20:10:07

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

jesse.wertheim20:10:19

You can also (source +) 😉

jesse.wertheim20:10:59

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

leandrotk10021:10:29

hey people, there is any way to transform this function

(defn slugify
  [string]
  (clojure.string/replace
    (clojure.string/lower-case
      (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
  [string]
  (comp clojure.string/replace
        clojure.string/lower-case
        clojure.string/trim)
        string)
But the replace function needs the #" " "-" parameters.

dadair22:10:32

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

leandrotk10022:10:55

@dadair I tried it

(defn slugify
  [string]
  (comp #(clojure.string/replace % #" " "-")
        clojure.string/lower-case
        clojure.string/trim)
        string)

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

dadair22:10:42

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

dadair22:10:54

((comp inc inc) 1)

dadair22:10:03

Not (comp inc inc 1)

dadair22:10:03

Your parens are off

dadair22:10:43

You are returning “string” as the last form

leandrotk10022:10:27

(defn slugify
  [string]
  ((comp #(clojure.string/replace % #" " "-")
        clojure.string/lower-case
        clojure.string/trim)
        string))

leandrotk10022:10:31

This works now! 🙂

lew.alexander.k22:10:42

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?

lew.alexander.k23:10:02

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 =?

bronsa23:10:26

@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

lew.alexander.k23:10:35

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.

jesse.wertheim23:10:33

@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]

jesse.wertheim23:10:51

hmm but it does work

jesse.wertheim23:10:10

@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).