Fork me on GitHub
#beginners
<
2020-06-01
>
Kazuki Yokoyama00:06:35

Hi there. Does anybody have a good resource to learn testing with Midje besides its own documentation? Thank you.

seancorfield01:06:40

@yokoyama.km Not many people use Midje. A lot of people don't consider it to be very idiomatic Clojure. Is there a reason you want to use it instead of standard testing tooling that all the IDEs/editors and test runners supports?

Kazuki Yokoyama01:06:06

It is primarily for the sake of learning. I don't have any compelling reason. Could you explain a little more why it is not considered to be very idiomatic Clojure?

seancorfield01:06:34

A lot of existing tooling is designed to support clojure.test which means all editors and IDEs can run tests and report failures etc. Midje isn't compatible with any of that so you're at the mercy of whatever custom tooling has been created for Midje. This was an issue I ran into with Expectations, which was a testing DSL that I started using years ago -- CIDER in particular had zero support for Expectations (and has no support for Midje either). So I wrote expectations.clojure.test which is essentially a clojure.test-compatible version of Expectations: almost the same syntax as Expectations although it requires named tests.

seancorfield01:06:11

Midje's DSL is a lot more extreme than Expectations: it goes far beyond Clojure syntax and it's very macro-heavy.

seancorfield01:06:51

Midje also has a ton of dependencies so it's "heavy" as a testing technology -- you end up dragging in a huge number of little-used libraries.

seancorfield01:06:24

In addition, the author of Midje has moved on from the Clojure community as far as I know -- he stopped contributing to it well over three years ago... although it looks like a new maintainer is trying to keep it alive.

seancorfield01:06:46

(although we use Expectations heavily at work, we transitioned to the clojure.test-compatible version and if clojure.test itself gets some enhancements, we'd probably transition off to that directly -- since that's where all the focus of maintenance and support is across all of the tooling)

deactivateduser01:06:44

And that argument extends to other cool libraries like test.check. FWIW I started off using Midje (I liked the cleaner syntax), and switched to clojure.test when I realised what I was missing out on.

deactivateduser01:06:05

And as @U04V70XH6 mentioned, the syntax of Midje tests (despite being “clean”) isn’t idiomatic, which I think can mess with beginners. OTOH clojure.test tests are just bog-standard Clojure code.

Kazuki Yokoyama01:06:32

I see. So is clojure.test supposed to be enough or there any other testing library I should be aware (either as main testing library or complementary)? I mean, what are the main deficiencies of clojure.test (if any) that can cause me to need other libraries?

deactivateduser01:06:20

I think it’s more about what more you’d like your tests to do, beyond basic “example based” tests (which is what clojure.test provides out of the tin).

deactivateduser01:06:58

test.check adds property based testing functionality, expectations adds … not sure - I haven’t used it (paging @U04V70XH6). 😉

Kazuki Yokoyama01:06:05

Yes, clojure.test looks very spartan, so my first impression was "okay, maybe I'm going to need more libraries".

deactivateduser01:06:38

I think that’s actually a good thing - Clojure really encourages / favours composability.

deactivateduser01:06:09

And for testing specifically, most everyone seems to have standardised on extending clojure.test.

Kazuki Yokoyama01:06:25

I'll take a look into test.check and expectations.

💯 4
deactivateduser01:06:24

Might help to not think of them as “new” libraries, but instead as “optional clojure.test capabilities”.

✔️ 4
aisamu02:06:11

If you like midje's matchers but want to use clojure.test, check https://github.com/nubank/matcher-combinators

Kazuki Yokoyama02:06:42

Thank you, I'll take a look!

seancorfield03:06:47

@U0MDMDYR3 The two main things Expectations adds are a) more BDD-style syntax "Expect <something> (to be true of) <some actual expression>" and b) some convenient matchers. /cc @yokoyama.km

👍 8
seancorfield03:06:31

