Fork me on GitHub
#beginners
<
2018-12-07
>
jstaab01:12:37

Got another one: I'm trying to test a group of functions in a common context, which is defined by a global var. To do this, I'd like to do (binding [*x* {...}] (deftest test-1 ...) (deftest test-2 ...)), but this ends up throwing away my binding. I know about fixtures, but I'd have to use set! on the var, which feels prone to error and messiness in a way that binding doesn't, since I'd also need another global variable to restore the root binding from. Any happy medium here?

jstaab01:12:53

The other alternative of course is to call binding inside every test, or combine my assertions into a single test per binding. Both of which are kinda yucky

noisesmith01:12:34

you don't need to use set! in fixtures

noisesmith01:12:54

the fixture takes the tests to run as a function to call, you can call it inside the binding block

jstaab01:12:24

Oh, nice, I forgot about that

noisesmith01:12:30

also, instead of using binding and dynamic vars, you can have a first class function that takes the functions to test in the arglist, and makes assertions etc., and call that from inside deftest

jstaab01:12:38

I'm in cljs, and the docs don't mention that form: https://clojurescript.org/tools/testing

jstaab01:12:48

Oh good idea

noisesmith01:12:25

Then you still have a separate deftest for each fn group, but the body is small - just invoking the thing that does the tests with different args. When I worked on a db abstraction that supported multiple backends that is how we exercised all the implementations.

jstaab01:12:22

Cool, I'll try that. I do remember looking at the source for cljs fixtures, and I think they support old-style too.

stardiviner05:12:25

How to generate a sequences of dates/times? Like "2018-09-01", "2018-09-02", "2018-09-03", ...

seancorfield06:12:59

@stardiviner Clojure or ClojureScript?

seancorfield06:12:57

If you're on the JVM

