Fork me on GitHub
#clojure
<
2016-09-07
>
danielcompton01:09:50

@bwstearns there’s also https://www.snowtide.com by Chas Emerick (same person doing http://pdfdata.io)

dpsutton02:09:28

if anyone was wondering more about what ghadi was talking about (seq vs reduces) hiredman has written a good piece here: https://ce2144dc-f7c9-4f54-8fb6-7321a4c318db.s3.amazonaws.com/reducers.html (which was linked from here: http://paul.stadig.name/2016/08/reducible-streams.html)

surreal.analysis03:09:39

What’s the appropriate way to reference a Java enum that has a period in it, e.g. as found here - http://icu-project.org/apiref/icu4j/com/ibm/icu/util/ULocale.Category.html

surreal.analysis03:09:20

Category is a nested class of ULocale

quoll04:09:46

@surreal.analysis: try using a $ in place of that period

quoll04:09:03

Whenever I get stuck on a class name like that, I just list the contents of the jar file (jar tf filename.jar | grep ClassIWant), or look for the .class file if it's a project I'm building. The filename of the .class file gives you the string you need

maleghast04:09:11

Does anyone have a really elegant solution to the requirement of “randomly intersperse items from this short list in amongst this longer list to create a single list”? I have been thinking about it in another programming language and would love to know how I could do it with Clojure with a view to proving the point that “Functional is better at this stuff”, but I am rusty and never got good at being properly Clojuric, so I am stumbling and producing code (in my head) that feels far more clunky than my OO / Procedural approach.

maleghast04:09:44

(it is acceptable for not all of the items in the shorter list to actually be used, but it is preferable that they are)

cddr05:09:51

Is (shuffle (concat short-list longer-list)) cheating?

maleghast06:09:10

@cddr I don’t think so, no...

maleghast06:09:24

Just let me try it out...

seancorfield06:09:33

Depends whether you want all the elements in a random order or the shorter list interspersed into the longer list

maleghast06:09:57

@cddr @seancorfield - Just tried it out to be sure and it’s not cheating, but it’s not in line with the spec as it disrupts the overall order of the longer list.

seancorfield06:09:20

Right, the latter is what you really want...

maleghast06:09:21

@cddr - Thanks though - for a different use-case it would still be very useful

maleghast06:09:53

@seancorfield - Yes; the shorter list somewhat randomly interspersed into the longer list.

seancorfield06:09:54

I'd have to think about that... interesting problem...

maleghast06:09:11

@seancorfield - Yeah, I thought so too...

seancorfield06:09:15

Although I'd be more interested in a real world problem and that doesn't seem real world?

maleghast06:09:55

and the proof to me that I am right to feel I am not a Clojuric programmer was that I was able to run up a reasonably elegant solution in Python Ruby and PHP easily, but Clojure ideas that I had seemed clunky.

maleghast06:09:06

@seancorfield - It is actually a real-world problem

maleghast06:09:38

Our stack where I work is PHP / Python, so I gave the devs working on it an example in PHP in the end, though I brainstormed it in Ruby to being with.

seancorfield06:09:54

What is the real world problem?

maleghast06:09:08

Take a list of DB results that is 20 results (page limited), and interleave a smaller list of result into the data for display purposes that are the result of a different query

maleghast06:09:54

The occurrences of the second data set’s rows should appear random

maleghast06:09:31

The idea is to instil FOMO in the user by showing them things that they could have bought but that have already been snapped up by another customer, but do that with real sales data rather than faking it.

maleghast06:09:50

Here’s the example that I gave my devs to work from in PHP: https://gist.github.com/maleghast/453ec0b99212ef4e1ef1c619cd60d199

seancorfield06:09:14

Ah, so you don't have to use all of the shorter list? Just some of them?

maleghast06:09:56

better to use it all up if possible, but not a requirement

maleghast06:09:16

It also only happens on page 1

maleghast06:09:25

It felt__ as though it ought to be something Lisp, and in my world that’s only Clojure, should be good at, but I’ve never really had the time and space to use Clojure enough to become a Lisp-y thinker… So I thought I would use this as a learning experience 😉

Chris O’Donnell07:09:45

@maleghast if I'm reading your php correctly, there's a 50% chance a random item from data_second is placed between each element of data_first?