So you can (expect ::some-spec (my-expr)) for example. Or (expect #"regex" (string-fn))

seancorfield03:06:54

and things like (expect {:sub :map} (in (map-fn))) and then there's a destructuring version of are: (expect (more-of {:keys [x y z]} pred? x stuff? y ::a-spec z) (map-fn))

seancorfield03:06:49

It all reduces down to is with some extensions to clojure.test (since is and assert-expr and report are all easily extensible).

Kazuki Yokoyama03:06:07

Hmm, looks useful. Thank you, @U04V70XH6!

seancorfield03:06:33

There's an #expectations channel if you want to dig deep and ask more questions.

✔️ 4
didibus03:06:08

I would say clojure.test has no deficiencies and can do anything you want for a test.

didibus03:06:54

Other libs just provide little utility on top, but it's nothing you couldn't do without. Its just to make certain things a bit more convenient

didibus03:06:37

Arguably, you could say those other libs can make things easier or harder depending on your outlook (learning one more DSL, vs using your existing knowledge of Clojure)

didibus03:06:17

Especially if you're a beginner, I'd start with plain clojure.test

👍 12
seancorfield04:06:09

Yes, definitely, even as maintainer of expectations.clojure.test I'd definitely recommend starting with clojure.test!

Kazuki Yokoyama16:06:32

Nice advices! Thank you. Any thoughts on mock libraries?

seancorfield17:06:40

@yokoyama.km If you split your code into pure functions and side-effecting functions, then you can always pass "mock" data into the pure functions in your tests -- that's just good practice.

seancorfield17:06:17

i.e., try to write your code so it doesn't need mock functions at all, and it will be much easier to test (and also be more idiomatic).

seancorfield17:06:25

But if you need to mock a called function, most folks use with-redefs to temporarily overwrite functions. No mocking library needed.

Kazuki Yokoyama17:06:20

Nice, using with-redefs is what I'm currently doing, but it looked a little low-level (I even thought it was not idiomatic for some reason). Since I'm using protocols, I also end up using reify often in my tests.

Kazuki Yokoyama17:06:27

Yes, I try to separate pure from impure functions as much as possible, but I still need to test those impure functions somehow. That's where things get hairy.

seancorfield17:06:50

Life can sometimes be easier with protocols if you write wrapper functions and use those as the actual API (and treat the protocols as low-level implementation). You can't instrument protocol functions, for example, but you can instrument wrapper functions. And, yes, reify is also a reasonable approach in tests.

seancorfield17:06:51

Remember that Clojure favors small, composable abstractions and functions -- you often don't need anything more than functions to achieve stuff that would require libraries in other languages (or even -- shudder -- frameworks!).

Kazuki Yokoyama18:06:14

> if you write wrapper functions and use those as the actual API (and treat the protocols as low-level implementation). This sounds interesting. Where can I find some examples of this?

seancorfield19:06:56

https://github.com/seancorfield/next-jdbc/blob/master/src/next/jdbc.clj is a good example: everything in this library is protocol-based but the API is all wrapper functions. There is an optional Spec namespace (that can instrument the public API). The protocol definitions are in next.jdbc.protocols and the default implementations are in various next.jdbc.* namespaces.

Kazuki Yokoyama19:06:47

Cool. I'll take a look. Thank you, @U04V70XH6!

hyc04:06:44

hello everyone

seancorfield04:06:11

👋:skin-tone-2: Welcome to the Clojure #beginners channel! Have fun learning Clojure!

amirali08:06:15

hi guys I'm trying to map a nested vector. But i cant get the logic to make it happen inside nested vector. Example: [[1 2][3 4]] to {[a: 1 b: 2][c: 3 d: 4]}. Thanks

Tzafrir Ben Ami09:06:16

the expected result you have posted is not really valid. should it be:

[{:a 1 :b 2} {:c 3 :d 4}]
(vector of maps basically)?

🙏 4
agigao09:06:11

+1, expected result doesn’t look correct. You need a vector of maps. How to get it?

(map (fn [a b] (zipmap (map keyword a) b))  [["a" "b"] ["c" "d"]] [[1 2] [3 4]])

=> ({:a 1, :b 2} {:c 3, :d 4})
1 - map takes unlimited # of collections, you can provide coll1, coll2, coll3 etc. and map will take the first element of all collections, we’re feeding 2 collections to map right now. 2 - zipmap takes 2 collections: keys and values, in this case ["a" "b"] are the keys, and [1 2] are values and the function bakes a map: {:a 1, :b 2} 3 - For extra we turn strings into keywords by applying keyword function to all keys: (map keyword ["a" "b"]) => [:a :b] That’s it I guess.

agigao09:06:11

+1, expected result doesn’t look correct. You need a vector of maps. How to get it?

(map (fn [a b] (zipmap (map keyword a) b))  [["a" "b"] ["c" "d"]] [[1 2] [3 4]])

=> ({:a 1, :b 2} {:c 3, :d 4})
1 - map takes unlimited # of collections, you can provide coll1, coll2, coll3 etc. and map will take the first element of all collections, we’re feeding 2 collections to map right now. 2 - zipmap takes 2 collections: keys and values, in this case ["a" "b"] are the keys, and [1 2] are values and the function bakes a map: {:a 1, :b 2} 3 - For extra we turn strings into keywords by applying keyword function to all keys: (map keyword ["a" "b"]) => [:a :b] That’s it I guess.

agigao09:06:25

Clojurians, I wonder how do we take care of keeping different database connections for dev and master branches. Any pointers?

Eddie16:06:32

Not sure if this is exactly what you are asking, but we use aero to manage the config of our platform. You can use the "profile" feature to get a config for a specific environment. There is also usage pattern for managing secrets in the README. https://github.com/juxt/aero

👍 4
Kris C10:06:49

Doing excersises on 4clojure, but I am interested in the following: did anybody else find some problems that were marked as "easy" to be actually difficult (or at least medium) for a clojure noob? Or am I just too stupid?

Kris C10:06:38

NOTE: I am not looking for a solution!

agigao10:06:39

I checked my solutions and it seems I skipped it :))

