Fork me on GitHub
#clojure
<
2017-07-26
>
tianshu00:07:01

I'm trying to use environ, but using it at compile time(our build machine have all env vars), if I wrote (def ^:const x (env :x)) it works, x will be compile to a constant. but (def ^:const x (Integer/parseInt (env :x)))will not work.

danielcompton00:07:28

@doglooksgood are you doing AOT compiling? Or is this ClojureScript?

qqq00:07:29

@bfabry : why should delete be more expensive than assoc? can't we just 'mark it with a tombstone' ?

tianshu00:07:40

@danielcompton I'm doing AOT compile in Clojure.

seancorfield00:07:28

@qqq A look at the source for assoc on vectors should convince you that delete would be more expensive -- assoc doesn't change any of the indices for the vector so only the segment containing the "changed" item needs to be updated. delete would cascade a change of indices across the segments of the vector. As I understand how vectors are implemented in Clojure -- happy to be corrected.

seancorfield00:07:33

@doglooksgood Aside from all the myriad problems associated with AOT and why you should avoid it... ahem ...could you be a bit more specific about "will not work"? What exactly is the error/behavior you get?

qqq00:07:38

@seancorfield : I failed to consider indexing. Does vector maintain the invariant "the only non-full 32-item block is the last block" ?

bfabry01:07:45

I'm not sure about the internal implementation, but vectors guarantee constant time index lookup. seems hard to have both that guarantee and a fast delete-at-index op

qqq01:07:34

vectors guarantee a (log_32 num-elements) time lookup, it's stored as a btree with branching factor of 32

qqq01:07:18

I think it's possible to store "how many elements does this subtree have" , which also allows fast lookup

bfabry16:07:59

you're right, log32n. so if you allowed delete you'd have to rebalance the tree right? honestly I haven't thought about data structure algorithmic performance since uni, but I just kind of trust that this probably isn't feasible. I'm sure the fact that the data structures are persistent makes it even more complicated

qqq16:07:25

disclaimer: I haven't read the actual source code so now we both agree that a persistent vector is a tree with depth log32n this means that assoc is NOT O(1), but O(log_32 n) the reason being, when we update a node, we have to update "all anvestors" until we get to the root

qqq16:07:57

since vectors are persistent => assoc has to create a new node => but then it has to create a new block for every anvestor from the thing we want to update until we get up to the root

qqq16:07:31

so then the question is: can we do delete! in O(log_32 n) time, whilemaking it easy to index -- and I don't know, but I'm leaning towards yes

tianshu00:07:44

if I use (def ^:const x (env :x)) and :x in env is "hello", it will be compile to (def ^:const x "hello") i think, because our build machine have environment variables, and production machine dont, this is the way we want. if I use (def ^:const x (Integer/parseInt (env :x))), x will not be compiled to a literal constant. so at runtime, this (Integer/parseInt (env :x)) will run again.

hiredman01:07:09

^:const has a very small very specific use, that is not it, it only happens to "work" for (env :x) but that could change at anytime

tianshu01:07:15

what's the explain for this, what's the difference between these two?

seancorfield01:07:35