seancorfield07:09:55

Yeah, I can imagine a number of solutions to this problem... not sure what would be the cleanest...

Chris O’Donnell07:09:23

if data_second is half the length of data_first, I suppose

maleghast07:09:48

@codonnell - Yep it’s a naive algo, just designed to show some of my devs how one might__ approach it - they were saying it could not be done...

seancorfield07:09:59

assuming you have no nil elements... you could randomly insert nil into the shorter list to make something at least as long as the other list and then mapcat them producing pairs for non-`nil` second elements or just the first element (in [ ]).

Chris O’Donnell07:09:02

(remove nil? (interleave coll1 (shuffle (interleave coll2 (repeat nil))))) would give you a 50% chance of interleaving an element from coll2 in between elements of coll1, assuming your collections do not contain any nil entries.

maleghast07:09:05

@codonnell - I’m not claiming that it’s complete or particularly good to be honest, but I was able to run up a solution in about 10 minutes, hand-balling all the dummy data that gave a different answer form one run to the next, so it got them to see that it’s not that__ hard… 😉

seancorfield07:09:25

@codonnell shuffle won't work -- it loses order

Chris O’Donnell07:09:40

I think we can change the order of coll2?

maleghast07:09:02

Yep you can - that coll is order-unimportant

Chris O’Donnell07:09:45

might be cleaner with ->>

seancorfield07:09:49

then, yes, that will work... you can adjust the nil list to a given length based on a computation of ratio between the two list lengths

Chris O’Donnell07:09:52

yeah, instead of (interleave coll2 (repeat nil)), you could write (concat coll2 (repeat n nil)), where you adjust n based on the probability of not adding a coll2 element

maleghast07:09:54

It would be good to be able to make it a bit more “random” i.e. have list1-item, list2-item, list2-item, list1-item, list2-item, list1-item, list1-item, list1-item, list2-item, list1-item, list2-item, list2-item

seancorfield07:09:17

(->> (repeat n nil) (interleave coll2) shuffle (interleave coll1) (remove nil?))

seancorfield07:09:01

Given an appropriate n that will be suitably random.

seancorfield07:09:36