Ben Sless10:06:44

It's a classic algorithms question which might be slightly complicated by the fact you're not allowed to define functions, so how are you supposed to recurse? You can recurse anonymous functions like so:

(fn foo [arg] ... (foo ...))
Does this help?

Ben Sless10:06:19

In general, you shouldn't feel bad if you find some of the "easy" questions difficult, half the time they maybe require a feature you didn't know existed yet, because you're new

👍 8
sogaiu13:06:56

i found some easy ones hard and vice versa

practicalli-johnny13:06:13

There is no scientific method for measuring if something is easy or hard, it’s all completely subjective. They are all simple once you discover a nice answer, so almost impossible to consistently define something as easy or hard. It’s also greatly influenced to individual experiences and which of the 600 functions you remember the most.

Xarlyle014:06:51

I will say, at least that particular example does require a good grasp of how to utilize clojure's recursion constructs since you can't define functions.

Cas Shun15:06:50

I'm new to programming (~6 months) and clojure is my first language. I started trying 4clojure and found that about 25% of the "easy" problems were very difficult. Many of the hard problems were not difficult at all. So I just ignore the difficult now.

Ben Sless16:06:00

@U014A5N6CNN you can always cheat and "define" functions in a let binding.

practicalli-johnny17:06:45

I did a video series walking through the first 60 challenges of 4Clojure, using different approaches to solve them. It was a very useful way of practicing different aspects of Clojure and learning more of the clojure.core functions. I recommend trying to solve the challenges yourself first, before looking at the videos 🙂 https://www.youtube.com/playlist?list=PLpr9V-R8ZxiDB_KGrbliCsCUrmcBvdW16

💯 4
alwyn21:06:35

Going through them now and I often know what I want to achieve, just not how to go about it without being almost imperative.

alwyn21:06:12

but when the lightbulb goes on its like a nova

practicalli-johnny06:06:35

Yes, trying to solve the 4Clojure challenges in different ways certainly helped me understand different levels of abstraction I could use, just from the clojure.core functions.

Drew Verlee16:06:13

Whats the name for "_#"? thats a reader condition right? It seems like a the comment fn (macro?) , whats inside still has to be "readable" right?

Alex Miller (Clojure team)18:06:02

in the code it's called the discard reader

noisesmith16:06:14

are you sure you don't mean #_? that's a reader macro that drops a form. _# is idiomatically a binding that you are ignoring inside syntax quote

noisesmith16:06:40

since syntax quote rejects bindings that don't end in # and _ indicates a binding you intend to ignore

noisesmith16:06:10

so you'd see _# in a side effect in a let block inside syntax quote, for example

phronmophobic16:06:50

yes, the form following #_ must still be readable:

> #_{:foo}
Syntax error reading source at (REPL:3868:26).
Map literal must contain an even number of forms
> #_{:foo nil :foo nil}
Syntax error reading source at (REPL:3874:39).
Duplicate key: :foo

ghadi17:06:46

it's unclear if @drewverlee meant _# or #_ but @noisesmith has the explanation

👍 4
Rameez17:06:12

Hey folks! 👋 Started learning Clojure a few days ago and I'm trying to figure out how get result in the shape/form below:

