Fork me on GitHub
#clojure
<
2022-10-21
>
Al Z. Heymer14:10:09

I tried to mock a function map with a field, that should not be accessed, so I used

(constantly (throw (Exception. "NOPE")))
This immediately throws an exception, but when I use
(fn [& args] (throw (Exception. "NOPE")))
It works. Do I see correctly, that this is because constantly is a function, but fn is a macro? It feels weird nevertheless...

p-himik14:10:38

That's correct. In the latter case, (throw ...) is the body of the created function - it's not executed right there. In the former case, (throw ...) is an argument to an already existing function, constantly.

Al Z. Heymer14:10:04

I just noticed, that using (constantly (throw ...)) doesn't really work for my case. It will literally only work if the encapsulated map (the throwing fn) is used as a function object, like key lookups. Doesn't work using it as a seq , like with reduce or map.

Al Z. Heymer14:10:42

any suggestions how to realize something like that? Maybe a smarter approach to check if a key is accessed?

p-himik14:10:17

First of all, why do you need something like that in the first place? That's very unusual and I'm willing to bet pretty much nobody does that.

Ed14:10:35

I'm unsure what you're trying to achieve? Are you trying to create an object that will throw an exception if you get a key that's not in a special list?

Al Z. Heymer14:10:14

@U2FRKM4TW well, the use case is a conditional branching (very rough example): HR doesn't need to access the employee if they are an external worker. So my goal is to write an appropriate test that makes sure, the code on that conditional branch never touches that area. And yes, the kv has to stay. @U0P0TMEFJ kind of, it's a map which mustn't be accessed, neither by get, nor by seq. Think stack canary on a key.

Ed14:10:55

I often feel like when the need to reach for a "mock" appears it highlights a design problem ... can you be more specific?

p-himik14:10:54

Feel like you're trying to apply a pattern from Java to Clojure, TBH. But I don't have a good advice here except for "don't do it".

Ed15:10:59

> Feel like you're trying to apply a pattern from Java to Clojure, TBH I agree. That's why I'd like to see a wider context of the problem that's being solved. I expect that there's a different way that would address the issue.

Al Z. Heymer15:10:25

Design problems are neither my responsibility, nor my goal to fix right now. So I really don't care about that atm. I rather intend to constrain what is given, so that it can't be misused any further. The context given is pretty much that: you have a map with 2 keys, the key :employee in question points to another map object, and the access to that object is to be generelly constrained, both access, and further processing. If k1 :type says :external, :employee is not to be accessed (like a "Key canary") Only that there are like hundreds of nested maps under certain keys, that decide whether or not that should be accessed, and one decision to make: access or not.

Ed15:10:39

so you have a function that controls access to these bits of data? maybe something like:

(defn get-employee-for-hr [data]
  (when-not (-> data :type (= :external))
    (:employee data)))

(comment
  (let [data [{:employee {} :type :external}
              {:employee {} :type :internal}]]
    (mapv get-employee-for-hr data))

  )
and you can easily write a test for that function that solves your problem?

Ed15:10:10

or maybe a can-hr-access? function?

Al Z. Heymer15:10:01

That is a possible solution iff the access to that data structure is not clustered across thousands of LoC. This is why I need the Data struct to alert, when it is accessed.

Ed15:10:11

does grep -rHi "employee" identify all these places? ... and presumably at some point you'll need to write tests for all of these places where the decision is made and decide if the answer to can-hr-access? should be yes or no?

Vesa Norilo16:10:07

if you really need to do this you could do it on the level of the map. see https://blog.wsscode.com/guide-to-custom-map-types/

Ed16:10:15

if you want a value that you can pass to a function that will explode when you ask for a key you can use proxy or reify to create one.

(let [sup {:a 1 :b 2}
        mok (proxy [clojure.lang.ILookup] []
              (valAt
                ([k] (or (get sup k) (throw (ex-info "err" {:access :illegal}))))
                ([k nf] (or (get sup k) (throw (ex-info "err" {:access :illegal}))))))]
    (:qqq mok))

Ed16:10:37

but I'd suggest that you're still going to have the same design problem

Ed16:10:45

or for a seq (second (concat [1] (lazy-seq (throw (ex-info "err" {:access :illegal})))))

Sam Ritchie20:10:41

Or try a function with a case statement inside

Al Z. Heymer09:10:55

Thank you guys. The idea of using a proxy is quite useful, I think. But instead of implementing everything with a throw I would rather do an empty map implementation, that only implements type . That way it would basically throw a RuntimeError (or similar) on seq and reduce , yet basic schema validation still works.

Al Z. Heymer09:10:17

To be honest, I observe this argument "design problem" a lot here. Sure there is a design problem, but just "shouting" that doesn't fix a problem. You can't just rebuild anything whenever someone brings that up. We are in the process of doing that, but everyone is aware of that, which is why I said it's not my concern for this particular issue. @U0P0TMEFJ

👍 1
acron16:10:01

What is the most idiomatic way to call remove when the predicate is supplied? Any improvements over (remove (or pred (constantly false)) coll) ?

colinkahn16:10:25

If its when pred could be nil then you can do

(cond->> coll
  pred (remove pred)))
Sort of like wrapping it with an if, but if pred is falsey you'll just get the unaltered collection back.

☝️ 3
dpsutton16:10:53

👍 no reason to traverse the collection if it will not remove any items

dpsutton16:10:09

and the or solution above will work perfectly fine

acron16:10:24

Cheers folks

skylize16:10:39

I think you are trying to do (filter (complement pred) coll) ?

dpsutton16:10:23

(filter (complement pred) coll) is equivalent to (remove pred coll) but not as clear. it's basically a demorgan of the remove version

pppaul16:10:52

i was confused, i thought that the code example was trying to express multiple preds

acron16:10:16

No, sorry, just one pred but it could be nil

pppaul16:10:42

it seems like a condition on the pred, like what @U0CLLU3QT wrote, is expressing the ides best

pppaul16:10:16

moving logic higher up in your code, before you execute your main ideas, is very good

hiredman23:10:26

I am having an issue where when I run the clj command or rlwrap clojure if I type in a form and hit enter, the form is evaluated successfully, but where I typed it in, the form gets kind of mangled, sort of the last paren in the form is replaced with the form again

hiredman23:10:24

for example

Clojure 1.11.1
user=> (+ 1 2)(+ 1 2)
3
user=>
I just typed in (+ 1 2) and hit enter, the second (+ 1 2) appears to have been added by rlwrap

hiredman23:10:07

and my rlwrap version is 0.45.2, so maybe it got re-broken

Alex Miller (Clojure team)00:10:47

Yeah, have been seeing some reports of this on latest