(given that order doesn't matter for coll2)

maleghast07:09:24

I might go and give this a try...

Chris O’Donnell07:09:14

if you want to allow adding multiple elements of coll2 in between, you could use reduce to take a random number of elements from the shuffled and "nil-enhanced" coll2 each time you add an element of coll1

maleghast07:09:22

Yeah, that doesn’t work...

maleghast07:09:49

Here’s the first list (dummy / test data):

[{:status "active", :name "Tom Sawyer", :telno "+639876123456"} {:status "active", :name "Dave Sawyer", :telno "+639877123456"} {:status "active", :name "Gertrude Sawyer", :telno "+639878123456"} {:status "active", :name "Stephanie Sawyer", :telno "+639879123456"} {:status "active", :name "Dwight Sawyer", :telno "+639880123456"} {:status "active", :name "Eleanor Sawyer", :telno "+639881123456”}]

maleghast07:09:13

Here’s the second:

[{:status "sold", :name "Huckleberry Finn", :telno "+639890123456"} {:status "sold", :name "Jemimah Finn", :telno "+639891123456"} {:status "sold", :name "Dorothy Finn", :telno "+639892123456”}]

maleghast07:09:00

If you pass them in with a value of 10 for n you get:

({:status "sold", :name "Huckleberry Finn", :telno "+639890123456"} {:status "sold", :name "Huckleberry Finn", :telno "+639890123456"} {:status "sold", :name "Jemimah Finn", :telno "+639891123456"} {:status "sold", :name "Jemimah Finn", :telno "+639891123456"} {:status "sold", :name "Dorothy Finn", :telno "+639892123456”})

maleghast07:09:13

Which is nowhere close to the spec 😞

Chris O’Donnell07:09:13

This is what I get:

({:status "active", :name "Tom Sawyer", :telno "+639876123456"} {:status "sold", :name "Jemimah Finn", :telno "+639891123456"} {:status "active", :name "Dave Sawyer", :telno "+639877123456"} {:status "active", :name "Gertrude Sawyer", :telno "+639878123456"} {:status "active", :name "Stephanie Sawyer", :telno "+639879123456"} {:status "sold", :name "Dorothy Finn", :telno "+639892123456"} {:status "active", :name "Dwight Sawyer", :telno "+639880123456"} {:status "sold", :name "Huckleberry Finn", :telno "+639890123456"} {:status "active", :name "Eleanor Sawyer", :telno "+639881123456"})

seancorfield07:09:56

Here's what I got with n equal 10:

({:status "active", :name "Tom Sawyer", :telno "+639876123456"} {:status "active", :name "Dave Sawyer", :telno "+639877123456"} {:status "active", :name "Gertrude Sawyer", :telno "+639878123456"} {:status "sold", :name "Jemimah Finn", :telno "+639891123456"} {:status "active", :name "Stephanie Sawyer", :telno "+639879123456"} {:status "sold", :name "Huckleberry Finn", :telno "+639890123456"} {:status "active", :name "Dwight Sawyer", :telno "+639880123456"} {:status "sold", :name "Dorothy Finn", :telno "+639892123456"} {:status "active", :name "Eleanor Sawyer", :telno "+639881123456"})

seancorfield07:09:40

So if you only got sold entries @maleghast maybe you did something wrong?

Chris O’Donnell07:09:01

I think you put the second list in for the first list.

maleghast07:09:07

Possibly, let me try again...

seancorfield07:09:30

Here's my REPL session

boot.user=> (def coll1 [{:status "active", :name "Tom Sawyer", :telno "+639876123456"} {:status "active", :name "Dave Sawyer", :telno "+639877123456"} {:status "active", :name "Gertrude Sawyer", :telno "+639878123456"} {:status "active", :name "Stephanie Sawyer", :telno "+639879123456"} {:status "active", :name "Dwight Sawyer", :telno "+639880123456"} {:status "active", :name "Eleanor Sawyer", :telno "+639881123456"}])
#'boot.user/coll1
boot.user=> (def coll2 [{:status "sold", :name "Huckleberry Finn", :telno "+639890123456"} {:status "sold", :name "Jemimah Finn", :telno "+639891123456"} {:status "sold", :name "Dorothy Finn", :telno "+639892123456"}])
#'boot.user/coll2
boot.user=> (def n 10)
#'boot.user/n
boot.user=> (->> (repeat n nil) (interleave coll2) shuffle (interleave coll1) (remove nil?))
({:status "active", :name "Tom Sawyer", :telno "+639876123456"} {:status "active", :name "Dave Sawyer", :telno "+639877123456"} {:status "active", :name "Gertrude Sawyer", :telno "+639878123456"} {:status "sold", :name "Jemimah Finn", :telno "+639891123456"} {:status "active", :name "Stephanie Sawyer", :telno "+639879123456"} {:status "sold", :name "Huckleberry Finn", :telno "+639890123456"} {:status "active", :name "Dwight Sawyer", :telno "+639880123456"} {:status "sold", :name "Dorothy Finn", :telno "+639892123456"} {:status "active", :name "Eleanor Sawyer", :telno "+639881123456"})
boot.user=>

maleghast07:09:46

Oops, my bad - must’ve slipped - sorry

maleghast07:09:06

Yes, I have it working now and it’s really rather lovely - thanks chaps 🙂

seancorfield07:09:38

Functional Rocks! 🙂

maleghast07:09:00

I like the easy way to pad the shorter coll - never thought of doing that, but it’s really simple once you know how 😉

seancorfield07:09:27

If you needed to preserve order on the shorter collection, you could use reduce and randomly conj the element and nils into a sequence to be interleaved.

maleghast07:09:00

Yeah, actually I was working on using reduce to do it, but I felt as though it was an over-complication for the spec...

maleghast07:09:38

This is a really nice elegant “this is why Lisps rock” solution and it’s a good “help me to think in lists” example - I appreciate you guys helping, thanks 🙂

lucasbradstreet08:09:51

Can someone tell me that I’m not going insane? This seems like a bug in checking the spec of the return value

lucasbradstreet08:09:56

(ns fdef-ret-value.core
  (:require [clojure.spec :as s]
            [clojure.test :refer :all]
            [clojure.spec.test :as stest]))

(defn hypotenuse
  "returns hypotenuse"
  [a b]
  (int (Math/sqrt (+ (* a a) (* b b)))))

(s/fdef hypotenuse
        :args (s/cat :a int? :b int?)
        :ret double?)

(stest/instrument `hypotenuse)

(deftest specdtest
  (is (hypotenuse 33 500)))

(clojure.test/run-all-tests)

lucasbradstreet08:09:24

The test will definitely fail if I supply a double argument, but it always passes no matter what I do with the return argument

seancorfield08:09:49

Instrument does not check :ret or :fn -- only :args

lucasbradstreet08:09:36

Huh. Thanks! What’s the best way to run tests checking everything?

seancorfield08:09:43

Instrument verifies functions are called correctly, check verifies functions are implemented correctly (with generative testing).

lucasbradstreet08:09:12

Hmm, what if I just want to run my tests and verify all the specs against my regular clojure.test tests?

seancorfield08:09:14

Remember that generative testing is not like regular (unit) testing.

lucasbradstreet08:09:49

I use test.check all the time, but I also have a test suite that doesn’t use test.check and I still want to be able to check everything in the spec against it

lucasbradstreet08:09:32

I thought "clojure.spec.test/run-tests” was the answer, as described in the docs, however it doesn’t exist

lucasbradstreet08:09:37

Maybe that’s the long term answer

seancorfield08:09:39

I feel like this has been covered at length in all the material written about clojure.spec

lucasbradstreet08:09:09

Thanks for the answer. I’m not sure I feel the same, but I will re-read it

seancorfield08:09:18

Rich seems very clear that there are two very different types of testing here.

bfabry08:09:53

fwiw, the :ret and :fn thing comes up again and again, but it's been in slack and groups. I don't think anyone's written up an official rationale for it, or at least I have managed to miss it

seancorfield08:09:00

(It's late here so I'm too tired to debate it - catch me on Pacific day time and I'll be happy to elaborate! 😀)

lucasbradstreet08:09:28

Sure, thanks for the reply. I didn’t realise it has been discussed to death. I’ll find it on the google groups. I don’t believe it’s discussed in the docs.

sveri08:09:58

Its new for me too, but explains why some errors did not pop up during my tests

idiomancy14:09:28

anyone know of any resources to help me understand namespaced keywords better? I dont really get the Big Deal with them. Like, why is ::my-key different from just saying :user/my-key? what can you do with a namespaced keyword that you cant do with a normal keyword?

keymone14:09:19

@idiomancy as far as i understand the idea is to have a sense of ownership for keywords when working with them in foreign namespaces. think collisions in complex hashmaps, multimethods etc.

Alex Miller (Clojure team)14:09:07

@idiomancy both of those are namespaced keywords. :: is just a naming shortcut.

idiomancy14:09:55

yeah, im starting to realize that the "namespace" is kind of overloaded

Alex Miller (Clojure team)14:09:14

namespaced keywords have context (a namespace) which allows people and organizations to play nicely together, just like namespaces for our code, etc

idiomancy14:09:30

because while the :: refers to autoexpanding to the current ns, "namespacing" your keyword need not have anything to do with a valid ns

Alex Miller (Clojure team)14:09:46

yes there is confusion between the namespace part of a keyword (maybe better called a “qualifier”)

idiomancy14:09:10

that latter part is the real confusion, because im starting to get into the libraries that use "namespacing" pretty cleverly

idiomancy14:09:12

like core.spec

ashishnegi15:09:00

hi.. has anybody written any blog post on adding repl endpoint in clojure server projects ? i have a clojure project and i want to access prod instance via cider repl from local machine..

seancorfield16:09:51

@idiomancy That’s why in clojure.java.jdbc I chose :qualifier as the option for "namespacing" the keywords produced for column names /cc @alexmiller

seancorfield16:09:30

We do something very similar to the latter inside our long-running processes. Being able to cider-connect from Emacs directly into a process in order to debug (and fix) a production problem can be very valuable. Although patching live into a production process via the REPL can be risky — "With great power comes great responsibility!" 😈

ashishnegi16:09:01

thanks @seancorfield i becomes very difficult for a newbie to understand so many nrepls.. but this is helpful..

Madara Uchiha16:09:34

Hey guys. I'm reading CftBaT and am at the chapter about concurrency

Madara Uchiha16:09:13

Aside from the memoization part, how does is a delay different from a normal function?

fellshard16:09:40

The body of the delay call won't be evaluated until forced. What you're writing inside there is basically the structure of an expression that can later be unpacked and evaluated only when needed.

fellshard16:09:12

It wraps the body in a nullary function, a cheap way of saying 'this expression already has what it needs to run, all you have to do is kick it off when you need'.

Madara Uchiha16:09:13

@fellshard Although, isn't that exactly what a function is?

Madara Uchiha16:09:31

A bunch of expressions waiting to be run?

fellshard16:09:31

This also wraps it in a Delay object

fellshard16:09:47

Which gives other functions a little more info about whether the result has been forced or not

fellshard16:09:59

And memoizes the result so it doesn't need to be evaluated multiple times

Madara Uchiha16:09:26

So, essentially, the big thing is the memoization

fellshard16:09:37

And the delay

Madara Uchiha16:09:42

That I can realize it from different parts of the app, and I know it'll only run the first time

Madara Uchiha16:09:16

The delay itself isn't too different from just waiting and then calling a function (unless you're referring to the Delay wrapper, in which case, yeah)

fellshard16:09:20

It's mostly the laziness - it won't be evaluated unless needed, and I can tell if it has been forced

fellshard16:09:40

So it's a nullary function with a bit of wrapping context... just like its implementation implies 🙂

fellshard16:09:58

It may also be easier for Java interop in some cases, I'm guessing

Madara Uchiha16:09:17

So essentially, delay is a wrapped, memoized function, future is JavaScript's Promise primitive, and promise is similar to JavaScript's Promise.deferred (in that you provide the value from the outside, rather than from the inside of the future creation macro)

fellshard16:09:36

future is more like a multi-threaded language's async-await in some ways, since it explicitly invokes the body in another thread

fellshard16:09:56

And hands you back an object that will contain the result when that thread's task completes

fellshard16:09:55

promise is just a primitive, probably a wrapper around an atom?

fellshard16:09:24

Yeah, it's a wrapper around an atom. With both future and promise, trying to realize a value before one has been generated will cause you to block and wait for that value to arrive.

fellshard16:09:15

promise here isn't necessarily as robust as JS' tend to be. It's not worried about success or failure, just about the 'assign once' and blocking mechanics.

Madara Uchiha16:09:29

In JavaScript, you would use a Promise similarly to how you'd use a future

Madara Uchiha16:09:44

It gives you "hooks" to call if/when the promise is successful or rejected

Madara Uchiha16:09:58

But the entire logic to be run under that Promise object is contained in the Promise constructor

Madara Uchiha16:09:04

(in this case, the (future) macro)

fellshard16:09:06

None o' that here. 🙂

fellshard16:09:24

This is the real bare-bones promise as it was originally created, iirc

Madara Uchiha16:09:39

But with (promise) it just waits and does nothing until you deliver to it from the outside (be it the main thread or another thread)

Madara Uchiha16:09:48

So it's essentially an orchestration tool

fellshard16:09:02

It hands you an object that you can block on

fellshard16:09:14

Yep. You'll find that's the case with a lot of these. 🙂

fellshard16:09:22

Small, atomic, understandable little pieces

fellshard16:09:39

With a variety of different mechanics so you can pick ones that meet your needs.

Madara Uchiha16:09:48

The concept of forcefully blocking is still foreign to me, I have to admit.

Madara Uchiha16:09:01

That's probably because I main JS

Madara Uchiha16:09:07

And in JS blocking == death.

fellshard16:09:20

Think multi-threaded, though

Madara Uchiha16:09:57

Is there a mechanism like (promise-all p1 p2 p3) ;=> Promise([res1 res2 res3])?

Madara Uchiha16:09:09

Or future-all rather

fellshard16:09:32

Sounds like something you could make 🙂

fellshard16:09:03

I don't know of one that exists, but I'm still learning too 😄

fellshard16:09:17

I'd expect something like that exists in a library

Madara Uchiha16:09:19

Yeah, it does.

Madara Uchiha16:09:26

Welp, time to get my hands dirty

richiardiandrea17:09:30

in core.async is it good practice/makes sense to do (async/alts! [timeout-ch [out-ch val]] :priority true) when putting values in a channel to avoid that the put blocks forever?

hiredman17:09:37

I think it does, you might look at offer!, which I think is a newish addition to core.async

richiardiandrea17:09:19

@hiredman thanks I am looking, it definitely makes no sense with an async >! or put! but I am inside my own thread and therefore using the !! variants

hiredman17:09:15

depending on what you are going to do if a put "fails" you might also just use a dropping or sliding buffer

richiardiandrea17:09:34

so offer! can be an alternative to the timeout check

hiredman17:09:53

because offer! never blocks, it is safe to use in a go block

richiardiandrea17:09:42

yes, I see, I guess it can be used outside a go block as well (more like my use case) but as a way to check if you can put and act if not

ccann19:09:10

is there a function that returns true for a set, list, or vector, but false for map?

hiredman19:09:17

(complement map?)

ccann19:09:57

well, I’m trying to wrap a vector around something that isn’t a set, list, or vector

ccann19:09:06

(and is usually a map)

ccann19:09:43

this seems too verbose to be right…

(if (or (seq? data) (list? data) (vector? data) (set? data))
    data
    (vector data)))

jr19:09:57

you can use some-fn to make it less verbose

jr19:09:20

(if ((some-fn seq? list? vector? set?) data)
....

ccann19:09:39

thanks @jr that’s a new one to me

idiomancy20:09:00

how does one use core.spec in a clojure project?

idiomancy20:09:26

1.9.0 seems to throw errors if i include it in my lein deps

idiomancy20:09:28

ah, im a dummy. you need to include the alpha stuff

fellshard21:09:10

Shower thoughts - what if a lisp-oriented VCS was developed to run inside a Clojure application?

fellshard21:09:29

Might be able to adapt something like Git's graph model, with multiple users pushing and pulling from the live application's repl, or even updating the application to run a specific commit. Hmm.

fellshard21:09:56

Not sure it confers any benefit compared to an external VCS, but...

idiomancy22:09:58

> However what happens if two libraries modify the same map? On the same key? Then we are in trouble, because one overwrites the other. clojure doesn't modify maps, it produced new maps based on existing values, so how could they you face a problem of "overwriting each other"??

timgilbert22:09:53

That doesn't make much sense to me either @idiomancy, what's the context?

idiomancy22:09:09

its explaining the value of namespaced keywords

timgilbert22:09:16

I guess it's talking about namespaced keywords?

idiomancy22:09:24

which I just dont effing get. its driving me crazy

timgilbert22:09:33

Oh right. Well, maybe if you think of it like two different libraries attach different semantic information to the same keyword

idiomancy22:09:50

if anyone can point me to another one that lays out what the idea behind them is, and why they are used like CRAZY in core.spec, I would devour it

bfabry22:09:56

@idiomancy it's talking about if the map were to pass through multiple functions, making it into a new map along the way. you're right that the language there is bad, because it doesn't 'modify' shit

timgilbert22:09:32

Like maybe they both use a :user key, but library 1 expects a user-id integer and library 2 expects a map with a session id in it

bfabry22:09:54

@idiomancy my favourite example is middleware. you chain a bunch of middleware functions together in an arbitrary order. they all add things to the map and pass it on to the next middleware function (or rather, they return it and something else does), namespaced keywords ensure that the different middleware functions don't interfere with each other despite working on the 'same' map

timgilbert22:09:14

Yeah, that's a good example

idiomancy22:09:19

@timgilbert isnt using those two maps together an error, then? isnt that something that should just be taken care of by a core.spec or something?

idiomancy22:09:02

ahhh, that is a good example, @bfabry

Alex Miller (Clojure team)22:09:03

the request and response maps are giant buckets of un-namespaced keys right now

timgilbert22:09:04

Well, it's just data. core.spec could definitely help you sort if out, but maybe one of your libraries doesn't offer a spec

Alex Miller (Clojure team)22:09:30

if middleware used qualified keywords those could be given some context and made a little less fragile

idiomancy22:09:01

what i meant was that using two peices of code that have different expectations for a value seems intrinsically like an error, not a namespace collision

timgilbert22:09:03

(One of the good design decisions in spec is that it forces you to namespace your keys anyways)

shaun-mahood22:09:20

@idiomancy: Good interview with Rich if you want to get deeper into some of the rationale behind it http://blog.cognitect.com/cognicast/103

idiomancy22:09:27

ahhh, niiice

idiomancy22:09:54

yeah, im always down to hear the sermons from the master

timgilbert22:09:30

The problem is more interoperability between two libraries that don't necessarily know anything about one another, but happen to have a collision in a key they use

idiomancy22:09:38

it seems kind of weird that a chain of middleware functions would all just add keys to the same map.. why not just have a different map for each middleware?

idiomancy22:09:51

why is getting one aggregate map more desirable than a sequence of maps?

idiomancy22:09:20

wouldnt the latter even give you the benefit of retrospection on the order of operations for free?

Alex Miller (Clojure team)22:09:08

using qualified keywords means you can attach semantics to keywords with context

timgilbert22:09:36

It's not the only way to compose a bunch of middleware, but it's a common one, cf https://github.com/boot-clj/boot/wiki/Tasks

bfabry22:09:44

few answers: 1. because that's just the way it is right now 2. flat maps are 'simpler' than nested maps. to read from, to add to, to refactor usage of 3. because you would have the same issue with the keyword that references the middleware's middleware-specific map 😛

bfabry22:09:02

oh sorry you said sequence, ignore the last point

idiomancy22:09:52

not sure I buy the simplicity of having a "symbol/" precede every keyword

idiomancy22:09:00

but I'll definitely buy the first point

timgilbert22:09:10

Pedestal takes a somewhat different approach with interceptors, which re-frame has now also adopted. But anyways the problem is the same, two things using a single key with different semantics

bfabry22:09:24

well a namespace for a keyword is a pretty simple idea. I'd agree that it's definitely not succinct though. but the latest version of clojure has been adding syntax to help with that

idiomancy22:09:28

hmm.. yeah, I particularly cant see the "ease of access in a flat map" argument. If you just joined all of your maps together, and nested the data based on namespace, then what is the difference between, say

(get map :ns/value)
and
(get-in map [:ns :value])
?

idiomancy22:09:11

grr.. how do i markdown

tanzoniteblack22:09:11

three backticks, not 2

tanzoniteblack22:09:27

@idiomancy ok, now try dissoc :value in :ns

bfabry22:09:27

if I wanted to do something with every value in the map, or if I wanted to get the values for the keys [:basic/user :ssl/token :csrf/token]

tanzoniteblack22:09:58

doesn’t exist

tanzoniteblack22:09:05

not sure the history of why it doesn’t though

tanzoniteblack22:09:11

and bfabry’s point is much better

idiomancy22:09:15

its not exactly a difficult concept to just create in two lines of code though

idiomancy22:09:20

@bfabry I suppose it makes sense, but that seems like it could just as easily have been solved by making a select-keys variant

bfabry22:09:55

also what if I want to change the key name in the basic middleware from ::user to ::user-name

idiomancy22:09:33

sorry, i dont understand what you mean there

idiomancy22:09:38

thats my fault

idiomancy22:09:46

i dont do much middleware interaction

bfabry22:09:17

like, I could selectively find and rename all the :basic/user's to :basic/user-name without ever worrying that I'd break :ssl/user

idiomancy22:09:42

well, i mean, theres no gain there over the nested approach

idiomancy22:09:53

you just only operate on the "basic" sub map

bfabry22:09:05

I mean refactoring tools for the codebase

idiomancy22:09:00

ohhh, I see. but within a codebase, you dont have that problem, I thought. its all just named ::user

bfabry22:09:38

I might have that problem if my codebase consumes both the basic and the ssl middleware

idiomancy22:09:23

hahaha, youre describing a clojure codebase larger than I have ever worked with, so its very possible I'm just out of my depth here

bfabry22:09:48

you never use the same keyword for multiple purposes?

idiomancy22:09:49

Im not sure I can picture the scenario youre describing

idiomancy22:09:59

I mean... no?

idiomancy22:09:38

lol, I feel like I must be a loser, but I've just never "run out of names"

bfabry22:09:15

:id, :name, :application, :token, :parent?

idiomancy22:09:12

well, for something like :id i'd use ":entity-id" in general, if im expecting a completely different concept of the value

idiomancy22:09:42

but I see what you mean, :entity/id is certainly better

idiomancy22:09:54

but i still dont see the need there for "::entity/id"

idiomancy22:09:59

like, the namespacing

idiomancy22:09:33

really, it just means that slashes look better than hyphens

bfabry22:09:11

look better but also get filled in for free with :: and aliases

bfabry22:09:36

and namespaced maps

idiomancy22:09:37

maybe im starting to see the autorefactoring argument

idiomancy22:09:45

kind of? guhh, Id need to see the codebase. I still cant quite picture it.

bfabry22:09:46

I mean it's definitely true that you can get largely the same benefits through using longer more descriptive keywords. but no one does. clojure is just adding a bunch of tooling and encouragement to make it easier and more common

idiomancy22:09:55

I'll watch rich hickey's talk, hopefully it will help me out.

quoll22:09:56

If I have a list of: ‘(+ 2 3) then I can eval it and it will return 5. What if I have a list of ‘(+ 2 x) ? Is there some way I can eval it in an environment where I can bind the symbol ‘x to a value? The best I’ve managed is:

(def x) (with-redefs [x 3] (eval the-list))
but this is hard coded to a symbol called “x”, and it pollutes the environment with a var. Similarly for dynamic vars and bindings. I also can’t use a macro, because the-list will only get the expression at runtime, and the symbol isn’t fixed either. I feel like I’m missing something obvious here….

gfredericks22:09:47

@quoll where are you getting the list from?

quoll22:09:54

it’s being built

gfredericks22:09:01

this is fundamentally a runtime thing?

gfredericks22:09:49

(eval `(let [~'x ~3] ~the-list))

bfabry22:09:28

(eval (replace {'x 3} the-list)) 😛

quoll22:09:45

I’ve been tempted to do this, but the expression has the potential to be more complex. It will also be inside a tight loop

bfabry22:09:32

(eval (clojure.walk/prewalk-replace {'x 3} the-expression)) 😆

gfredericks22:09:14

@quoll what does tight loop mean? You'll be evaluating the same expression multiple times with different values for x?

quoll22:09:40

it’s executing a filter in a database query

gfredericks22:09:04

(let [f (eval `(fn [~'x] ~the-list))] (f 3) (f 4) (f 5) etc...)

quoll22:09:04

and the filter is built from an expression that the user entered in the query language

hiredman22:09:17

what you want to do is walk the expression finding free variables, then create a function using eval, that has the free varaiables as parameters

hiredman22:09:31

like what @gfredericks said, but not hard coded to x

gfredericks22:09:36

@quoll is the query language just arbitrary lisp? or is it a really short whitelist of ops?

quoll22:09:48

for now, a short whitelist of ops

quoll22:09:14

over time, more ops would be implemented. No, it won’t allow someone to create arbitrary lisp

gfredericks22:09:18

I guess eval might be fast enough for an http request

gfredericks22:09:56

it's less than a millisecond

quoll23:09:38

Hmmm, that’s a point. I hadn’t considered how long eval would take

gfredericks23:09:44

little languages like that ofter aren't difficult to compile by hand though

hiredman23:09:34

eval is calling in to the clojure compiler, compiling to jvm bytecode, loading the class containing the bytecode, and running it

quoll23:09:44

I was also trying to avoid evaluating the expressions by hand. I’ve done that before in Java, but since I had this magical thing called “eval” I was hoping to leverage that

quoll23:09:12

Hmmm. It might be faster to compile a class (slow, but only once), and then call an invoke function on it (fast)

bfabry23:09:48

@quoll that's essentially what @gfredericks last snippet did

quoll23:09:36

re-read… OK. Yes. Half asleep, obviously 😳

hiredman23:09:11

in general, I would be suspect of any approach that translates your queries in to clojure, then calls eval

hiredman23:09:38

eval is for clojure, you language likely isn't clojure

quoll23:09:48

In any but a toy set of instructions, then I’d share the concern

quoll23:09:02

In this case, it’s mostly arithmetic operations

quoll23:09:24

maybe it’s easier to walk a structure and evaluate it manually

hiredman23:09:15

you could use tools.analyzer for the free variable analysis, but in that case you will definitely want to compile and re-use, tools.analyzer is complicated

hiredman23:09:01

if you have a simpler language to start with, you can do that free analysis first, before translating to clojure

hiredman23:09:11

which be way easier

gfredericks23:09:50

If the language doesn't have bound variables at all then stupid approaches can work