(def assets [{:month 1 :amount 10}
             {:month 2 :amount 5}])

(def liabilities [{:month 1 :amount 8}
                  {:month 2 :amount 4}])

(def result [{:month 1 :amount 2}
             {:month 2 :amount 1}])

;; where result = assets - liabilities

noisesmith17:06:21

this gets a lot simpler if you have a map from month to amount for each

noisesmith17:06:42

{1 8 2 4} then {1 2 2 1}

noisesmith17:06:47

after that, it's just a merge-with

deactivateduser17:06:47

Or even just two vectors [10 5] and [8 4].

noisesmith17:06:18

do vectors do the right thing in merge-with?

noisesmith17:06:57

you need a lot more code to make the vector version work, regardless

Rameez17:06:18

Thanks! And after that? How do I get the "shape" back. In other words. To associate "month" with the result for each month?

noisesmith17:06:29

(cmd)user=> (merge-with - {1 10 2 5} {1 8 2 4})
{1 2, 2 1}
(ins)user=> (into [] (map (fn [[k v]] {:month k :result v})) *1)
[{:month 1, :result 2} {:month 2, :result 1}]

Rameez17:06:05

Gotcha! Thanks @noisesmith 🙂

noisesmith17:06:26

the function to get the input for the merge-with looks a lot like the function to get it back out

noisesmith17:06:57

but using into {} instead of into []

👍 4
deactivateduser17:06:57

@noisesmith disagree - it’s just a map-indexed I think.

deactivateduser17:06:18

Either way, it’s a trivial amount of code if the data is in a good “shape”.

noisesmith17:06:19

why would index even come into play?

noisesmith17:06:36

each entry already tells you its order via the data inside it

deactivateduser17:06:58

We’re probably approaching it different ways then.

deactivateduser17:06:18

TIL map can take two collections:

(def assets [10 5])
(def liabilities [8 4])
(map - assets liabilities)

Hlodowig17:06:35

Hi everybody. Any functional programming books you recommend? There's a lot of Clojure books, but I would like to find some that can help you learn the underlying paradigm, not just syntax, api, etc. Thanks.

noisesmith17:06:53

SICP is a classic, and there's even a partial translation of SICP from scheme to clojure out there

thanks2 4
AC17:06:12

http://www.sicpdistilled.com/ ^ that's the (in-progress) clojure version of SICP. you'll have to switch back to the original once you get further into it.

👍 8
noisesmith17:06:33

I don't use the term classic lightly - I learned a lot going through it when I was first learning to program, and I still get so much from it each time I go back to it

Kazuki Yokoyama17:06:12