user=> (map str (take 10 (iterate #(.plus % 1 java.time.temporal.ChronoUnit/DAYS) (java.time.LocalDate/of 2018 9 1))))
("2018-09-01" "2018-09-02" "2018-09-03" "2018-09-04" "2018-09-05" "2018-09-06" "2018-09-07" "2018-09-08" "2018-09-09" "2018-09-10")
user=>

stardiviner06:12:07

@seancorfield Where does the .plus comes from? I use cider-doc on it. It's from java.math.BigDecimal/plus. But I guess it might be from some java class instance's method. Can you explain it a little?

orestis06:12:49

In this case, .plus is a method of java.time.LocalDate

orestis06:12:38

The example uses direct Java interop to access the JVM8 built-in Java.time stuff. There is a clojure wrapper here: https://github.com/dm3/clojure.java-time

orestis06:12:42

The clojure wrapper doesn’t offer much, but it’s easier on the eyes :)

grierson09:12:47

how do I destructure a function that takes two maps with the same keys?

Dante T. M.11:12:45

Why do you need to destructure? If you're using the same key regardless of the map, you can just check the value inside the function, right? What is the reason for doing the destructuring? Will the value be different in some significant way?

Adam Toth11:12:23

You can map names in destructuring with the other syntax, eg. {name-a :name :as a} {name-b :name :as b} https://clojure.org/reference/special_forms#_map_binding_destructuring

nikola11:12:35

I have a function that takes four parameters, I partially apply the first two parameters, but the last two I'd like to take from a vector like [[1 2] [3 4]]. Is there a trick for that?

nikola11:12:24

I've started with (map (partial manhattan-distance x y) locations), but I obviously get a list of partially applied functions 😂. I want the result of the execution of manhattan-distance

andersmurphy11:12:13

Question: is there a function that converts a normal keyword into a qualified keyword. So :key -> ::key?

mseddon12:12:57

Perhaps #(keyword (str (ns-name *ns*)) (name %))?

mseddon12:12:39

I don't think you can actually have "::key", since that gets expanded to :whatever-ns-you-were-in/key in the reader. So this function takes a key and qualifies it with the current ns.

andersmurphy12:12:41

Thanks, yeah (fn [k] (keyword (str ns) (name k))) is what i ended up with. It’s for a spec helper macro. 🙂

mseddon12:12:24

🙂 yeah, yours is a little shorter too, I forgot you could (str ns)

dpsutton12:12:07

@nikola.kasev you're only calling it with three arguments there. (manhattan x y [1 2]). You could map #(apply manhattan x y) or you could change your distance function to operate on two points rather than four numbers and (map (partial manhattan [x y]) locations)

👍 4
Saikyun13:12:08

how do I fdef a function with variadic parameters?

jaihindhreddy13:12:16

The regex ops s/*, s/alt, s/+ and others help in this

Saikyun13:12:46

okay, but how? like now I'm using :args (s/cat :a number?)

Saikyun13:12:02

do I write :args (s/cat :a number? (s/alt :b number?))?

Saikyun13:12:16

if I want :b to be variadic

manutter5113:12:58

(defn foo [a & more]
  (prn {:a a :more more}))

(s/fdef foo
        :args (s/cat :a any? :more (s/* any?)))

Saikyun13:12:52

I've plowed through https://clojure.org/guides/spec a couple of times, but there are some stuff I still don't get... 😄

manutter5113:12:09

Yeah, I know the feeling, I always have to play around with it to get it right

manutter5113:12:38

and it’s going to change in the not-too-distant future

manutter5113:12:15

Or at least the (s/keys) stuff will.

jaihindhreddy13:12:32

Yeah, excited for s/schema and s/select

manutter5113:12:56

Yeah, that’s going to be a big improvement I expect

Saikyun13:12:44

I was also wondering about how I'd map a key of a map to another spec. e.g.

(s/def ::b number?)
(s/def ::a (s/keys :req [::b-with-different-name]))
where I'd want {:b-with-different-name 10} to qualify for ::a

Saikyun13:12:08

I'm quessing I shouldn't use s/keys, but I don't know what to use instead

Saikyun13:12:44

a lot of the spec functions run spec-impl, which I'm not supposed to run myself, so it's hard to know what to do by reading the code 😛

manutter5113:12:26

It’s easy to alias a spec, just do (s/def ::b-with-other-name ::b)

Saikyun13:12:21

oh, okay. but what if the key is really common? in my case I'm parsing xml, and I want the attrs to map to some specs some times, and other specs at other times

manutter5113:12:31

That sounds like you might not want aliasing, more like you want a multi-spec maybe?

manutter5113:12:11

I know that multi-spec exists, I’ve kind of skipped over learning about it because it sounded complicated.

Saikyun13:12:24

haha, I haven't even heard of them... 😄

manutter5113:12:38

Honestly, for something like XML attributes, I’d try to make the spec for the attrs as generic as possible, like map? or coll? or something.

manutter5113:12:53

If you’re getting the XML elements as maps with keys for :tag and :attrs, and the :attrs can be any number of different things, it’s going to be crazy to spec them all.

manutter5113:12:14

but I guess if you were dealing with a relatively small number of different types of attrs, you could do something like define ::foo-attrs, and ::bar-attrs and ::baz-attrs, etc, and then (s/def ::attrs (s/alt :foo ::foo-attrs, :bar ::bar-attrs, :baz ::baz-attrs...)

manutter5113:12:42

I think that would work.

Saikyun13:12:58

well, it's pretty simple xml in this case, and there are a bunch of values that I want to validate since the program shouldn't continue if they aren't there

Saikyun14:12:57

I'd just want to be able to do something like (s/keys :req [(::b-with-diferent-name ::b)])

alexmiller14:12:16

something like that is potentially coming in spec2

Saikyun10:12:15

ah, cool. thanks for the info 🙂

mseddon15:12:44

can i only get the line of a macro invocation (via &form), or is there a way to find the source position of an arbitrarily deep structure passed in?

alexmiller15:12:42

there’s metadata on the forms you get

mseddon15:12:29

hm... maybe i am having weird repl issues then

alexmiller15:12:43

well, that might depend actually

mseddon15:12:53

(defmacro foo [& body]
  (println (meta &form))
  (doseq [x &form]
    (println (meta x))))

(foo 1 2 3) =>
{:line 1006, :column 27}
nil
nil
nil
nil

mseddon15:12:33

if I bind the doseq to body, I get the same nil result

andrewzhurov15:12:27

Hi all, how can I achieve automatic test re-run on file changes, (as does lein test-refresh), but with clj?

andrewzhurov16:12:12

Did the job reused lein-test-refresh

clj -Sdeps "{:deps {healthsamurai/matcho {:mvn/version \"0.3.2\"} com.jakemccrary/lein-test-refresh {:mvn/version \"0.23.0\"}}}" -C:test -e "(require 'com.jakemccrary.test-refresh) (com.jakemccrary.test-refresh/monitor-project [\"test\" \"src\"] {:watch-dirs [\"src\" \"test\"] :refresh-dirs [\"src\" \"test\"]})"

mseddon15:12:28

the problem is i am doing all manner of not-particularly-amenable-to-spec validation (this is a dsl that compiles down to GLSL, so it needs some simple type analysis), so i'd like fine grained error reporting

mseddon15:12:09

I may be able to do some really horrible cruft with *file*, the clojure reader and... other stuff. but that is REALLY nasty (a macro that reads it's invocation from the source file externally with a metadata preserving read... yuck).

alexmiller15:12:44

you might want to look at read+string - that has been a useful tool in some of the repl changes I’ve done recently

alexmiller15:12:56

(this is well beyond #beginners territory :)

mseddon15:12:14

oh, thanks, where can I find that?

alexmiller15:12:20

it’s in core

mseddon15:12:38

ok great, let me study that for a bit. thanks again!

alexmiller15:12:59

oh, we added that in 1.10 I guess (time flies)

mseddon15:12:13

yeah, figured it was 😉 np looking forward to getting my teeth into that anyway

alexmiller15:12:24

it both reads and also gives you the string it read

mseddon15:12:56

I presume the issue with metadata in macros is.. propagating it from within other macros (scheme has somewhat of an easier time with 'wrapped source' information and its macroexpanders)

mseddon15:12:41

i do have metadata, I am a fool. the problem is simple- integers don't implement IMeta so my test is stupid

alexmiller16:12:00

yeah, you will mostly see it on colls

mseddon16:12:54

that's fine, i can push that down manually. phew, i thought I didn't have anything at all. (I am still a beginner ;)) Thanks again!

alexpashkov17:12:36

Hello, everybody! I’m looking for ways to optimise my a-star algorithm. In particular, I’m trying to find parts of it, that can run in parallel efficiently. I have to say that I’m an absolute beginner in parallel computing. I’m operating 2d vectors of ints (matrices) no more than 10x10 in size and I’m doing are a lot of lookups of cells. I’m wondering if it makes sense to run it in parallel? Maybe somebody can tell of the top of their had if it’s viable or not? I suspect that the question may be naive and hard/impossible to answer without deeper understanding of the problem. I’ll still try, since re-doing the algorithm to benchmark it would be quite hard. I’ve already tried to use fold from clojure.core.reducers in a simple example of reducing a range of few millions ints with a + fn. It actually took longer than a sequential version on a machine with 8 cores.

kvlt17:12:21

Anyone know how to get a cider repl to work with deps.edn? I am able to connect, but evaluating anything throws an error as such:

(require '[ion-sample.server :as server])
FileNotFoundException Could not locate ion_sample/server__init.class or ion_sample/server.clj on classpath. Please check that namespaces with dashes use underscores in the Clojure file name.  clojure.lang.RT.load (RT.java:463)

mseddon17:12:06

is the correct way to raise a clojure.lang.Compiler$CompilerException by just constructing it as (clojure.lang.Compiler$CompilerException. <args>) or is there a more idiomatic way buried in the library somewhere?

schmee17:12:14

CompilerException is internal to the compiler so you shouldn’t be raising one of those at all (AFAIK)

mseddon17:12:48

what i need is to raise it such that my correct line/column information is reported back, is there a user-exposed alternative?

mseddon17:12:06

cider/figwheel etc appear to catch that exception for the purpose

andy.fingerhut17:12:31

Is this exception being thrown from inside of a macro you are writing, perhaps?

andy.fingerhut17:12:49

I don't know of any more idiomatic way to construct one than by using the raw constructor.

mseddon17:12:51

yup. and it's working rather nicely. but I'd hate to be naughty

mseddon17:12:09

it was 'public' but then rhicky makes everything public so that isn't exactly permission to root around in there

andy.fingerhut17:12:12

I can't think of any reason it is necessarily a bad idea -- just not something that most macros do.

mseddon17:12:47

this is translating a dsl into GLSL, so i need to be pretty specific when i report the error to the user

andy.fingerhut17:12:57

There is a somewhat higher probability that it could break with a future Clojure release, because it isn't part of the Clojure public API, but if you are willing to live with that.

mseddon17:12:59

"expected a vec2, but got 1" is not much help in an entire program 🙂

mseddon17:12:07

I think I can live with it. It works so well in my case right now I'd argue it should be public 🙂

andy.fingerhut17:12:51

There are a lot of things that are declared 'public' in Java code that are still probably considered implementation details subject to change, I believe, but not always clear which is which in the Java code.

andy.fingerhut17:12:45

You are neither the first, nor likely the last, to dig through the Java implementation of Clojure and rely on something in there.

mseddon17:12:20

hehe, it's the lisp way.

Daouda18:12:34

hey guys, advent of code in clojure @potetm https://www.twitch.tv/timpote, broadcasting right now

potetm19:12:35

Thanks Daouda! I really appreciate your promoting the steam and all of your awesome questions!

alexmiller18:12:14

@mseddon the area of throwing exceptions from macros is one we have focused on a bit during 1.10

alexmiller18:12:32

my recommendation is NOT to use CompilerException directly

alexmiller18:12:05

we are specifically looking for exceptions thrown from macros and automatically wrapping them into CompilerExceptions that track the source location

mseddon18:12:17

ah, thanks. I've noticed you have a new constructor that takes a phase keyword, though the constructor I was using hasn't changed for 7 years 😉

alexmiller18:12:29

well, we try to retain backward compatibility :)

alexmiller18:12:38

we also treat some special types of exceptions as “intentional macro checks” - things like IllegalArgumentException

alexmiller18:12:09

the set of those is based on a review of all the macros in Clojure and a survey of macros in many public libraries

alexmiller18:12:01

the 3 in that set are IllegalArg, IllegalState, and ExceptionInfo (which lets you throw extra data as well)

mseddon18:12:02

I think my use-case is pretty narrow- only really useful for some hairy embedded language

mseddon18:12:14

but there's no way to attach my own source/line information in a supported way currently?

alexmiller18:12:34

well, you can attach it the same way the compiler does elsewhere

mseddon18:12:04

Where would I look for that sort of thing?

mseddon18:12:23

ah, that is basically exactly what I am doing

alexmiller18:12:38

the compiler is tracking that stuff ambiently in anonymous vars

alexmiller18:12:59

and that info ultimately comes from metadata set by the LispReader

mseddon18:12:32

yeah, so (should) mine, although for fun I can naturally 'hack' emacs and report fake errors anywhere in the code if I felt particularly mean

alexmiller18:12:05

the Compiler code is written to allow CompilerExceptions to flow through so should work fine

alexmiller18:12:14

flow up that is

alexmiller18:12:22

without re-wrapping

mseddon18:12:03

oh, so I can elide that last test?

mseddon18:12:06

ah, yes, so i can. although now cider gives me a bit of 'compiler exception' noise.

mseddon18:12:15

nothing offensive though.

mseddon18:12:07

it's just extremely useful for me to see something like this when I get a static type error generated by my transpiler:

chrisulloa20:12:03

Does anyone have an example of why you would ever use a hash-map as a function?

chrisulloa20:12:02

I understand that ({:a 1 :b 2} :a) is equivalent to (:a {:a 1 :b 2}), but has anyone found a case where the former was more convenient?

noisesmith20:12:27

@christian.gonzalez for example {nil 1 "a" 2 [1 2] 3} - it's a valid hash-map, none of its keys can be used to do a lookup

noisesmith20:12:40

keywords and symbols do that with hash-maps, other types don't

noisesmith20:12:19

having {nil x} as an arg to replace is probably the most common usage of replace

chrisulloa20:12:57

Oh that’s excellent thanks! @noisesmith

alexmiller20:12:52

any time you’re just building a straight lookup table

👍 8
alexmiller21:12:38

user=> (def dna {\a "adenine" \g "guanine" \t "thymine" \c "cytosine"})
#'user/dna
user=> (map dna "aggcgtgc")
("adenine" "guanine" "guanine" "cytosine" "guanine" "thymine" "guanine" "cytosine")

chrisulloa21:12:10

Haha that is awesome. I was having a discussion with a coworker, why would we ever do this? Question answered.

alexmiller21:12:28

generally I only use this syntax when I have a constant map which I’m using as a function, which isn’t too common in Clojure but it does happen sometimes

alexmiller21:12:08

it’s important not to do this with maps someone else is giving you, because if it is null, this will throw a NullPointerException

alexmiller21:12:36

also, I think it’s cool that the invoker of dna here doesn’t know that it’s a constant map. you could change dna later to be a defn and the invoking it would continue to work the same

manutter5121:12:22

I was going to say “that would make a cool ROT-13 encoder/decoder,” but who here remembers ROT-13? :man-shrugging:

mseddon22:12:02

Military grade encryption. 😀

😄 4
alexmiller21:12:43

yeah, it’s pretty easy with cycle and zip to make a rot-13 encode/decode map

manutter5121:12:29

Now if there were still any place you could use one…

Leonid Scott22:12:38

Hi everyone. I’m refactoring some less than pretty loop-recur code. The reason why it used is to check that all elements of a vector share certain properties. The loop-recur ensures an early exit on the first encounter of an element that doesn’t have those properties. We want to maintain this early exit. I want to replace this loop-recur with an every? function. Taking a look at the source for every?, it should exit on the first encounter of a bad element. Moreover, the source has been that way since version 1.0. However, never in the docs does it mention that the early exit is a feature of every? that is permanent. The concern is that in a later version of Clojure, the early exit property of every? might be scrapped since that property is not mentioned in the docs. Is this a valid concern? Any help is much appreciated. Thanks! https://github.com/clojure/clojure/blob/clojure-1.9.0/src/clj/clojure/core.clj#L2664

johnj22:12:27

don't understand your concern, what is this early exit thing? if the predicate doesn't hold for some value it produces false, that's all there is to it

Leonid Scott22:12:57

The vector could be quite large, so we want to make sure that once the function finds an element that doesn’t have the properties we want, the function returns false and doesn’t compute the rest of the vector.

enforser22:12:46

I don't foresee any reason that every? would ever be changed to keep going. I personally wouldn't worry about it

johnj22:12:54

@scot0530 the function source says so, it will stop at the first item for which the predicate doesn't hold, so yes, it won't compute the rest of the vector, but that "bad item" might be the last 😉

👍 4
andy.fingerhut22:12:13

You could create a unit test that fails if the implementation of every? ever changes, e.g. using a lazy sequence that generates an element that throws an exception for an element much later than one that should cause an early exit, if you are very very concerned about this for some reason.

andy.fingerhut22:12:49

Or just examine the source code on each new release of Clojure to see if it changed (although that is more manual, of course)

dpsutton22:12:05

You could just def your own predicate with a guarantee of short circuiting

andy.fingerhut22:12:40

Sure, yeah, copy the current source code of every? into your own local version.

andy.fingerhut22:12:31

Given how concerned the Clojure core team is about performance, it seems pretty far-out-there to imagine them making something like that perform worse than it does now, on purpose.

noisesmith22:12:55

something something chunking

johnj22:12:42

his concern would violate the semantics of the function, performance is another thing here

Leonid Scott22:12:25

Ok, I think I will just include a test case to keep everyone on the team happy about this. That is a good idea. Personally, I am not too concerned that the source for every? will change but I just wanted to get some other opinions. Thank you!

andy.fingerhut22:12:54

Leonid, are you saying you have a requirement in this code that the loop end on the very first element that fails the predicate, and is guaranteed to never try to call the predicate on even one more element, ever?

andy.fingerhut22:12:20

In that case, chunking of lazy sequences could cause that expectation to be violated.

johnj22:12:29

good point, maybe that's why the original version uses loop/recur 😉

andy.fingerhut22:12:16

or rather, it is definitely the case that if you pass a lazy sequence into every?, it might cause the "producer" of the lazy sequence to generate a few input elements to the every? function than it actually needs, but that shouldn't cause the current implementation of every? to call its predicate function on those extra elements.

noisesmith22:12:16

right - loop would do the same thing

noisesmith22:12:53

(and now that I look at the source of every? it is effectively implemented with loop anyway)

andy.fingerhut22:12:38

Another way of looking at the issue -- I think it is far far more likely that someone will try to hack the security of your system in a way that is independent of the implementation of every?, than that the implementation of every? will change in a way that will make you unhappy in a future version of Clojure 🙂

Leonid Scott22:12:55

Fortunately, its guaranteed that we won’t pass every? a lazy sequence in this context, so I wouldn’t be too worried about chunking from lazy sequences.

mseddon22:12:24

"The vector could be quite large, so we want to make sure that once the function finds an element that doesn’t have the properties we want, the function returns false and doesn’t compute the rest of the vector."

mseddon22:12:50

Outsidr of laziness surely it's already computed? I am confused

mseddon22:12:24

Oh, do you mean the function is evaluated?

johnj22:12:24

@noisesmith but the next call will cause some chunking right? which you can avoid using loop/recur on the vector with something like pop or same thing?

noisesmith22:12:32

that's not chunking, it's potentially realizing one extra item though

noisesmith22:12:00

you can avoid that by using rest instead of next

noisesmith22:12:04

the funny thing is that the function is calling seq anyway, so I don't see why it would use next over rest, I'm likely missing something

johnj22:12:19

@noisesmith I guess it doesn't matter since (first coll) is realizing the head all the time

noisesmith22:12:30

(seq has the same behavior of forcing an item, for the same reason - defined to return nil for empty input)

johnj22:12:25

so in this case rest won't give you "avoid realization of the head"

mseddon23:12:49

rest "Returns a possibly empty seq of the items after the first. Calls seq on its argument." So no. As I understand, given @noisesmith's statement above.

mseddon23:12:05

iiuc, the major difference between rest and next is purely to return nil vs () in the case of the empty list.

johnj23:12:59

with rest you can avoid realizing the head of non-empty seq, but that's probably a small use case

mseddon23:12:20

Ah. Then it is not at least documented in the disappointing short docstring. :) not at a repl to test atm

andy.fingerhut23:12:30

http://ClojureDocs.org is a good place to quickly write short bits of documentation you believe are correct, but of course limited to review only by people who happen to read it and choose to fix things.

mseddon23:12:36

Good answer :) with that caveat it can work.

andy.fingerhut23:12:37

Then you would be in good company with anyone adding documentation to http://ClojureDocs.org. You can add it with a note like: "note the following behavior in Clojure 1.9.0" (or whatever version you tested), if you are trying to be more precise.

andy.fingerhut23:12:14

And/or if the docs ever become wrong, edit them again

mseddon23:12:16

Good answer :) I will add that exact caveat and freely edit.

mseddon23:12:25

Moving from committee driven lisp to clojure is scary for me.

andy.fingerhut23:12:29

Well, there are only 2 implementations that most people use: Clojure/Java and ClojureScript, and they are in effect defined by their implementations. Once a version is released, it is immutable.

andy.fingerhut23:12:40

And the developers care a lot about backwards compatibility.

mseddon23:12:37

Yup, makes sense. The beauty and dangers of lisp is feeling the bits between your toes. As far as backwards compatibility goes, I would hate to accidentally close off future optimisation though :)

mseddon23:12:18

But if I can merely claim interesting implementation details for a given version, I don't inherit the guilt. Btw thanks for contributing to such an excellent community and such a usable Lisp! I hope one day to be able to give back too.

andy.fingerhut01:12:57

You are welcome. It is a fun thing to learn about, and I hope to be able to use it for more than a hobby in my future.

johnj23:12:33

@mseddon I actually found that by accident

mseddon23:12:06

I'm scared of accidents :) common lisp implementation variability taught me to read the hyperspec and only rely on what is stated, and with clojure particularly with realisation semantics I can see scope for realisation that might later be considered undefined.

johnj23:12:17

maybe the docs intention is that it doesn't matter, so omits that part, don't know if it could cause trouble with side effects though

mseddon23:12:32

It's 'unspecified', so it may not been considered. But I am way above my paygrade now.

johnj23:12:33

but there's a saying that you shouldn't mix side effects with lazy seqs