Fork me on GitHub
#beginners
<
2019-02-26
>
pberganza1003:02:53

Hi! I was wondering if there is a way to mock Java classes (constructors, etc) for testing in Clojure. The only solution I've found is to wrap whatever I'm doing in a function and mocking that function, but that doesn't feel quite right. I'm specifically trying to mock (Socket. host port) from java.net.Socket. 😮

seancorfield03:02:51

@pberganza10 If the function you are testing took a socket as an argument, instead of creating it, then you could pass in a "mocked" socket using reify.

seancorfield03:02:41

If your function constructs Java classes and then uses them, it's going to be very tightly coupled to those classes, making it harder to test (as you've found).

pberganza1003:02:51

@seancorfield Yes, that's basically what it's doing. So, basically, I should change my approach and separate Java as much as I can from my functions if I want to make my functions testable?

seancorfield03:02:56

It's more about separating construction from use. Whenever you have a function that "constructs" something, that function is closely tied to the thing it constructs. By having functions that "use" things be passed in the thing that they use, you separate the construction part and make it easier to test functions because you can pass thing different versions of those "things".

seancorfield03:02:36

Does that make sense @pberganza10? (Sorry, for the slow response, I was listening to an episode of NPR's "Live From Here" because Amanda Palmer was on it)

pberganza1004:02:46

It does make sense. And no worries at all! Thanks a lot! I'll take that into account going forward

rodrigo75910:02:47

Hello guys, What is the idiomatic way of writing something like: (defn myfunction [something something-else] (let [x1 (transient something)] (let [x2 (assoc! x1 :somewhere (some-function (something-else (x1 :some-value))))] (let [x3 (my-function-that-assoc! something-else (another-function (x2 :another-value)))] (let [x4 (my-funtion-that-conj! something-else-again (x3 :test))])))) (persistent! x4))

rodrigo75910:02:11

Is there a way to get rid of the nested let in this case?

borkdude10:02:31

(let [x1 ..., x2 ..., x3 ...])?

borkdude10:02:03

let accepts multiple bindings

rodrigo75913:02:47

@borkdude it is not the point. It won't work. It is a cascaded let

rodrigo75913:02:41

x3 depends on x2. x2 depends on x1, etc...

borkdude13:02:51

let supports that

rodrigo75913:02:15

😲 let me try

borkdude13:02:58

(let
  [a 1
   b (+ a 1)]
  (+ a b)
unless I’m missing something…

rodrigo75913:02:22

In this case my example above would be:

rodrigo75913:02:23

(defn myfunction [something something-else] (let [x1 (transient something), x2 (assoc! x1 :somewhere (some-function (something-else (x1 :some-value)))), x3 (my-function-that-assoc! something-else (another-function (x2 :another-value))), x4 (my-funtion-that-conj! something-else-again (x3 :test)), (persistent! x4)]))

borkdude13:02:30

you need to have a closing square bracket before (persistent! x4) if you want that as the return value

rodrigo75913:02:34

For some reason Sublime does not like it:

rodrigo75913:02:14

It does not like the commas.

rodrigo75913:02:57

Is there any issue if I reassign to the same variable like this:

rodrigo75913:02:58

(defn myfunction [something something-else] (let [something (transient something) something (assoc! something :somewhere (some-function (something-else (something :some-value)))) something (my-function-that-assoc! something-else (another-function (something :another-value))) something (my-funtion-that-conj! something-else-again (something :test))] (persistent! something)))

jaihindh.reddy21:02:11

You can do this but it's not idiomatic in most cases. The threading macros -> and ->> help here. Also check out destructuring. It can be used in all places where symbols are bound (except binding)

rahul08032713:02:32

@rodrigo759 It will work totally fine, let simply rebinds the value

bronsa13:02:38

no, you're not reassigning it

bronsa13:02:41

you're just shadowing

bronsa13:02:43

so it's fine

bronsa13:02:40

(let [a b c d] f) is precisely equivalent to (let [a b] (let [c d] f))

rodrigo75913:02:50

great! Thanks my dear clojurians friends!

funyako.funyao15613:02:26

Hi, I have an integration test that utilize use-fixtures to start and stop com.stuartsierra/component system that I have. Let's say the app is supporting multiple dbs, in short:

;; system.clj

(defn new-db
  [{:keys [kind] :as config}]
  (case kind
    :datomic    (new-datomic config)
    :postgresql (new-postgresql config)
    :mongodb    (new-mongodb config)))

(defn new-system
  [config]
  (c/system-map
   :db (new-db (:db config))))

;; test_fixtures.clj

(defstate system
  (atom nil))

(defn system-fixtures
  [f]
  (let [config (-> "config.edn" io/resource slurp read-string)]
    (reset! system (c/start (new-system config)))
    (f)
    (swap! system c/stop)))

;; app_test.clj

(use-fixtures :each system-fixtures)

(deftest some-test
  ;; tests here...
  )

Any ideas on how to tell the fixtures to read from another config file? Currently I have multiple file with same namespaces and functions, and load the ns with which one I want to use.

borkdude13:02:35

@rodrigo759 > It does not like the commas. Commas are optional in Clojure.

rodrigo75914:02:57

The code below is not guaranteed to work because it does not use the result of conj!. But I am a little bit lost on how to replace the for to a more idiomatic construct. What is the idiomatic way of doing it?

rodrigo75914:02:59

(defn myfunction [bean-object] (let [transient-obj (transient (process bean-object))] (doall (for [[k v] bean-object] (conj! transient-obj {k v}))) (println (transient-obj :somethig)) (persistent! transient-obj)))

rodrigo75914:02:44

By the way... It does work on my computer... But it is not guaranteed.

bronsa14:02:50

when using transients you want to use reduce-like iterations

bronsa14:02:03

it works up to 16 elements

bronsa14:02:10

"accidentally"

bronsa14:02:42

also you don't want to use persistent! like that

rodrigo75914:02:49

What is the problem with persistent! ?

borkdude14:02:01

I think you can reduce your code to something like:

(into (process bean-object) bean-object)

rodrigo75914:02:00

🤔 ok. I will try. Thanks!

bronsa14:02:41

you're using transients as if they were mutable objects (which they are, but not always)

bronsa14:02:12

you can't do (let [x (transient y)] (conj! x 1) (persistent! x))

bronsa14:02:32

you need to do (let [x (transient y)] (let [x (conj! x 1)] (persistent! x)))

bronsa14:02:56

so one correct way to write your code from above would be

(defn myfunction [bean-object]
  (let [transient-obj (transient! (process bean-object))
        transient-obj (reduce (fn [transient-obj [k v]] (conj! transient-obj {k v})) transient-obj bean-object)]
    (persistent! transient-obj))

rodrigo75914:02:57

yes. that's what I am fixing in my code now.

bronsa14:02:06

but as @borkdude suggests into is better

rodrigo75914:02:00

I am still struggling to understand how to use into to replace my original for construction.

bronsa14:02:03

if you're using transients directly, which there's often not very good reasons to, think of them as if they were immutable objects

rodrigo75914:02:38

ok. I am very concerned about performance. In my case that bean-object is a huge map. The code I showed is the simplification of my real code. It has a lot of processing on that map. I am using transients hoping that it will give me a better performance. It is part of an optimization AI routine. Every millisecond is important for me.

bronsa14:02:54

into internally uses transients

rodrigo75914:02:30

🤔 Ok! Still trying to convert my original for code to something using into

borkdude14:02:40

@rodrigo759 the expression I posted is effectively the body of your function

mathpunk16:02:03

I really really want to work out how to use zippers in anger, and I came across a case where they seemed potentially useful at work. Couldn't figure out the rules for implementing them though.

mathpunk16:02:18

Here's some example data:

mathpunk16:02:22

'({::case/situation ["The login page"], ::case/expect "can be reached"},
  {::case/situation ["The login page" "when incorrect credentials are entered"], ::case/expect "rejects the login attempt"},
  {::case/situation ["The login page" "when correct credentials are entered"], ::case/expect "logs the user in"},
  {::case/situation ["The login page" "when correct credentials are entered"], ::case/expect "greets the user"},
  {::case/situation ["The login page" "when correct credentials are entered" "and the user is new"], ::case/expect "offers the tutorial"},
  {::case/situation ["The users page"] ::case/expect "has a Users header"})

mathpunk16:02:05

The above is the result of what I named a story function which acts on a mess of test output

mathpunk16:02:37

The longer the case/situation vector, the more complicated the setup before the test checks its assertion

mathpunk16:02:42

This looks like a tree to me --- "The login page" and "The users page" are the children of an unlabeled root node, "The login page" has one leaf child and 2 branch children, etc

mathpunk16:02:07

(defn branch?
      "If there are no more precondition statements (or no precondition at all, if that can even happen),
  then the node cannot be a branch."
      [story]
      (when-let [pre (::case/precondition story)]
        (not (empty? pre))))

    (defn children
      [stories]
      (vals (group-by (comp first ::case/precondition) stories)))

mathpunk16:02:13

These seem pretty close... Although maybe for this children function to work, it would have to remove the bit of the case/situation that we've seen in the parent

mathpunk16:02:49

But I really struggled on the make-node function --- I made some attempts but I don't really know what it means so I was just thrashing around

mathpunk16:02:08

Does it... • Get the label given a node? That can't be right, it takes a sequence of 'new children' (which doesn't super make sense to me in this situation but neither did branch? at first and I got there eventually) • Given a label and some children "build up" a higher bit of the tree? That might make sense if zippers can infer how to travel down but they need help to travel up. • ???

mathpunk16:02:01

Shout-out to @plexus for a great explanation of why zippers are extremely cool! But I'm not quite making that last jump...

lilactown16:02:28

what’s your goal @mathpunk?

mathpunk16:02:21

It's twofold: - learn to implement a zipper when something looks treeful - "zoom in" on the test data by complexity of story. So for instance, zip down to the situation, "The login page when correct credentials are entered" and return that zoomed-in bit of the results

lilactown16:02:07

well, zippers work best on data that’s already a tree

lilactown16:02:30

it looks like your data is actually flat, but could be reorganized as a tree

mathpunk16:02:03

see, I thought that the z/zipper function was for, You clearly see that your data could be a tree so here's the functions that would make it into one so it can be navigated that way

mathpunk16:02:10

maybe I'm wrong on that, then

lilactown16:02:40

zipper is more about creating a structure that allows you to move within the tree, transforming it to a tree would be a separate step before it IMO

mathpunk16:02:41

I ended up with a couple of functions that would update a precondition by dropping the first element, or concatenating a higher precondition onto 'em

mathpunk16:02:00

do you think, then, the right way is to transform the given structure into something that is actually nested?

lilactown16:02:14

yes, that is what I feel is correct

lilactown16:02:23

someone else can jump in and tell me I’m wrong, but that’s what I would do

lilactown16:02:50

otherwise you’re going to be doing two things at once: transform it AND making it zipper-able which would make my brain hurt

lilactown16:02:45

also, the major benefit of zippers is that it allows you to transform the tree “in-place”

lilactown16:02:07

if you don’t need that capability, you might find that the new nav stuff is actually a better fit

mathpunk16:02:38

which means.... implement datafy? I'm nervous about that 'cause I haven't worked with metadata

lilactown16:02:51

like, if all you want to do is traverse the tree and return a specific branch then nav will be much easier

lilactown16:02:30

you might not need to use datafy, if you’re already working with Clojure data and you don’t need to do anything fancy

mathpunk16:02:50

that does sound closer to my use case

mathpunk16:02:11

it's trees I'm obsessed with, more than zippers specifically 🙂

lilactown16:02:42

riddle me this: if you had this in a tree structure already, would you be able to do what you want to do with get-in?

mathpunk16:02:41

Dunno. Probably? Goal questions are things like, "What's the widest node, let's get that one and then examine the children of that wide node one by one"

mathpunk16:02:37

When I am actually squinting at this data, I am looking at the widest collection of failures, cause sometimes if I fix that, the most complicated stories (the ones with the longest preconditions) will end up fixed as a side effect

mathpunk16:02:05

And since I'm looking to prioritize more than change, that certainly sounds get-in-y

lilactown16:02:08

by “wide” you mean “has the longest vector of situation“?

lilactown16:02:31

so maybe there’s some filtering that you would want to do, in order to figure out which nodes are the best candidates

mathpunk16:02:35

ah, no--- by wide I mean, is there a situation with many children

lilactown16:02:34

ah yeah, then I would transform it into an actual tree organized by situation to make that easier

mathpunk16:02:12

Alright --- so my key misunderstanding was, it's not that zippers make tree-like data trees, it's that they make tree data navigable

mathpunk16:02:41

plus, there's other ways of skinning that cat, with nav/datafy

mathpunk16:02:11

one more clarification: nav/datafy, would they also require me to organize this into a real tree first?

lilactown16:02:36

That would be the easiest way 😅

lilactown16:02:37

You can get clever with making a special nav or datafy implementation

lilactown16:02:05

But at some point you just gotta write that reduce or loop-recur to transform it into a tree

mathpunk16:02:09

cool cool. thanks!

patrickwinter17:02:36

I've a small workflow question. I've the following program:

clojure
(ns socketserver.core
  (:require
   [aleph.tcp :as tcp]
   [manifold.stream :as s])
  (:gen-class))

(defn handler [s info]
  (s/connect s s)
  (s/put! s "Hello! I'm the socket server :-)"))

(defn -main [& args]
  (tcp/start-server handler {:port 1678}))
I would expect that after I've run (-main), change the handler string and reevaluate the handler in CIDER, that the handler would now return the new string. However I still get the old message 😕.

overde17:02:44

Hi fellas, this mind-boggling error is consuming me Can't make API call *METHOD* in a thread that is neither the original request thread nor a thread created by ThreadManager

oliver.marshall17:02:43

@patrickwinter I've not looked into it too deeply but I had a similar case recently, I think it's an issue where where the the value of the var get's passed down but when you update the var that doesn't change the value that got passed to your start-server function

oliver.marshall17:02:11

The hacky fix I used was to replace the reference in your -main function with something like #(apply (var-get #'handler) %&)

oliver.marshall17:02:13

Very hacky I admit, but I couldn't figure out a better way to fix it quickly

hiredman17:02:23

#(apply (var-get #'handler) %&) is just #'handler

oliver.marshall17:02:20

Ah, does that work? Nice

hiredman17:02:56

(var-get #'handler) is @#'var-get

seancorfield18:02:55

@#'handler, you mean?

patrickwinter18:02:50

Thanks for your help. @#'handler does not seem to work.

hiredman18:02:30

yeah, you don't want @#'handler

hiredman18:02:36

you want #'handler

hiredman18:02:07

def interns vars, which are mutable cells that hold values

hiredman18:02:23

when you re-def something you are mutating the value of that cell

hiredman18:02:12

when you write a name like handler if the compiler doesn't find a local in scope with that name, it resolves it to a var and compiles it as a deref of that var

hiredman18:02:19

so (foo handler) compiles to something like "deref the var handler, and use it as an argument when invoking foo"

hiredman18:02:38

meaning foo is passed in the value the var currently has, not the var

hiredman18:02:56

#'
or "var quote" means give me the var

patrickwinter18:02:12

Thanks for the explanation!

hiredman18:02:12

so #'handler is the var, the mutable cell

hiredman18:02:42

and vars proxy function calls to their values, so if a var contains a function, you can invoke the var like a function

mathpunk21:02:35

Earlier this morning I was wrestling with zippers before turning my data into trees first, and I'm realizing I have a very informal understanding of a tree. - It is a graph without cycles - The nodes may have labels - The nodes may have children - (branch? node) => true iff the node could have children, otherwise (leaf? node) => true Since I'm coming from graph theory rather than computering, are there bits of the protocol that seem obvious that I'm leaving out?

hiredman21:02:31

zippers also require a function that returns a seq of nodes and function that given a seq of nodes returns a new node

hiredman21:02:01

if you are coming at it from graph theory, I doubt zippers will be useful for you

mathpunk21:02:45

i'm not sure --- i don't need to do graph theory, i am just interested in zooming in and out of a collection that has a tree structure

mathpunk21:02:20

i mean, my experience is from graph theory, so i feel like there's a chance there's a bunch of obvious stuff that i'm overlooking because we never stored data in our trees in school

hiredman21:02:44

like, a tree is any datastructure

hiredman21:02:05

[1 2 3] is a tree with 3 leaves, 1 2 3

hiredman21:02:21

[[1] [2 3]] is a slightly more interesting nested tree

noisesmith21:02:45

but since branch / leaf / children are functions, you could define a tree an an adjacency list

hiredman21:02:47

the problem with these trees of functional datastructures is doing deep updates

mathpunk21:02:00

and i can squint at it and see the tree without it literally having that nesting structure

mathpunk21:02:07

it's like... within some functional transformations of a nesting structure

noisesmith21:02:17

but also direct operations on an adjacency list are easier than using the zipper abstraction in many cases...

hiredman21:02:20

because they aren't mutable, so you have to pull them apart on the way down, and build them back up with the change in place on the way back up

hiredman21:02:36

that is more or less what zippers do

hiredman21:02:13

they are similar there to lenses or specter

mathpunk21:02:37

thank you both. I am very interested in getting good at working with trees or things-that-can-be-transformed-to-trees, and I will look at those. (Has someone implemented lenses or would I be reading papers?)

jaihindh.reddy22:02:08

specter's navigators are very close to lenses.

mathpunk21:02:37

I should add, there is additional data in those maps I posted --- so I've been trying to think for a while about navigating with those sequences of strings, in order to obtain the entire zoomed-in collection of the maps

hiredman21:02:53

having played with zippers a bit for doing crazy macro rewrites, I've never reached for zippers for real stuff

hiredman21:02:15

in general because if you hand me a deeply nested structure the first thing I do is flatten it out

hiredman21:02:16

the data you linked to doesn't look nested at all

hiredman21:02:22

it looks like a nice flat seq of maps

noisesmith21:02:06

there is a straightforward algorithm for building a tree from an adjacency list that meets the structure requirements (sort from leaf up and nest onto parents), and what you show above seems not to be far from being an adjacency-list

hiredman21:02:22

zippers are for trees in the sense that any nested datastructure is a tree, they are not some generalized library for trees

noisesmith21:02:22

but I agree that usually the flattened form is more useful