SICP is a quite dense and large book (almost 900 pages). Is it super worthy? (I hope I'm not committing any heresy here)

noisesmith17:06:10

it's not something you need to read end to end to get the full value from IMHO, and it's much higher quality (in terms of both accuracy and clarity) than the rivals I know of

noisesmith17:06:27

but it's not a closed question, I'd be interested in hearing others suggestions too

amirali18:06:14

"closure for the brave and true" is good for both syntax and functional comprehension.

thanks2 4
Phil Hunt18:06:25

"Getting Clojure" seems quite good at articulating the thinking behind the language and "Joy of Clojure" goes deeper (but is a bit old).

thanks2 4
Kazuki Yokoyama18:06:57

I've never read it, but the TOC of Clojure Applied looks very good (any opinions on that?). One thing that helped me a lot to grasp FP was to learn some Haskell.

Lennart Buit20:06:20

^If you feel like learning a bit of Haskell, ‘Learn you a Haskell for Great Good’ is a nice lightweight Haskell book. Like the Clojure for the Brave and True of Haskell, so to say.

Joe20:06:26

Grokking Simplicity is pretty WIP, but what is out there I enjoyed. It’s code is in JS but is simple enough to understand. I also liked Scott Wlaschins ‘Domain Modelling Made Functional’ - which is F# and bangs the ADT drum hard

Hlodowig22:06:45

Thank y'all.

amirali18:06:41

hi guys, Im new so sorry for noob question. when i want to map a vector, the result is a map that has the string of that vector. example:

(defn inp []
  (let [initVector (read-line)]
    initVector))
(def matt (inp))

(defn init
  [matt]
  {:myGame matt}
  )
=> {:myGame ["[1 2 3][4 5 6]"]}
it puts the given vector inside quotation. How to get map without quotation

ghadi18:06:05

clojure.core/read-line returns the string of what was read, which is the raw input. You may want to look at clojure.core/read if you wanted to read a datastructure

alwyn18:06:44

Just tested this in the REPL, thanks!

amirali20:06:13

Thank u sir now if i use read in the following code, i get an extra brackets example: [[1 2][3 4]] => [[[1 2][3 4]]] do u know the cause?

andy.fingerhut22:06:50

Can you give an example expression you can enter in a REPL session that gives that result?

andy.fingerhut22:06:12

Also a minor note, clojure.edn/read is safer in terms of disallowing arbitrary code execution, which clojure.core/read has features that can cause that to happen. clojure.core/read should definitely not be used to read from untrusted data sources (e.g. network sockets coming from clients that you didn't write)

amirali04:06:07

(defn inp []
  (let [initVector (read)]
    initVector
    )
  )

(def matt (inp))

(defn init
  [matt]
   {:myGame matt}
  )
so if i enter [[1 2][3 4]] to the readthe result of map is [[[1 2][3 4]]] and thanks for extra info

andy.fingerhut05:06:51

Here is a sample REPL session that I just tried:

$ clj
Clojure 1.10.1
(defn inp []
  (let [initVector (read-string "[[1 2] [3 4]]")]
    initVector))
#'user/inp
user=> (def matt (inp))
#'user/matt
(defn init [matt]
  {:myGame matt})
#'user/init
user=> (init matt)
{:myGame [[1 2] [3 4]]}

andy.fingerhut05:06:12

The vector in the map does not have [[[1 2] [3 4]]]

andy.fingerhut05:06:35

You are probably doing something different than what I showed above, but I do not have a good guess what you are doing differently.

andy.fingerhut05:06:15

Maybe you are calling the function init like this? (init [matt]) ? If you are doing that, the expression [matt] means "create a vector with one element, where that one element is whatever the value of matt is." If matt 's value is [[1 2] [3 4]] , then the value of the expression [matt] is [[[1 2] [3 4]]]

👍 4
amirali05:06:31

ooo thats right sir. Thank you for advice 🙏

PB18:06:12

It seems to escape me, but with lein, how do I build my library and have it placed into my local .m2?

phronmophobic18:06:42

lein install should do the trick

🙏 4
ghadi18:06:27

no need to apologize for being a noob @amirali.sadeghloo 🙂

🙏 4
ghadi18:06:47

what you typed in [1 2 3][4 5 6] is two vectors

ghadi18:06:54

even though it was on a single line

ghadi18:06:27

you may have to call read twice

Lukas20:06:01

Hey, I'm trying to write a multimethod which dispatches on the class of the first given parameter, the second I don't care of the type

(defmulti check-answer (fn [g _] (class g)))

(defmethod check-answer ::collection [given correct]
  "test")

(check-answer '(1 2) "a")
that's what I've got but I get an ArityException. Any pointers how to resolve this? I tried (defmethod check-answer [::collection _] [given correct] "test") but _ is not allowed in this context //

hiredman20:06:30

defmulti behaves like defonce

hiredman20:06:10

meaning it doesn't redefine a multimethod if one already exists there, so likely you defined your multimethod to start with a different dispatch function

👍 4
hiredman20:06:28

fixed your dispatch function, but now the redefinition doesn't happen when you re-eval the defmulti form

hiredman20:06:02

a quick hack to get around this is to (def check-answer nil) before you (defmulti check-answer

Lukas20:06:04

this worked pretty well 😊 thanks again

Lukas20:06:21

Thanks alot

Ian Fernandez20:06:44

or (declare check-answer)

hiredman20:06:03

I don't think so

hiredman20:06:56

Clojure 1.10.1
user=> (defmulti foo (fn [] (println "a")))
#'user/foo
user=> (foo)
a
Execution error (IllegalArgumentException) at user/eval142 (REPL:1).
No method in multimethod 'foo' for dispatch value: null
user=> (declare foo)
#'user/foo
user=> (defmulti foo (fn [] (println "b")))
nil
user=> (foo)
a
Execution error (IllegalArgumentException) at user/eval150 (REPL:1).
No method in multimethod 'foo' for dispatch value: null
user=> (def foo nil)
#'user/foo
user=> (defmulti foo (fn [] (println "b")))
#'user/foo
user=> (foo)
b
Execution error (IllegalArgumentException) at user/eval158 (REPL:1).
No method in multimethod 'foo' for dispatch value: null
user=>