Fork me on GitHub
#clojure
<
2017-11-21
>
danielcompton00:11:05

Using bidi and yada you can get this, I suspect bidi with Ring would get you most of the way there too. If I understand the question correctly

delitescere09:11:06

G’day all. Is the closure.algo.generic really the only standard-ish library fmap? That’s astounding! Is there a transducer for it? I like the look of @gerritjvv but might be a bit much stuff for all I need (at the moment)... mind you I’ll probably use ap and lift soon enough.

tbaldridge13:11:07

@delitescere by fmap you mean monads? Then yes, monad usage is quite rare in Clojure except when no other approach will work (for example in test.check).

tbaldridge13:11:57

In general Clojure tends to drive abstractions with data, and data interpreters/compilers instead of directly with functional composition.

delitescere21:11:46

Not a Monad, no. By fmap I mean (at the minimum) to be able to apply a function to the members of something that this can be done to such that it creates another thing that this can be done to whose members are the result of applying that function. This is very much something we’d want to do with abstract data structures. Like a vector or a map. If it happens to follow the rules of being a Functor (which a Monad does, yes), then great.

tbaldridge00:11:24

Isn’t that just map, or perhaps the result of (map f)?

triss14:11:45

hey all. I’ve got a function that provides me with a list of all the possible combinations of values from two lists:

(defn all-combinations
  "Return all possible combinations of values from as and bs collections."
  [as bs] (apply concat (for [a as] (for [b bs] [a b]))))

triss14:11:26

I’d like to do this to a list of lists rather than just a pair of lists.

triss14:11:37

Has anyone got any hints for doing so neatly?

triss14:11:03

Thanks @curlyfry cartesian-product looks just the job

curlyfry14:11:46

Np! A small side note: your original implementation can be simplified to

(defn all-combinations
  "Return all possible combinations of values from as and bs collections."
  [as bs] (for [a as b bs] [a b]))

triss14:11:46

oh lovely! thankyou

djabbat14:11:34

Plz help cteate and use data base. Telegram Bot. Morse dep

timrichardt17:11:22

@djabbat Are you the beginning of the singularity? Morse dep, bro!

itaied17:11:10

Hello, wanted to hear your opinions on honeysql and yesql. I want to build a small web app on top of a RDBMS and I wonder how should I handle the queries. Any thoughts or past experience?

bostonaholic17:11:51

I prefer #hugsql

bostonaholic17:11:13

you can also ask in #sql

josh.freckleton20:11:14