(I'm a bit surprised (def ^:const x (env :x)) works, to be honest)

bronsa01:07:33

^:const causes the value of def to be inlined at the invocation site, it doesn't care about how the value is produced

bronsa01:07:05

(env :x) is fine if that returns an value that can be used as a constant

bronsa01:07:23

const doesn't mean that the literal expression passed to def will be inlined, (def ^:const x (do (println "foo") 2)) the println will only ever be runned once

tianshu02:07:52

and x will be 2?

tianshu02:07:37

and x will be 2?

qqq04:07:44

I know how to write macros. Is it easy to write reader macros?

qqq04:07:20

I'm trying to implement a special macro (:: a t-sig) where it always evals to a, regarxless of where the :: is located at

qqq04:07:34

so I can write something like

(defn [ (::a int) (:: b int)] .... )

qqq04:07:44

and it should become

(defn [a b] ...)

seancorfield04:07:28

@qqq Not sure I'm understanding you but Clojure does not support "reader macros"...

seancorfield05:07:04

Tagged literals begin with # -- like #inst and #uuid -- but that's not "reader macros".

seancorfield05:07:34

Tagged literals have #, a namespace-qualified symbol, and a regular Clojure expression. The regular Clojure expression is read, then passed to the function associated with that symbol.

seancorfield05:07:18

@qqq We use tagged literals in our configuration library at work so we can define values in "special" ways.

qqq06:07:09

@seancorfield : (I know nothing about tagged literals / reader macros) -- so what you're saying is that (1) what tagged literals get is after macro expansion and (2) it's basically a function call with a single argument ?

rauh07:07:38

How come tools.deps.alpha is using a map (which is merged with the default deps)? Doesn't that mean I can't specify the order of the deps? Which, IMO is crucial on the JVM.

Alex Miller (Clojure team)15:07:49

This is discussed in my talk, but if order matters, then your system is already broken. That is, you should never have two different versions of the same class on your classpath - if you do, then something bad has already happened.

henrik07:07:25

Just tried out Compojure API (2.0). https://github.com/metosin/compojure-api

henrik07:07:41

I had no idea it was this easy to set up an API. Is this normal?

pesterhazy08:07:04

@rauh. Do you mean the order of maven coordinates in project.clj? I wasn't aware that ordered mattered there

mpenet08:07:04

it matters anywhere (boot, lein etc) I think, it's a maven property

rauh08:07:25

@pesterhazy No I mean the new tools.deps.alpha project that just got released. In maven all of the order matters, which makes projects predictable. Though there is a bug report for leiningen too: https://github.com/technomancy/leiningen/issues/2283

mpenet08:07:35

but yes, I think @rauh is right about that

rauh08:07:32

It's going to seeminly work for projects that have <=8 dependencies and then all in a sudden the depedencies will be semi random and even change if you just change the version number.

rauh08:07:56

So IMO, deps shouldn't be specified by a map.

mpenet08:07:07

it's prolly worth raising the issue with @alexmiller & co

rauh08:07:23

Yeah I'll wait until they wake up in the states.

pesterhazy08:07:12

@rauh, I was aware of tools.deps but didn't know order influenced the maven algorithm

pesterhazy08:07:13

Maybe just order apathetically?

pesterhazy08:07:31

Whoops alphabetically

pesterhazy08:07:55

A wonderful typo though

mpenet08:07:40

sorting wouldn't help

mpenet08:07:52

> Dependency mediation - this determines what version of a dependency will be used when multiple versions of an artifact are encountered. Currently, Maven 2.0 only supports using the "nearest definition" which means that it will use the version of the closest dependency to your project in the tree of dependencies. You can always guarantee a version by declaring it explicitly in your project's POM. Note that if two dependency versions are at the same depth in the dependency tree, until Maven 2.0.8 it was not defined which one would win, but since Maven 2.0.9 it's the order in the declaration that counts: the first declaration wins. https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html

pesterhazy08:07:42

At least it would make it predictable

mpenet08:07:05

but it prevents you to have control over it

pesterhazy08:07:39

But yeah deviating from maven here doesn't seem like a good idea

mpenet08:07:44

I imagine deps could support a vector of "tuples" and map transparently, use the former if you care, the other if not (but since maps make ordering unpredictable I am not sure that even make sense to support them at all)

rauh08:07:27

Yeah I wish the new t.d.alpha was leaning heavily on maven and would just provide a hiccup wrapper around their XML config. That'd be neat.

pesterhazy08:07:46

to be fair I've never been in a situation where I had to re-order maven coordinates in clojure to fix dependencies. Have you?

pesterhazy08:07:07

Can't you accomplish the same thing through exclusions?

mpenet08:07:50

imho it doesn't really matter, if the aim is to have a simple tool that kind of maps 1-1 with maven without too much indirection, that ordering issue could be considered a flaw

mpenet08:07:12

but no, never had to mess with ordering myself I think

rauh08:07:13

It's seldom and only matters if you have the same files in two jars. Which you shouldn't have. But then, it's not clear how often this problems has been the issue with user reported problems who never figured it out what the issue was.

gmercer08:07:22

@pesterhazy we have had to do this many times when using xml

mpenet08:07:08

deps conflicts is quite common, ex between guava versions in transitive deps it happens to me all the time

rauh08:07:17

IMO predictability is crucial, so deps should neither be put into a set (as leiningen does currently) nor into a map.

isankhairul08:07:38

hi everyone,…i want ask, sqlkorma how set date and datetime mysql format timezone GMT ?

pesterhazy08:07:09

@rauh, wait why is it a set? do you mean internally?

danielcompton09:07:24

@rauh my (limited) understanding is that you're meant to resolve all dependency conflicts completely, so it would actually be ok to store them as a map

pesterhazy09:07:38

@danielcompton how would that work given that there are transitive dependencies, java dependnecies, etc.?

pesterhazy09:07:52

do you mean a complete spec in the style of yarn.lock?

danielcompton09:07:21

I thought the idea was that you completely resolved all of the transitive dependencies explicitly

mpenet09:07:44

more like, being explicit about exclusions to solve these kind of issues, but still

mpenet09:07:01

(which is the best way to fix these in general anyway)

mpenet09:07:31

@danielcompton ahh? didn't understand it that way.

danielcompton09:07:51

yeah I think you're closer @mpenet

danielcompton09:07:26

but I thought idea is that there is no conflicts

danielcompton09:07:39

sort of like lein pedantic

mpenet09:07:07

I see, that would make sense, and actually an improvement over raw maven definitions (arguably, since way more verbose)

rauh09:07:57

@danielcompton My issue isn't only about transitive deps, but about the non-predictable ORDER of the jar's on the classpath. (See my leiningen ticket)

danielcompton09:07:50

Does the order matter for you because you have several JARs which provide the same namespaces/classes?

pesterhazy09:07:58

alex mentioned the classpath order in the talk here in berlin

pesterhazy09:07:14

iirc, he said that the order is useful for some things (like overriding packages a la carte) but is not a great fit for other things, like making sure the right dependencies are picked up

pesterhazy09:07:53

so it sounded to me like tools.deps is intended to take classpath order out of the equation somehow

t0m09:07:41

hey, anyone here who can help out with a boot-clj related question?

kurt-o-sys09:07:45

well... probably someone, just ask, but maybe in the #boot channel...

andrea.crotti10:07:58

what could be an easy way to some a random choice given some probabilities I just need a function that given something like this: {:a 1/2 :b 1/4 :c 1/4}

andrea.crotti10:07:33

returns me a random element from [:a ':b :c] respecting the probabilities for each of them

qqq10:07:49

put the cnhoices in arbitrary order

qqq10:07:54

sample randomly from [0, 1]

qqq10:07:02

index into the cumulative probability

andrea.crotti10:07:32

yeah I thought about that, so I guess there is no easier way?

sundarj10:07:06

wouldnt repeating each choice depending on the probability and then using rand-nth do the trick?

andrea.crotti10:07:22

that would work in a simple case like this one

andrea.crotti10:07:35

but in the general case I would need to find the LCD of all the fractions

andrea.crotti10:07:45

and generate a massive vector with all the repeated stuff

sundarj10:07:54

right, i see

qqq10:07:57

suppose an item has probability .3957 , do you repeat it 3957 times? 🙂

sundarj10:07:39

fair point

andrea.crotti10:07:45

Something like this gives me the ranges I can then check

user> (def probs {0 1/2 1 1/4 2 1/4})
#'user/probs
user> (into {} (map-indexed
                (fn [idx [el prob]]
                  [el
                   [(apply + (take idx (vals probs)))
                   (apply + (take (inc idx) (vals probs)))]])
                probs))
{0 [0 1/2], 1 [1/2 3/4], 2 [3/4 1N]}

andrea.crotti10:07:08

quite sure it can be improved a lot though

hmaurer11:07:35

I am pondering about how I would implement a simple compiler and/or interpreter in Clojure. In particular, I am wondering what would be the best way to model the abstract syntax tree. The first (and most obvious) option would need to represent AST nodes as maps, tagged with their type/label (e.g. :type key). I would then define the various functions that operate on the AST as multimethods using the node’s types as dispatch keys. Another option would be to represent each type of node as a record, and to then have them all implement a protocol (e.g. IExpr) which would contain have implementations for all the functions operation on AST nodes. This comes wth the added benefits that AST nodes could implement existing protocols too. Which of these options would be most idiomatic in Clojure? Or do you have another one to suggest?

a1311:07:27

Take a look at Instaparse

mpenet11:07:54

clj-antlr isn't bad either

hmaurer11:07:33

Which parts in particular? I am not looking to write a parser, just to understand what the best way to model an AST in clojure would be

a1311:07:59

multimethods are nice, I think

a1311:07:28

I made a notice about instaparse because if you use it — you don't have to think about AST representation yourself

hmaurer11:07:25

ok thanks, I’ll take a look

bronsa11:07:34

@hmaurer take a look at tools.analyzer

hmaurer11:07:19

thanks! so from that I gather that raw maps with a :type field seems to be the way to go?

bronsa12:07:10

it's the most common way at least

hmaurer11:07:05

Another, completely unrelated question… Does anyone have advice/examples on how to structure application business logic? This is a fairly general question (not specific to Clojure), but there might be Clojure-specific idioms coming into play. Roughly speaking I am wondering if I should take a CQRS-y road and try to have a clear distinction between functions that modify the app’s state and functions that read that state. The application I am currently working on is a fairly simple, CRUD-y Clojure+Datomic web app, but I would like to use it as a learning ground for good practices

noisesmith17:07:35

I’d start with the general concept of event sourcing, which is more general and more usable than CQRS

hmaurer18:07:54

@noisesmith right, sorry, I think my question was misphrased. Right now I am more concerned about how to structure my code at the application level (how to organise functions and manage side effects)

noisesmith18:07:09

event sourcing is a strategy for this

hmaurer18:07:14

Datomic already gives me some level of event sourcing, which is enough for my needs

noisesmith18:07:32

the concept is that events are immutable and describe the actual domain data

noisesmith18:07:53

then, your db describes state, and is created via a reduce across the events (literally or conceptually)

hmaurer18:07:56

@noisesmith i mean, I am familiar with what event sourcing is, but I don’t see how it would help with this

noisesmith18:07:12

because now your state is just a query across your events

noisesmith18:07:17

it’s an optimization

noisesmith18:07:49

so you have “all events”, from that you derive a postgress db, or a datomic db, or even a mongo db - via looping over the events, maybe with reduce

noisesmith18:07:54

that’s your state of the world

hmaurer18:07:24

Right, but Datomic already gives a fairly event-sourced model (except that transactions dont’ represent domain actions, but they can be reified with a key that does represent a domain action)

noisesmith18:07:28

now, if your events are set up properly (strictly ordered, immutable) - all instances of your app have access to the same time series of immutable states

hmaurer18:07:59

Datomic’s transaction log can be considered as the event log

noisesmith18:07:00

queries are reads of the current state, “modifications” are insertions into the stream of events (you must loop back up to that level)

noisesmith18:07:04

right, right

noisesmith18:07:17

so you are on the right track if using datomic, you don’t need CQRS for this

hmaurer18:07:49

My concern is more: how do I nicely separate my “business logic” (functions that perform transactions, authorisation and reify transactions with additional infos) from my API

hmaurer18:07:58

in the application code

hmaurer18:07:02

sorry if this wasn’t clear

noisesmith18:07:25

I use protocols to describe my domain / API level abstractions

hmaurer18:07:25

what should the interface for those functions be to make them easy to think about and deal with, etc

noisesmith18:07:49

then for my implementation, I use functional code over vanilla data structures implementing those protocols

noisesmith18:07:20

the protocols are used as a signal to a reader of the code / user of the library that these names describe domain level concepts, they are the big picture organization of the code

hmaurer18:07:32

do you have an example of this structure?

noisesmith18:07:53

I’ll have to see if I have a good open source implementation of it…

hmaurer18:07:57

so protocols are your “public API”, and all other functions are implementation details

noisesmith18:07:18

right - and the protocol methods are expected to take and return hash maps, vectors, keywords, numbers

noisesmith18:07:51

so we don’t pile on mountains of OO, we use modeling tools on the boundaries as a line in the sand, so to speak - to declare organizational intention

hmaurer18:07:52

how do you pass context around (db, conn and auth)? and how do you perform authorisation / reified transactions, if you do so?

noisesmith18:07:20

I use component, so that each subsystem gets the parts of the app that it needs passed in on initialization

hmaurer18:07:33

Could you give me a example of a protocol definition you use in one of your apps?

noisesmith18:07:10

absolutely - found it!

hmaurer18:07:41

and brilliant, thanks!

noisesmith18:07:57

oh man - that project is in a slightly weird state, there’s two GameBoard protocols that should have different names, and some protocols that need to be moved to the proto namespace

hmaurer18:07:03

if those functions were hitting a database, would your record hold its ref?

noisesmith18:07:05

sorry ! it’s still in heavy development

noisesmith18:07:21

the record would expect the ref, yes

hmaurer18:07:29

And if I want to reify every transaction with, say, the access token of the current user, how would you do this? Would you provide your own “transact” function, wrapping Datomic’s?

hmaurer18:07:40

Sorry for the messy questions

noisesmith18:07:50

that’s a usage of reify that doesn’t match what I thought the word meant

noisesmith18:07:13

do you mean parameterize? I thought reify meant “make an abstract thing into a concrete one”

hmaurer18:07:59

I thought that’s a usage I read in the doc

hmaurer18:07:02

I might be mis-using the term

hmaurer18:07:20

I should have said “add an attribute to the reified transaction”

noisesmith18:07:32

ahh! now I get it, thanks

noisesmith18:07:52

so yeah, I would make an object that represents that reifications including the user id

noisesmith18:07:25

this is getting deeper into datomic than my working knowledge of it - I’ve taken a workshop but not gotten far with it in real usage

hmaurer18:07:26

@noisesmith haha ok, no worries. In general though, would you consider it “good code” if I wrapped all access to Datomic inside a protocol (e.g. IDatomicReaderWriter or something) so as to control how every transaction is made, and add some data to the transactions as I see fit?

noisesmith18:07:40

I’d first see if this is an abstraction datomic itself allows

noisesmith18:07:08

unless your goal is to be able to swap in another database (which probably means forgoing a bunch of the features that make datomic worth it?)

noisesmith18:07:50

I wouldn’t bother abstracting things that pragmatically wouldn’t be worth replacing ever

hmaurer18:07:51

I wouldn’t want to swap out Datomic. The only thing I would want is “intercept” calls to datomic/transact to add some data to the transactions

hmaurer18:07:14

e.g. add the “current user id” as an attribute on the transaction

noisesmith18:07:30

I’d say make your own function over transact that adds the data, probably parameterized with a hash map so you can generalize and introspect

hmaurer18:07:31

so that I don’t have to do this manually everywhere I call datomic/transact in my application

hmaurer18:07:02

ok; thanks!

noisesmith18:07:51

one thing to avoid is opaque wrappers (whether partial or an Object with hidden state - which btw is what a partial or closure is) - use a record implementing datomic’s own protocol if possible, but parameterized by keys you can introspect on the record and access in context

hmaurer18:07:07

@noisesmith is there a repl command to get the list of all protocols implemented by an object?

hmaurer18:07:14

well, the record of which an object is an instance

hmaurer18:07:44

ah right because protocols are just interfaces

noisesmith18:07:46

oh, supers needs the class, but that’s easy enough

noisesmith18:07:04

=> (supers (class {}))
#{clojure.lang.IKVReduce clojure.lang.IFn clojure.lang.IMapIterable java.io.Serializable java.lang.Object clojure.lang.IObj clojure.lang.IMeta java.lang.Runnable clojure.lang.MapEquivalence clojure.lang.IHashEq clojure.lang.ILookup clojure.lang.IPersistentMap clojure.lang.Counted clojure.lang.IEditableCollection clojure.lang.Associative java.lang.Iterable clojure.lang.IPersistentCollection clojure.lang.AFn java.util.Map java.util.concurrent.Callable clojure.lang.Seqable clojure.lang.APersistentMap}

hmaurer18:07:06

wef-backend.core=> (supers (type (get-conn)))
#{#<Class@35fc6dc4 java.lang.Object>
  #<Class@e7b265e clojure.lang.IType>
  #<Class@6b337969 datomic.Connection>}

noisesmith18:07:33

cool - so you can make a defrecord that implements Connection - the others come free with defrecord

hmaurer18:07:44

is Connection a protocol there?

noisesmith18:07:00

this is the point where I end up reading source code usually, heh

noisesmith18:07:10

I bet it’s documented … somewhere

hmaurer18:07:25

I’ll look into it. Thanks 🙂

hmaurer18:07:33

I’ll ask @U0509NKGK , I am sure he has insights on this

noisesmith18:07:12

cool - thanks for asking about this, I learned a couple of things in trying to find your answer

hmaurer18:07:41

@noisesmith Glad to hear that! I was afraid I wasted a bit too much of your time

hmaurer18:07:58

In this article they wrap d/transact, as you were suggesting

noisesmith18:07:30

well - they do it slightly differently than I suggested, because the user-id org-id and tx-data are totally hidden once you call defn

noisesmith18:07:41

err, I mean once you call transact-wrapper

noisesmith18:07:53

the return value of that function doesn’t expose any of those things as data

hmaurer18:07:56

@noisesmith do you think that’s an issue? it seems to simplify the life of the caller, especially if he doesn’t care about those

hmaurer18:07:22

cluttering every function that calls transact with auth data that it doesn’t care about sounds problematic

noisesmith18:07:50

@hmaurer until you are trying to debug code using the wrapper (in my experience) - it’s not always neccessary to use the alternative of using a record to store the data instead of a closure, but what this gains is quick access to what the thing actually encompases

noisesmith18:07:47

you don’t clutter - the record itself is something you can call if you implement IFn - or you just expect people to use a protocol method with it as the first arg (also fairly reasonable but less fancy)

hmaurer18:07:15

@noisesmith sorry, I think I am missing something. Can you give me a code example of what you mean?

hmaurer18:07:37

Just a quick snippet here on Slack

noisesmith18:07:32

OK - I was just looking at this right now, sorry about the distracting details, but the big picture structure should be illustrative of what I am saying

(defrecord Transmitter [transmit from user-data creator to journey routing]
  IFn
  (call [this] (.invoke this [nil this]))
  (run [this] (.invoke this [nil this]))
  (applyTo [this coll] (.invoke this (first coll)))
  (invoke [this [routing-override message]]
    (let [updated (into this message)
          routing (or routing-override routing (first journey))]
      (.invoke this routing updated)))
  (invoke [_ routing-override message]
    (let [{:keys [transmit generic]} message
          message (dissoc message :transmit :generic :routing :from :to)
          {:keys [journey routing message]}
          (if generic
            {:journey [routing-override]
             :routing :generic/reply
             :message (assoc message :generic-raw [routing-override message])}
            {:journey (rest journey)
             :routing routing-override
             :message message})
          {:keys [request-id birth-time]} user-data
          final-message (assoc message :journey journey :mediary to)]
      (when-let [schema-error (check-schema from routing final-message)]
        (log/error ::Transmitter
                   "for routing"
                   (pr-str routing)
                   (pr-str {:journey journey})
                   (pr-str schema-error)))
      (log/trace ::simple-kafka-transmit routing "to" request-id "from"
                 birth-time "-" (pull-transmit-info message))
      (transmit from routing final-message))))

noisesmith18:07:27

the transmitter has all this incidental data - who is sending? who is the target? what data did the initiator of the request expect to get back with any responses? what is the path the overall task should take through the system?

hmaurer18:07:37

But that’s basically the approach I was suggesting with a “DatomicWriter” protocol, no?

noisesmith18:07:37

v1 wrapped this in calls to partial

hmaurer18:07:42

that “wraps’ transact

noisesmith18:07:01

the difference is that this returns an object that acts like a function

noisesmith18:07:42

maybe I misunderstaood what @(d/transact ...) is in the transact-wrapper function

hmaurer18:07:46

right, but so instead of doing something like

(transact transmitter conn tx-data)
you would do
(transmitter conn tx-data)

hmaurer18:07:11

@noisesmith (d/transact returns a promise I think, and @ dereferences it

noisesmith18:07:29

and if you look at transmitter it shows you all the data it has inside

noisesmith18:07:33

it acts like a hash-map

noisesmith18:07:36

that’s the key thing to me

robert-stuttaford18:07:20

what was the question 🙂

hmaurer18:07:38

@U0509NKGK Hi! 76 messages to read 😄

hmaurer18:07:48

just kidding, let me clarify the question:

hmaurer18:07:28

I want to add some attributes to every transaction in my sytem (e.g. for audit purposes; things like the current user ID) and I am wondering how I should do it

noisesmith18:07:52

your question suddenly sounds very focused and pragmatic

hmaurer18:07:53

e.g. should I wrap all access to Datomic behind a protocol that proxies most calls but does some stuff to transact’s tx-data before proxying

hmaurer18:07:06

should I have an explicit function wrapping d/transact

hmaurer18:07:28

I guess that’s what talking about something for 80 messages does

hmaurer18:07:30

it clarifies things

robert-stuttaford18:07:30

we use an explicit function wrapping d/transact and d/transact-async

hmaurer18:07:21

@U0509NKGK do you pass the auth context to all your “business functions” and then pass it explicitly to your function wrapping d/transact every time you call it?

robert-stuttaford18:07:05

no; we use middleware and binding with a dynamic var

robert-stuttaford18:07:11

and if the var has a value, we annotate

hmaurer18:07:24

I was told using dynamic vars will send me straight to hell

robert-stuttaford18:07:42

we have repl helpers that do the binding so that we also use it when manually altering the db at the repl

hmaurer18:07:56

do you somehow ensure that no intern misadvertantly uses d/transact instead of your wrapper?

robert-stuttaford18:07:47

no. but in 5 years, it’s not been a problem. we had ONE case where someone retracted more than they should have. it was easy to fix

robert-stuttaford18:07:01

i do live i fear of an accidental d/delete-database though

hmaurer18:07:09

Ok, thanks a lot. One other thing: how do you handle security? Specifically, do you use d/filter?

robert-stuttaford18:07:26

we can’t use d/filter ; we have too much sharing going on

hmaurer18:07:30

yeah, it’s a bit odd there is no way to prevent /delete-database tbh

hmaurer18:07:40

I guess with backups every 24 hours it eases the fear

robert-stuttaford18:07:46

d/filter is nice if you have very strict boxes. we don’t.

robert-stuttaford18:07:54

hah. try continuously

hmaurer18:07:55

although I wonder what would happen if the db got deleted, re-created, then a backup was run

hmaurer18:07:01

would it get rid of the incremental backup?

hmaurer18:07:19

how do you do access control them? do you have a nice approach?

robert-stuttaford18:07:55

we replicate our backups to off-AWS places, so we have some protection against that

robert-stuttaford18:07:06

access control what, the backups, or the repl access?

hmaurer18:07:42

data access control

hmaurer18:07:52

e.g. which users can see what

hmaurer18:07:55

enforcing it

robert-stuttaford18:07:09

oh, that’s basically normal queries in middleware

hmaurer18:07:32

@U0509NKGK middleware as in you filter the query after executing it?

robert-stuttaford18:07:09

no, simpler than that - we explicitly use the viewing user in queries

hmaurer18:07:14

what about the entity API?

robert-stuttaford18:07:43

very often we use datalog to find valid entities, map d/entity, and go from there

robert-stuttaford19:07:01

sometimes we d/entity on a lookup ref, query to validate access, and continue

hmaurer19:07:56

right, what I mean is that d/entity will let you traverse the data tree without access control

hmaurer19:07:06

but I assume so long as you control what is traversed, it’s fine

hmaurer19:07:25

I am using GraphQL on my app and d/entity could cause issues if I tried to use it directly

hmaurer19:07:35

and let the graphql query resolvers hit it directly

hmaurer19:07:55

as it could potentially go any level deep in the data tree, reaching data that should not be accessible to the current user

hmaurer19:07:57

if that makes any sense

robert-stuttaford19:07:58

oh right, yes. we totally control the query. we don’t allow arbitrary query from clientside

robert-stuttaford19:07:16

in that case, d/filter is a far safer approach

hmaurer19:07:48

@U0509NKGK ok, thanks. Sorry, my questions are not very clear today

robert-stuttaford19:07:01

it’s ok 🙂 hth

hmaurer11:07:15

A link to an open-source application that you consider well-structured would be helpful, or advice / link to blog posts / resources

manutter5112:07:03

@hmaurer have you seen the docs for re-frame? He talks extensively about CQRS-y architecture (even though it’s cljs not clj) https://github.com/Day8/re-frame

manutter5112:07:09

There’s a pretty detailed readme doc right up front, but don’t be fooled, the real meat is in the docs/ folder

hmaurer12:07:43

Oh wow, indeed

hmaurer12:07:16

@manutter51 ah, let me check it now, thanks! By the way, something I should have specified: I am looking for a way to structure my code while keeping it fairly straightforward. E.g. no crazy async at the moment; ideally I would like to keep everything synchronous

danielgrosse13:07:58

When I do some calculation with pmap and want to use another pmap on the result. How I can I wait until the first pmap is done?

Matt Butler14:07:40

To clarify your intention is to load the entirety of output of the first pmap into memory, then consume it once its realised?

Matt Butler14:07:09

If that's the case simply wrap the first pmap in a doall

(pmap #(println "b" %)
  (doall (pmap (fn [x] (println "a" x) x)
(range 1000))))

danielgrosse11:07:33

Thanks, that helped me

Garrett Hopper13:07:35

Does anyone know of a clean way to assoc in a field only if the field isn't null; otherwise return the map unmodified?

kurt-o-sys13:07:01

merge will overwrite existing keys in the original map... this may (or may not) be the expected behaviour.

dpsutton13:07:29

change the order

Garrett Hopper13:07:53

I'm aware. That is what I want. It's just an optional field that should be tacked on only if it's not nil. (An authorization http header)

dpsutton13:07:57

(merge {:field "value if not present"} my-map)

dpsutton13:07:07

yeah exactly

Garrett Hopper13:07:51

I don't suppose there's a merge that would do (merge {:a 1} {:b nil}) and return {:a 1}?

dpsutton13:07:53

that sounds orthogonal to merge. you want to prune your map after the merge?

dpsutton13:07:51

if it's just top level keys you could (into {} (map (fn [[k v]] (when v [k v])) my-map) would probably work

dpsutton13:07:34

there's probably an even better sort with filter

dpsutton14:07:00

(into {} (filter (fn [[k v]] v) my-map)

dpsutton14:07:17

and if you have possibility of false change the body of the fn to (some v)

Garrett Hopper14:07:05

All great ideas. I've gone with (merge xs (when a {:a a})).

dpsutton14:07:30

that will clobber your original value i thought

dpsutton14:07:35

you need your original map second

rauh14:07:48

@ghopper In that case you could do: (cond-> xs a (assoc :a a))

Garrett Hopper14:07:20

Sorry, I'm mixing names. xs doesn't have a :a yet. There's no possibility of clobbering.

Garrett Hopper14:07:43

@rauh I quite like that one. :thumbsup:

Garrett Hopper14:07:53

Is there a better way to do (symbol *ns* 'symbol)?

Garrett Hopper14:07:20

Huh, nevermind. Apparently I can do syntax quoting for the dispatch value of a multimethod. Not sure why I thought I couldn't earlier.

kurt-o-sys14:07:13

@ghopper Not sure if it would help, but https://github.com/nathanmarz/specter has some really 'navigators' to do all kinds of transformations on data structures. (Maybe not worth the effort for easy navigation, although I think it may be if you have at least 2 steps to navigate, including predicates 🙂 )

Garrett Hopper14:07:41

@kurt-o-sys Huh, that's cool. Definitely overkill for this though. 🙂

nathanmarz14:07:10

@ghopper for associng a value if not null, with specter it's (setval [:a some?] my-value amap)

hmaurer14:07:34

@kurt-o-sys are those basically lenses?

nathanmarz14:07:37

for associng only if field exists, it would be (setval (must :a) my-val amap)

nathanmarz14:07:53

will be way more performant than merge as well

kurt-o-sys14:07:53

I didn't use specter much yet - only found about it about a month ago - but it's definitely cool. Also for small cases.

jballanc16:07:02

Style question: say I have three items (def a ["a"]) (def b [["b" "c"] ["d" "e"]]) (def c "f"), and I want to put them into a list like so:

`[~a ~@b ~c]
but without using syntax-quote/unquote/splicing-unquote...how would you do it as concisely as possible (without losing generality)?

jballanc16:07:16

(i.e. the desired result is [["a"] ["b" "c"] ["d" "e"] "f"])

dpsutton16:07:43

so you just want to append them?

noisesmith16:07:56

the middle one is unpacked

noisesmith16:07:20

one option is (concat [a] b [c]) - I admit that looks weird

jballanc16:07:47

@noisesmith actually, that's not nearly as bad as what I was doing

jballanc16:07:08

Does suck a bit that you have to pack the elements you don't want unpacked just to...well...yeah

noisesmith16:07:50

@jballanc depending on your actual problem, you could use something like (def prepacked [x] (if (and (coll? x) (vector? (first x)))) x [x]) (mapcat prepacked [a b c])

jballanc16:07:59

hah...yeah, that probably won't work because some of these are multiple levels nested, and only some of the levels need to be unpacked

jballanc16:07:21

my personal preference is to "say what you mean" with syntax-quote, but understandably the team is worried about maintainability...

shreyas.n18:07:48

Hey there. How to update a key in a map to hold a empty vector?

shreyas.n18:07:51

=> (def Person {:person-id "person-1" :category "customer" :purchase ["p1"] :dates ["d1"]})
#'user/Person
=> (update :purchase Person [])
IllegalArgumentException Key must be integer  clojure.lang.APersistentVector.invoke (APersistentVector.java:292)

hiredman18:07:08

look at the docs for update

hiredman18:07:40

(you seem to have picked random arguments in a random order)

hiredman18:07:30

(and you want assoc not update, but go ahead and read the docs for both assoc and update)

shreyas.n18:07:35

dumbo! i was using the wrong function here. Thanks @hiredman

hiredman18:07:36

by docs I meant the docstring you access using doc in the repl, but you may also want to start here https://clojure.org/reference/data_structures#Maps

hmaurer18:07:00

Hi! I am getting a cryptic error which I do not understand. Here’s the piece of code: https://gist.github.com/hmaurer/8e786bfd507798393c8be45ffb3a1b46. The error is in the comments. Could someone take a look please?

hmaurer18:07:21

The error is Can't let qualified name, but I don’t get why the gensym’ed variable gets qualified by the syntax quote

hiredman18:07:41

I would double check to make sure the code that is being run actually matches the code you are reading (restart your jvms) and then I would suspect this is actually coming from some other macro

hmaurer18:07:11

I restarted lein’s repl. Is that enough?

hiredman18:07:33

I would put a println at the start of the macro to print out ctx-binding because I suspect it is already being passed in a fully qualified symbol

hiredman18:07:56

your docstring is also in the wrong place

hmaurer18:07:13

ah, thanks for pointing that out

hiredman18:07:00

it is a super common thing to do

hmaurer18:07:07

ctx-binding seems fine. The println prints ctx

hmaurer18:07:33

which matches how I am using the macro, e.g.

(deftest test-authentication
  (with-scratch-ctx ctx
      ...

hmaurer18:07:55

Still throwing :cause "Can't let qualified name: wef-backend.test-util/conn# " though

hiredman18:07:07

is d/db a macro?

hmaurer18:07:15

but I get the same error even if I deleted the whole ~ctx-binding line

hmaurer18:07:42

I never got that error with uri# or datomic# by the way, it only occured when I added conn#

hmaurer18:07:50

It’s probably something silly

hiredman18:07:09

do you have a var named conn# in that namespace?

rauh18:07:29

@hmaurer What does macroexpand give you?

hmaurer18:07:58

@hiredman no, I checked. Let me check again just to make sure..

hmaurer18:07:28

java.lang.RuntimeException: No such var: wef-backend.test-util/conn#

hiredman18:07:44

you could fix it immediately by replacing most of the macro with a function that returns the context, and have the macro expand in to invoking that and then deleting it

hiredman18:07:04

but that wouldn't tell you what is wrong

hmaurer18:07:58

@rauh

(let*
 [uri__16604__auto__
  (clojure.core/str "datomic:mem://hello-test-" (java.util.UUID/randomUUID))
  datomic__16605__auto__
  (integrant.core/init-key :wef-backend/datomic {:uri uri__16604__auto__})
  wef-backend.test-util/conn#
  (:conn datomic__16605__auto__)
  ctx
  {:auth nil :conn conn__16606__auto__ :db (datomic.api/db conn__16606__auto__)}]
 (try (+ 1 2) (finally (datomic.api/delete-database uri__16604__auto__))))

hmaurer18:07:12

when running

(macroexpand '(wef-backend.test-util/with-scratch-ctx ctx (+ 1 2)))

rauh18:07:46

What happens when you rename it to some-other-conn#?

hmaurer18:07:01

tried that, didn’t work. Trying again now just to make sure

hiredman18:07:05

is that a real pound sign, or some unicode nonsense?

rauh18:07:25

Well the second usage of conn# expands properly. (in the map)

hiredman18:07:45

yeah, which makes me suspect the first has some weird shenanigans

hmaurer18:07:05

Wait, I think I found the issue

hmaurer18:07:13

some-other-conn# (:conn datomic#)]

hmaurer18:07:25

the “space” between # and ( has charcode 160

hmaurer18:07:28

instead of 32

rauh18:07:46

oh lol. 🙂

hiredman18:07:50

unicode malarky

dpsutton18:07:09

did you copy past some of this from a github page?

hmaurer18:07:21

nope, I wrote it myself

hmaurer18:07:24

no idea what happened there

hmaurer18:07:40

well, I copied an earlier version of that code, which worked

hmaurer18:07:48

then added that line myself

dpsutton18:07:49

from where?

hmaurer18:07:54

it might have picked up a character from somewhere

hmaurer18:07:57

I just checked and none of the characters from the snippet I copy/pasted had character 160

hmaurer18:07:05

sorry for the trouble

hiredman18:07:23

there is actually an open jira issue that has had some recent activity about changing how clojure handles unicode whitespaces

dpsutton18:07:29

char code 160 would be &nbsp;

drowsy18:07:05

the fun thing about this is, thats 160 is not even considered whitespace by java

hiredman18:07:15

oh, I guess the issue it got declined

hmaurer18:07:15

Probably what happened is that OS X and/or Atom (the editor I am using) has some shortcut or some way to enter this type of whitespace, and it fat-fingered on the shortcut

dpsutton18:07:22

employee-resizer.core> (char 160)
\ 
employee-resizer.core> \ 
\space

hmaurer18:07:15

it had been a while since I debugged a unicode issue; almost forgot how fun it is

dpsutton18:07:14

apparently atom will enter a non-breaking space with alt-space

dpsutton18:07:22

probably pretty easy to inadvertently hit

hmaurer18:07:38

yep, I most definitely inadvertently hit that

hmaurer18:07:38

If Clojure’s reader threw an error saying “unknown character at position X” it would have been easy/easier to debug, but it considered it as part of the symbol

hiredman18:07:49

that actually may be just be a bug in the reader

hmaurer18:07:15

Now I know how to confuse the hell out of people though

hmaurer18:07:58

wef-backend.core=> (def     42)
#'wef-backend.core/
wef-backend.core=>
42
wef-backend.core=>

hmaurer18:07:02

this works

hmaurer18:07:31

wef-backend.core=> (def hello world this is a long variable 99)
#'wef-backend.core/hello world this is a long variable
wef-backend.core=>

Garrett Hopper19:07:11

What is a fast way to do this without the repeated nested pointer?

(-> state
    (assoc-in [:a :b :b/field0] "")
    (assoc-in [:a :b :b/field1] "")
    (assoc-in [:a :b :b/field2] ""))

petterik19:07:29

;; maybe?
(assoc-in [:a :b] {:b/field1 "" :b/field1 "" :b/field2 ""})

bfabry19:07:44

fast as in performance?

Garrett Hopper19:07:05

Fast as in, using standard library. I don't want to add any external libs or anything.

Garrett Hopper19:07:10

Performance isn't a concern.

Garrett Hopper19:07:23

Sorry, I shouldn't be using that work. 😛

bfabry19:07:29

(merge state {:b/field1 "" :b/field1 "" :b/field2 ""})

bfabry19:07:58

sorry, (update-in state [:a b] merge {:b/field1 "" :b/field1 "" :b/field2 ""})

bfabry19:07:01

forgot a few words

dpsutton19:07:12

(let [nav [:a :b]]
  (-> {:a {:b {}}}
      (assoc-in (conj nav :b/field0) "")
      (assoc-in (conj nav :b/field1) "")))
{:a {:b {:b/field0 "", :b/field1 ""}}}

dpsutton19:07:41

the navigator is just a vector, so create it with the shared navigation parts

dpsutton19:07:56

if i'm understanding correctly

Garrett Hopper19:07:11

@dpsutton That's what I thought I was going to need to do. @bfabry I'm not sure why I didn't think of update-in... That should work nicely.

Garrett Hopper19:07:47

Not the question is update-in with assoc or merge. Is there any performance difference?

bfabry19:07:34

I suspect they'd be identical

Garrett Hopper19:07:58

I figured they would be. Anyways, thanks all!

hmaurer19:07:58

When I use lein, where will the clojure stdlib be located on my file system?

hmaurer19:07:03

.clj files that is

dpsutton19:07:23

(let [nav [:a :b]]
  (reduce (fn [m [k v]] (assoc-in m (conj nav k) v))
          {:a {:b {}}}
          [[:b/field0 ""]
           [:b/field1 ""]
           [:b/field2 ""]]))
{:a {:b {:b/field0 "", :b/field1 "", :b/field2 ""}}}

manutter5119:07:19

@hmaurer You mean the jar files?

hmaurer19:07:35

ah right, so the std lib will be within jar files. Nevermind then

bfabry19:07:57

jar files are just zip files and you can unzip them and the .clj files will be inside, if that matters to you

hiredman19:07:00

@hmaurer lein runs 2 jvms, lein's jvm and your project's jvm, the clojure runtime that lein uses is part of the lein jar and lives in ~/.lein, the clojure runtime for your project is Just Another Jar(tm)

manutter5119:07:27

lots of interesting jar files in ~/.m2/repository too

wei19:07:20

what’s a good way to xor two sets?

bfabry19:07:51

(set/union (set/difference s1 s2) (set/difference s2 s1))

noisesmith19:07:35

a silly way to do it

=> (into #{} (comp cat (take 2)) (clojure.data/diff #{1 2 3} #{2 3 4}))
#{1 4}

aaelony20:07:41

is it possible to use clojure.java.shell to invoke a shell command like tr < infile.txt -d '\000' > outfile.txt ?

ghadi20:07:45

^ How cool is that?

dpsutton20:07:24

yeah got the email earlier. super super cool

noisesmith20:07:03

@aaelony in order to use things like < you need to invoke /bin/sh

noisesmith20:07:18

clojure.java.shell/sh, despite the name, is a raw system command that doesn’t use sh

noisesmith20:07:21

of course that’s not portable, and then you’ve created code that only works on a *nix system or reasonable facsimile

aaelony21:07:19

yeah, @noisesmith there is a thorny translation that tr handles well at the command line that I'd like to shell out within a clojure program ( that does a whole laundry list of things). Haven't yet found the right syntax for calling tr via clojure.java.shell/sh though

noisesmith21:07:11

@aaelony you send a normal shell command to sh (clojure.java.shell/sh "/bin/sh" "-c" "tr < infile.txt -d '\000' > outfile.txt")

aaelony21:07:38

wow, thank-you. That works. I tried a lot of other combinations but without the "/bin/sh" "-c"

noisesmith21:07:59

right, sh -c says “find and run the sh executable, tell it to run this”

aaelony21:07:29

that class of example should probably be added to the other examples (https://github.com/clojure/clojure/blob/master/src/clj/clojure/java/shell.clj#L130-L142)

aaelony21:07:39

thanks again

noisesmith21:07:03

np, glad I could help, I wonder if it would be worth submitting a patch to JIRA for adding another println to that comment block

aaelony21:07:15

agreed - I think that would help others. Not sure how easy that is to do...

noisesmith21:07:10

@aaelony there is an example of using sh -c as an arg to sh on the clojuredocs page https://clojuredocs.org/clojure.java.shell/sh

aaelony21:07:42

true enough, perhaps a few words there describing why it is useful/needed would work too

heucles21:07:11

@noisesmith thanks for your help yesterday, you were correct !!!!