i have a long list of let bindings where a conditional could branch an error at any time options: 1. I have many-nested (let [...] (cond ... (let [...] (cond ... 2. would it be idiomatic to build the let "context" as a record getting passed down a (cond->? 3. something else?

josh.freckleton20:11:59

actually, step 2 doesn't seem to work like I want

josh.freckleton20:11:41

basically, I'd just like a long list of let bindings, that depend on things before them, but if I have a conditional branch during that process, I can "short circuit" the whole thing

Garrett Hopper20:11:33

Is there a way for a deftype to extend Atom, or do I need to have an atom in its parameters and implement the lang.IAtom interface?

Garrett Hopper20:11:13

I assume I should do that instead of manually using mutable fields? (using set!?)

phronmophobic20:11:40

@ghopper, there are some cases where manually setting mutable fields might be more straightforward (eg. java interop)

phronmophobic20:11:11

why won’t using a regular atom work for your usecase?

josh.freckleton20:11:21

interesting, that could possibly work, but I'd rather not bring in a big dependency, nor copy out what I need from EPL'd code... is there not a standard trick for me to obviate heavily nested code?

noisesmith20:11:53

not that I know of - you could do something similar to what let-later is doing, but more manually - making delays and only realizing them as you need them for example

noisesmith20:11:09

there are also “monad” libraries that use the Either monad for a similar result

Garrett Hopper20:11:46

@smith.adriane I'm using deftype, because there are a number of other protocols to be implemented and modifications to the normal atom functions. I didn't know if there was a way for it to extend an atom, since its base data is just an atom.

noisesmith20:11:14

the typical approach is to extend all the interfaces / protocols atom implements, instead of concrete inheritance from atom itself

donaldball20:11:17

I usually implement IDeref when I want a thing that acts like an atom

noisesmith20:11:34

=> (supers (class (atom nil)))
#{clojure.lang.IRef java.lang.Object clojure.lang.IMeta clojure.lang.AReference clojure.lang.ARef clojure.lang.IDeref clojure.lang.IReference clojure.lang.IAtom}

noisesmith20:11:04

it depends what you need - IDeref doesn’t help with eg. swap! or add-watch

phronmophobic20:11:50

isn’t swap! specifically for atoms and not any other reference type?

noisesmith20:11:57

it depends what someone means by “acting like an atom” here - it’s fully in our power to make something that is a drop in replacement for an atom

phronmophobic20:11:19

right. I ask mostly because most of the time when I’ve seen someone try to implement IAtom, they don’t plan to also implement all of the same semantics. if you’re not implementing all of the same semantics, I think it probably makes more sense to just use another function to manipulate your reference type just like the other clojure reference types each have their own functions, (eg. vswap!, send, ref-set, etc)

Garrett Hopper20:11:52

Thanks, guys, this helps.

noisesmith20:11:05

@ghopper going back to your initial question, proxy can extend an atom, or you can implement all the right protocols / interfaces to replicate an atom (maybe even using a closed over atom) with deftype, reify, defrecord, etc.

noisesmith20:11:25

but it really depends on what you are trying to do, which behaviors you need, what you exactly you wanted from IAtom

noisesmith20:11:48

because extending a clojure built in tends to be a bit tedious because of how the interfaces / protocols are factored

Garrett Hopper20:11:48

Closed over meaning the deftype includes an atom as a parameter?

noisesmith20:11:20

as a parameter, or just creating a reify inside a let block that defines the atom - there’s a few ways to “capture” an atom for internal usage

Garrett Hopper20:11:43

@noisesmith When implementing IAtom's swap functions, is there a way to reduce duplication of swapping code? Do I need the 4 different arrity versions?

noisesmith20:11:53

since you can’t control the caller, and the interface supports all those arities, I think the only safe thing is to define them all (even if they all end up calling the same variadic function)

noisesmith20:11:26

clojure will let you skip arities or even methods, but it will lead to a runtime error if they get called

Garrett Hopper20:11:47

Is there a performance benefit of not colling the other arities of the type (duplicating the function). (e.g. should I call deref on the this argument, or does it make sense to reimplement the logic of the deref inline?)

Garrett Hopper20:11:21

Oh, so an external variable arity function could be called by all of them. That's probably the cleanest.

noisesmith20:11:36

that’s a more general software design question isn’t it? sometimes it’s worth it to avoid the abstraction for performance / resource reasons, sometimes it makes sense to abstract and avoid the repetition

noisesmith20:11:47

right, yeah, that’s probably cleanest

Garrett Hopper20:11:58

Yeah, I suppose it is. Thanks!

Garrett Hopper21:11:39

@noisesmith You mind giving me your opinion on this?:

(defn- swap-g-counter [this f & args]
  (let [oldv @this
        newv (apply f oldv args)]
    (assert (number? newv) "Swap! G-Counter function must return a Number")
    (swap! (.p this) update (.n this)  + (- newv oldv))
    @this))

(defn- compare-and-set-g-counter [this oldv newv]
  (let [value @this]
    (if (not= oldv value)
      false
      (do
        (swap! this + (- newv value))
        true))))

(deftype G-Counter [p n]
  clojure.lang.IDeref
  (deref [this]
    (reduce + (vals @p)))

  clojure.lang.IAtom
  (swap [this f]
    (swap-g-counter this f))
  (swap [this f a]
    (swap-g-counter this f a))
  (swap [this f a b]
    (swap-g-counter this f b))
  (swap [this f a b args]
    (apply swap-g-counter this f b args))
  (compareAndSet [this oldv newv]
    (compare-and-set-g-counter this oldv newv))
  (reset [this newv]
    (swap! this + (- newv @this))
    newv))

(defn gcounter []
  (new G-Counter (atom {:id 0}) :id))

noisesmith21:11:21

using @ and swap! in the same function is usually a race condition

Garrett Hopper21:11:20

On the reset function? So the @this should be bound before swap!?

noisesmith21:11:20

you probably want compare-and-set! which allows more complex logic

noisesmith21:11:57

@ghopper at a quick skim, every single function that uses @ and swap! in the same body is a likely race condition

noisesmith21:11:23

I mean, you even have compare-and-set in the name, so implementing via compare-and-set! is much more likely to be correct

Garrett Hopper21:11:00

I'm not following how it would be a race condition.

noisesmith21:11:05

and yes, looking closer, those are definite race conditions in both of the first two functions

Garrett Hopper21:11:27

Right, I didn't think about how the normal compare-and-set! would use my custom reset logic. I'll switch to that.

noisesmith21:11:57

@ghopper (+ newv oldv) - this is a race condition, oldv came from a deref, the swap! itself will retry, but your function won’t so it will use stale data in a race

noisesmith21:11:27

(not= oldv value) - the truth value of this can change before the swap! runs

Garrett Hopper21:11:30

@noisesmith Oh! Thanks for clerifying that. I'm following now.

Garrett Hopper21:11:56

I'd completely neglector the retrying functionality of atoms.

Garrett Hopper21:11:14

@noisesmith Oh, I can't use the normal compare-and-set!, because I'm not wanting the atom reset to the newval; only a subset of the atom can ever be set.

Garrett Hopper21:11:40

compare-and-set! uses a java interop .compareAndSet

noisesmith21:11:09

you can’t use it to implement your full functionality, but to preserve expected atom semantics it needs to be your building block

Garrett Hopper21:11:31

It is? compareAndSet

Garrett Hopper21:11:49

Between swap and reset

noisesmith21:11:52

you can’t just mix @ and swap! of the same atom and expect correctness of any sort

noisesmith21:11:08

but you can get it by using compare-and-set, with your own retry condition, etc.

Garrett Hopper21:11:41

Oh, I think I'm following. I'll try implementing that.

noisesmith21:11:20

the idea is to use compare-and-set to wrap both the read and the modification, and then recur as your retry if the operation did not succeed

phronmophobic21:11:06

is there a reason you want your reference type to use the atom functions instead of creating new functions to interact with your reference type?

Garrett Hopper21:11:07

Mostly as a proof of concept / out of curiosity at the moment.

phronmophobic21:11:10

it seems like instead of putting counter related logic inside of swap! and deref, it might make more sense to put that logic in another function that takes an atom as an argument

Garrett Hopper21:11:22

Well my thought was to abstract it, so users can completely ignore the implementation details of the crdt. 🤷 I think I'll do it both ways and see how it feels.

phronmophobic21:11:47

I would actually probably have the functions work on immutable data

phronmophobic21:11:05

and then let the consumers of the counter decide if they want to use an atom or other reference type

Garrett Hopper21:11:19

Yeah, that's likely how I'd do it.

Garrett Hopper21:11:41

How can I get two swap!s to run at the same time to test retrying?

Garrett Hopper21:11:49

Oh, it was just too fast... A Thread/sleep did the trick.

noisesmith21:11:34

yeah - adding sleeps (maybe randomized) is a quick way to get race conditions to happen

Garrett Hopper21:11:56

Randomized is a good idea

noisesmith21:11:35

combining randomized timeouts / sleeps with looped calls is a great way to stress test things that might misbehave

noisesmith21:11:00

(in terms of race conditions that is)

Garrett Hopper21:11:43

@noisesmith So I'm still not quite seeing the purpose of compare-and-set!. Is there a reason I shouldn't just implement reset! inside it with the new value if the old value is the expected value?

noisesmith21:11:29

the point of compare-and-set! is that atoms don’t lock, and you need to be able to retry if the value changes before your update completes

Garrett Hopper22:11:00

Which reset! would do, right?

noisesmith22:11:09

how would it even know?

Garrett Hopper22:11:19

Oh, unless you have some sort of abstract logic based on the value?

Garrett Hopper22:11:27

I think I'm starting to understand.

noisesmith22:11:32

reset! never retries, and the condition you would need to detect in order to retry is outside the scope of the reset! call

noisesmith22:11:41

which is why you have compare-and-set!

Garrett Hopper22:11:10

@noisesmith Mind giving this another look?

(defn- project-g-counter [p]
  (reduce + (vals p)))

(defn- swap-g-counter [p n f & args]
  (let [oldv (project-g-counter p)
        newv (apply f oldv args)]
    (assert (number? newv) "Swap! G-Counter function must return a Number")
    (assert (>= newv oldv) "G-Counter is grow only")
    (update p n + (- newv oldv))))

(deftype G-Counter [p n]
  clojure.lang.IDeref
  (deref [this]
    (project-g-counter @p))

  clojure.lang.IAtom
  (swap [this f]
    (swap! p swap-g-counter n f)
    @this)
  (swap [this f a]
    (swap! p swap-g-counter n f a)
    @this)
  (swap [this f a b]
    (swap! p swap-g-counter n f a b)
    @this)
  (swap [this f a b args]
    (apply swap! p swap-g-counter n f a b args)
    @this)
  (compareAndSet [this oldv newv]
    (loop []
      (let [pval @p]
        (if (not= oldv (project-g-counter pval))
          false
          (or (compare-and-set! p pval (swap-g-counter pval n + (- newv oldv)))
              (recur))))))
  (reset [this newv]
    (assert (number? newv) "Reset! G-Counter value must be a Number")
    (swap! this (partial + (- newv @this)))
    newv))

(defn gcounter []
  (new G-Counter (atom {:id 0}) :id))

hiredman22:11:19

that is all race conditions

noisesmith22:11:41

@ghopper I can see you are trying but @hiredman is right - all the code that uses deref and swap! in the same block of code is setting up a race of some sort

hiredman22:11:42

swap returns the value swapped in, derefing after the swap could return anything

noisesmith22:11:07

what I meant about compare-and-set! is that to make this code correct the whole thing needs to be inside a compare-and-set! loop which can either let the caller know if it failed or retry if there was any modification outside

Garrett Hopper22:11:33

Oh, right, I need to use project-g-counter on the return value of swap! instead. Is there something somewhere else?

noisesmith22:11:59

the various places you call swap! then return @this for example

Garrett Hopper22:11:40

I realize that problem now:

clojure.lang.IAtom
  (swap [this f]
    (project-g-counter (swap! p swap-g-counter n f)))
  (swap [this f a]
    (project-g-counter (swap! p swap-g-counter n f a)))
  (swap [this f a b]
    (project-g-counter (swap! p swap-g-counter n f a b)))
  (swap [this f a b args]
    (project-g-counter (apply swap! p swap-g-counter n f a b args)))

Garrett Hopper22:11:05

Is there something wrong with the implementation of compare-and-set! above?

noisesmith22:11:11

you don’t seem to understand why I was mentioning compare-and-set! - in order to check an arbitrary condition and retry if the value is modified and also return an arbitrary value other than the thing you just modified, compare-and-set! is the ideal tool

noisesmith22:11:35

it lets you keep the logic you wanted without having all the race conditions (as long as you use it properly)

Garrett Hopper22:11:05

Oh, you're saying compare-and-set! should be used not only for the implementation of compareAndSet, but also for the other implementations too?

noisesmith22:11:51

right - it lets you coordinate in a more flexible way than swap!

Garrett Hopper22:11:08

I apologize for not reading that more carefully. Makes sense. 🙂

Garrett Hopper22:11:28

I didn't even know compare-and-set! was a thing until I went to implement IAtom.

Garrett Hopper22:11:19

@noisesmith Like this?

(defn- project-g-counter [p]
  (reduce + (vals p)))

;; (defn- swap-g-counter [p n f & args]
;;   (let [oldv (project-g-counter p)
;;         newv (apply f oldv args)]
;;     (assert (number? newv) "Swap! G-Counter function must return a Number")
;;     (assert (>= newv oldv) "G-Counter is grow only")
;;     (update p n + (- newv oldv))))

(defn- swap-g-counter [this f & args]
  (let [p (.p this)
        n (.n this)]
    (loop []
      (let [oldval @this
            newval (apply f oldval args)
            pval @p
            nval (update pval n + (- newval oldval))]
        (if (compare-and-set! p pval nval)
          newval
          (recur))))))

(deftype G-Counter [p n]
  clojure.lang.IDeref
  (deref [this]
    (reduce + (vals p))
    ;; (project-g-counter @p)
    )

  clojure.lang.IAtom
  (swap [this f]
    (swap-g-counter this f)
    ;; (project-g-counter (swap! p swap-g-counter n f))
    )
  (swap [this f a]
    (swap-g-counter this f a)
    ;; (project-g-counter (swap! p swap-g-counter n f a))
    )
  (swap [this f a b]
    (swap-g-counter this f a b)
    ;; (project-g-counter (swap! p swap-g-counter n f a b))
    )
  (swap [this f a b args]
    (apply swap-g-counter this f a b args)
    ;; (project-g-counter (apply swap! p swap-g-counter n f a b args))
    )
  (compareAndSet [this oldv newv]
    (loop []
      (let [pval @p]
        (if (not= oldv (project-g-counter pval))
          false
          (or (compare-and-set! p pval (swap-g-counter pval n + (- newv oldv)))
              (recur))))))
  (reset [this newv]
    (assert (number? newv) "Reset! G-Counter value must be a Number")
    (swap! this (partial + (- newv @this)))
    newv))

(defn g-counter []
  (new G-Counter (atom {:id 0}) :id))

noisesmith22:11:04

I don’t get what’s happening in reset but the rest looks better at a first glance

Garrett Hopper22:11:48

Rreset needs to take the value the user wants the counter to be and find the difference between the that and the current value of the counter, then it adds that to the value in the map the user is allowed to modify.

Garrett Hopper22:11:30

(reset [this newv]
    (assert (number? newv) "Reset! G-Counter value must be a Number")
    (swap! this #(+ % (- newv @this)))
    newv)
I had this originally. I suppose it's more readable.

Garrett Hopper22:11:52

Can a swap function deref the atom it's swapping?

Garrett Hopper22:11:25

It's weird, because derefing doesn't return the same value as the swap function accepts.

noisesmith22:11:33

I don’t know if that’s correct yet, but fyi you can always replace (swap! a #(f % b)) with (swap! a f b)

Garrett Hopper22:11:52

Oh yeah, good point. :thumbsup:

Garrett Hopper22:11:45

Actually, swap does accept the current counter value. There's no need for the @this

noisesmith22:11:53

a deref of the same atom inside the function arg to swap! is safe because if the atom changes you will retry the swap!

noisesmith22:11:08

but yes, that too

Garrett Hopper22:11:29

That's what I thought, which is why I hadn't thought about it.

Garrett Hopper22:11:36

(reset [this newv]
    (assert (number? newv) "Reset! G-Counter value must be a Number")
    ;; (swap! this + (- newv @this))
    (swap! this #(+ % (- newv %)))
    newv)

Garrett Hopper23:11:41

... I'm not sure why I'm doing this. It just needs to return the newv, and the swap! will handle the difference stuff.

Garrett Hopper23:11:48

(reset [this newv]
    (assert (number? newv) "Reset! G-Counter value must be a Number")
    (swap! this (constantly newv))
    newv)

noisesmith23:11:24

won’t the swap! call already return newv?

Garrett Hopper23:11:42

Uh, yeah, it will.

Garrett Hopper23:11:48

Well, that shrunk a lot...

Garrett Hopper23:11:34

It feels a little weird to use swap for reset, though I suppose that allows me to keep the difference logic in the same place.

Garrett Hopper23:11:23

I'm pretty happy with that. For anyone interested:

(defn- swap-g-counter [this f & args]
  (let [p (.p this)
        n (.n this)]
    (loop []
      (let [oldval @this
            newval (apply f oldval args)
            pval @p
            nval (update pval n + (- newval oldval))]
        (assert (number? newval) "Swap! G-Counter function must return a Number")
        (assert (>= newval oldval) "G-Counter is grow only")
        (if (compare-and-set! p pval nval)
          newval
          (recur))))))

(deftype G-Counter [p n]
  clojure.lang.IDeref
  (deref [this]
    (reduce + (vals p)))

  clojure.lang.IAtom
  (swap [this f]
    (swap-g-counter this f))
  (swap [this f a]
    (swap-g-counter this f a))
  (swap [this f a b]
    (swap-g-counter this f a b))
  (swap [this f a b args]
    (apply swap-g-counter this f a b args))
  (compareAndSet [this oldv newv]
    (loop []
      (let [pval @p]
        (if (not= oldv (project-g-counter pval))
          false
          (or (compare-and-set! p pval (swap-g-counter pval n + (- newv oldv)))
              (recur))))))
  (reset [this newv]
    (assert (number? newv) "Reset! G-Counter value must be a Number")
    (swap! this (constantly newv))))

(defn g-counter []
  (new G-Counter (atom {:id 0}) :id))

phronmophobic23:11:01

how does the deref method work? isn’t p an atom?

phronmophobic23:11:13

seems like calling vals on p in the deref method would throw an exception

Garrett Hopper23:11:20

Yeah, that's a typo. I've fixed it:

clojure.lang.IDeref
  (deref [this]
    (reduce + (vals @p)))

Garrett Hopper23:11:29

I'm not sure when that got changed.

phronmophobic23:11:49

i might be reading it wrong, but seems like there’s still a race condition in your swap-g-counter function

noisesmith23:11:25

the compare-and-set! will fail if p no longer olds pval

noisesmith23:11:30

that’s the point of using that function

phronmophobic23:11:40

p is dereferenced twice

noisesmith23:11:52

inside a loop that retries if the compare-and-set! fails

noisesmith23:11:18

and the compare-and-set! will fail if p changes

phronmophobic23:11:26

so if it changes between the 1st and 2nd dereference, but not between the 2nd dereference and compare-and-set!

phronmophobic23:11:30

it would be inconsistent

phronmophobic23:11:49

since the new value is derived from both the 1st and 2nd dereference

phronmophobic23:11:00

and the first and second dereference might have different values

noisesmith23:11:12

no- if it changes between binding oldval and compare-and-set! the compare-and-set will fail

phronmophobic23:11:43

oldval isn’t passed into compare-and-set!, pval is

noisesmith23:11:08

yeah, this is weird

Garrett Hopper23:11:33

Yeah, I need to get a better naming convention.

phronmophobic23:11:01

i think the code would be simplified a lot by having functions that work on immutable counter data

noisesmith23:11:16

I think you need nested compare-and-set! calls to ensure consistency on both dereferences?

phronmophobic23:11:18

and using a plain ol’ atom to hold the data

phronmophobic23:11:02

if you really wanted it to work with this interface, you could have pval use the data from the oldval dereference

Garrett Hopper23:11:23

Yeah, I think I'm going to need to switch to immutable functions anyways for my merging logic. At the moment I'd need to have a function which takes one of these and a p (from somewhere else) and merge them. I'd also need another function to get the p out to send elsewhere.

phronmophobic23:11:49

the nice thing about atoms is that they just encapsulate identity. I don’t think you want to mix the identity and state pieces into one thing

Garrett Hopper23:11:01

pval can't use oldval, because oldval is just an integer.

Garrett Hopper23:11:36

Yeah, I think you're right.

phronmophobic23:11:40

right, I guess you would have to have oldval derive from pval

Garrett Hopper23:11:58

Good call, that's something that it could do.

Garrett Hopper23:11:17

Though I'd need to bring back project-g-counter for the reduction function.

phronmophobic23:11:19

or as long as you dereference pval before you do the dereference for oldval, it would work

phronmophobic23:11:29

actually, nvmd

phronmophobic23:11:33

i take that back

phronmophobic23:11:06

I think you have to do it with a single dereference

phronmophobic23:11:21

other wise it could change in between dereferences and then change back

Garrett Hopper23:11:21

(defn- swap-g-counter [this f & args]
  (let [p (.p this)
        n (.n this)]
    (loop []
      (let [pval @p
            oldval (reduce + (vals pval))
            newval (apply f oldval args)
            nval (update pval n + (- newval oldval))]
        (assert (number? newval) "Swap! G-Counter function must return a Number")
        (assert (>= newval oldval) "G-Counter is grow only")
        (if (compare-and-set! p pval nval)
          newval
          (recur))))))

Garrett Hopper23:11:30

Yeah, I'm pretty sure it's needed.

josh.freckleton23:11:22

I'm using wrap-json-response, but I'd like to opt out and return Content-Type: text/html sometimes, is there an easy way out?

josh.freckleton23:11:37

(oh, I'm working within Compojure-API)

noisesmith23:11:03

so you have one endpoint, that would sometimes return json and sometimes return html?

josh.freckleton23:11:06

@noisesmith correct, based on their request header of Content-Type, so they can have json or html

josh.freckleton23:11:42

I think I need to just pull out this route from the main app, and add it back in separate without the offending middleware

josh.freckleton23:11:53

is there a less awkward solution tho, perhaps?

noisesmith23:11:11

there’s no rule saying you can’t generate json inside your handler (or have a conditional in your handler that either dispatches to an html function or a json function)

josh.freckleton23:11:12

(the ring function content-type was a no go)

noisesmith23:11:34

those functions are just modifying data in a hash-map, if using them is at all awkward just skip them

josh.freckleton23:11:19

oh no, I mean, I'd like wrap-json-X most of the time, but on one route, reset it. I think what I'm setting is getting over-written though by the middleware

tvalerio23:11:47

@josh.freckleton I don’t know if that’s wrong, but when I need to return a different content-type I just change manually and return something like this: {:status 200 :headers {“content-type” “text/html”} :body {:message (:message (any-function))}}

noisesmith23:11:59

yeah, if you want to conditionally make json you don’t want a middleware that always returns json

tvalerio23:11:12

if you do that, the content-type is changed?

noisesmith23:11:18

that middleware isn’t doing as much as you think it is, just skip it