Fork me on GitHub
#clojure
<
2019-02-15
>
Alex Miller (Clojure team)00:02:46

3% said they were using something older than 1.8, but they could also have been using something newer

dpsutton00:02:01

just as a datapoint, CIDER now requires a minimum of 1.8 to work. There are still a few cljx based projects out there that are in wide use. cemerick's url package and its dependency pathetic for example

Twice04:02:39

@noisesmith, so if I switch to refs instead of atoms, it would be ok to use watches? I'm little bit reluctant to just use a function, as even getting derived lookup-index from data that stored in first atom requires to process over million rows, that's why I was thinking about caching it.

noisesmith19:02:09

sounds like what you want is a cache, and then an atom used to derive the cached values, and clear of (parts of) the cache if the atom changes

noisesmith19:02:48

you might want to check out core.cache, and for that usage a cache used by a function over an atom would make sense, where alterations to the atom might clear some or all of the function's cache

noisesmith19:02:22

using two atoms together isn't generally doable in a correct way, but a cache of a function over an atom is doable

jumar10:02:29

Trying some examples from SICP I've found that Ratio is pretty slow - is that expected? E.g. using continued fraction to compute inverse of golden ratio:

(defn cont-frac-iter [n d k]
  (letfn [(frac-iter [i acc]
            (if (< i 1)
              acc
              ;; WARNING: if you don't use double coercion then the procedure will be slow for large k
              (recur (dec i) (double (/ (n i) (+ (d i) acc))))))]
    (frac-iter k 0)))

;; this is pretty fast
(time (double (cont-frac-iter (constantly 1)
                              (constantly 1)
                              100000)))
;; "Elapsed time: 14.10361 msecs"
;; => 0.6180339887498948

;; BUT!
;; Original version without doulbe coercion and k=5000 took more than 3 seconds!!!
;; "Elapsed time: 3642.036658 msecs"
;; => 0.6180339887498948

andy.fingerhut18:02:16

Clojure Ratio values are exact, and I believe that every single arithmetic operation you do on one also does a greatest common divisor calculation to reduce the numerator and denominator integer values to lowest terms, changing the type to an integer if the result happens to be one.

👍 5
andy.fingerhut18:02:55

Because the results are exact, if the numerator and denominator get large, then all calculations will be doing exact arithmetic on large integers.

jumar08:02:27

Great, thanks for enlightenment

Wes Hall12:02:30

Folks, this strikes me as a question where I am sure that the answer is going to embarrass me, but I am drawing a mental blank. Is there a simple way to toggle a single item within a set. I.e. if it's there remove it, if not add it? Used to do this with set xor in my JS days.

potetm12:02:59

Besides (if (contains? s i) (disj s i) (conj s i))?

Wes Hall12:02:20

Yes, exactly the code I was trying to simplify.

Wes Hall12:02:50

Had a feeling there might be a (xor s #{i}) type thing.

Wes Hall12:02:57

No big deal but felt like it might be there.

potetm12:02:10

put that in a fn called toggle-member?

Wes Hall12:02:11

Yeah, can definitely do that. I just had this, "I am sure there is an API function for this" feeling. If not it's good, means I am not going crazy 🙂.

potetm12:02:30

not sure what you want to do, but set/intersection might help

potetm12:02:43

(set/difference s (set/intersection s #{i}))?

Wes Hall12:02:07

I think the first example is probably the most readable barring the existence of a (toggle s i) function or similar. I occasionally get this weird thing that happens in my brain where I think, "Ok, I am sure that there is either a built in function for this, or an obvious pattern that I am missing". No worries, thanks for your help.

potetm12:02:12

hmmm… doesn’t do the “if it’s no there, add it part”

borkdude12:02:02

You might want to use a map: (update {:foo true} :foo not)

Wes Hall12:02:09

Hah! That's a neat trick. I think this is the kind of thing I meant by some pattern that I might be missing. Will explore whether converting to a map makes sense. This is all "first world problems", the contains check is not that complex anyway really.

David Pham12:02:28

Is it possible to write docstring in defmethod when using multimethod?

Wes Hall12:02:20

@neo2551 To my knowledge, no. Docstring goes on the defmulti.

roti13:02:08

I have this weird problem, where I get a ClassCastException when calling (class x) but no error when calling (.getClass x). Here's the code:

(let [^Property src        (get-prop inst)
       [target-id target-prop] val
       target-inst (tree-search/find-nearest-by-id inst (str target-id))
       target-class (.getClass target-inst)
       get-target-prop (get-property-fn target-class target-prop)
       ^ObservableValue target     (get-target-prop target-inst)]

       (println "Now binding" src "with" target)

       (.bind src target))
This works, but when I replace (.getClass target-inst) with (class target-inst) I get: java.lang.Class cannot be cast to clojure.lang.IFn. What's going on here?

Wes Hall13:02:50

@roti Have you shadowed, "class" somewhere else in the context? (def class ...) etc

roti13:02:26

yes, I have. damn this has cost me something, why didn't I see it?

roti13:02:31

😡

roti13:02:58

I should take a break 🙂

Wes Hall13:02:09

Not a problem. I only saw it because I have done similar things myself 🙂

David Pham13:02:49

@wesley.hall Thanks a lot for your answer 🙂

David Pham13:02:06

Guess I am doing something wrong if I need to have docstring in the method xD

Wes Hall13:02:31

@neo2551 No worries. I actually quite often drop in a regular comment at the top of my multimethods in place of an actual docstring. Seems that I often find it useful to give a little explanation about what this specific case does. There is probably nothing to attach an actual docstring to though by the time all this stuff gets munged down to .class or .js.

potetm14:02:20

@neo2551 No you’re not doing it wrong. It’s a flaw IMO.

potetm14:02:40

I needed this the other day. I’ll play around w/ it more real quick.

Noah Bogart14:02:34

So I have a test helper function that checks to see if a given string is in hashmap state, which I use like (is (last-log-contains? state "Barrier")). This works fine when it's correct, but when it's not correct, the output is: expected: (is (last-log-contains? state "Barrier")) \n actual: (not (last-log-contains? [FULL STATE CONTENTS HERE] "Barrier")). Because of how this app works, the state hashmap can occasionally be extremely massive, which is tough to handle in test output.

Noah Bogart14:02:53

(defn last-log-contains?
  [state content]
  (some? (re-find (re-pattern content)
                  (-> @state :log last :text))))

Noah Bogart14:02:23

Is there any better way to write the function to avoid this? Is there any better way to write the is assertions to avoid this?

dpsutton14:02:19

perhaps you could check (select-keys state ["Barrier"]) for the key rather than the full map. When you are wrong you will be presented only with the value of the thing you are checking

Noah Bogart15:02:11

That's not a bad idea! Let me try that

Noah Bogart15:02:33

And thank you @dpsutton and @bronsa! Your help yesterday was fundamental to me getting my PR up

dpsutton15:02:09

that's awesome. was there any performance gain? measured or just "felt"?

dpsutton15:02:39

or just feel better that you are doing things more correctly. that's always a win too 🙂

Noah Bogart15:02:14

haha Mostly the last one. Compile times seem to take a bit longer, and the tests were already pretty quick, but this is a major win for DRY

Noah Bogart15:02:10

We had 3 macros that did similar but slightly different things, and this allows me to extract the main let bindings that were shared between the 3 into a single spot

potetm15:02:28

so a couple things

potetm15:02:47

you could use the meta reader macro anywhere you want

potetm15:02:24

e.g. (defmethod ^{:doc "foo"} foo* :bar [args])

potetm15:02:30

the problem with that^ is

potetm15:02:45

You basically can’t get back to that docstring at runtime. It’s attached to the symbol foo*.

potetm15:02:37

You could also do something more drastic, like re-implement defmethod:

(defmacro defmethod*
  [multifn dispatch-val mymeta & fn-tail]
  `(. ~(with-meta multifn {:tag 'clojure.lang.MultiFn})
      addMethod
      ~dispatch-val
      ~(with-meta `(fn ~@fn-tail)
                  mymeta)))

potetm15:02:02

Then you could get back to your docstring via: (meta (get-method foo* :baz))

potetm15:02:57

However, it’s odd, because the docstring is attached to the function. Whereas the docstring of the multimethod is attached to the var.

Alex Miller (Clojure team)15:02:20

there's a jira about this btw

potetm15:02:21

i.e. you get the multimethod docstring like so: (meta #'foo)

potetm15:02:25

ah good to know

potetm15:02:25

So the unfortunate truth is, as @wesley.hall deduced, there’s no consistent place to hang a method’s metadata.

Alex Miller (Clojure team)15:02:48

well, I thought there was, not seeing it now. I know I've had some conversation about it in the last 6 months

Wes Hall15:02:46

It doesn't seem like there is much point in supporting it because defmethods have no real identity. All of the tools that give power and usefulness to docstrings rely on being able to talk about a named thing, but defmethods are not named things. Perhaps if things like the (doc) function were changed to optionally support a dispatch value in the case of multimethods or something, but otherwise... why bother? 🙂

Alex Miller (Clojure team)15:02:55

but yes, that's the tricky bit

Wes Hall15:02:39

I would say, in my case it would only be supporting docstrings in the usual "docstring location", even if they were ignored entirely at runtime, but all that is really for is consistency of documentation in the code itself.

Wes Hall15:02:16

It's not really a problem to drop in a standard comment, they just look different in cursive and I don't get the pretty green 🙂

David Pham15:02:29

Thanks a lot for all your answer!

lumpy16:02:12

(def valid-guid?
  (s/with-gen
    #(int? %)
    (constantly 1)))
=> #'test-ns/valid-guid?
(s/form valid-guid?)
=> :clojure.spec.alpha/unknown

lumpy16:02:19

is this expected behavior?

borkdude16:02:02

$ clj -A:test
Clojure 1.10.0
user=> (require '[clojure.spec.gen.alpha :as gen])
nil
user=> (require '[clojure.spec.alpha :as s])
nil
user=> (s/def ::guid (s/with-gen int? #(gen/return 1)))
:user/guid
user=> (s/form ::guid)
clojure.core/int?
user=> (gen/generate (s/gen ::guid))
1
user=>

lumpy16:02:05

(def valid-guid?
  (s/with-gen
    int?
    (constantly 1)))
=> #'test-nss/valid-guid?
(s/form valid-guid?)
=> clojure.core/int?

lumpy16:02:17

right, anonymous functions in with-gen breaks s/form

borkdude16:02:54

In spec-alpha2 that won’t work at all anymore, so you better get used to it 😉

lumpy17:02:12

form won’t work at all with with-gen specs?

lumpy17:02:31

or you won’t be able to pass anonymous functions to with-gen?

borkdude18:02:32

you cannot do (s/form int?) in spec 2. you must write (s/form (s/spec int?))

lumpy18:02:41

would (s/form (s/spec #(int? %))) work?

borkdude18:02:20

This is what I get:

user=> (s/form (s/spec #(int? %)))
(fn [%] (clojure.core/int? %))

borkdude18:02:00

minor, but why are you writing #(int? %) instead of int?

lumpy18:02:21

just a simple example

dominicm16:02:04

Is there an existing pretty printing socket server repl I can utilize? Or do I need to write my own using clojure.pprint/pprint, is clojure.pprint/pprint even suited to pretty printing in a repl?

dpsutton16:02:41

doesn't CIDER offer that but default to fipp?

dpsutton16:02:48

meaning it should be fine?

mpenet16:02:08

yeah and fipp as a specialized fn that's repl friendly if I recall

dominicm16:02:44

I'm happy to use fipp for dev, but I'm not so sure about production with fipp. I don't want to conflict with someone else's version of fipp.

dominicm16:02:00

Cider offers a clojure.pprint integration too though

vemv17:02:55

Is it possible to check whether a given symbol corresponds to a protocol method? e.g. (protocol-method? 'my-method) => true, (protocol-method? 'inc) => false

ghadi17:02:03

resolve it, and inspect the var, if it resolves

vemv17:02:12

right, metadata of such vars has a :protocol key. thanks!

bbloom18:02:11

@dominicm Fipp is very stable. It should be safe to just forcibly use the latest version.

bbloom18:02:16

I promise stability.

dominicm19:02:41

@bbloom your word is sufficient, fipp it is!

bbloom19:02:16

if you ever have a problem w/ that, feel free to complain to me directly and i’ll get a fixed build out

hmaurer21:02:32

Is there ever a legitimate use of a map from keywords to functions instead of using a multimethod?

the2bears21:02:52

I suppose if the function to be called can change at runtime based on context. Meaning you have one keyword, but the fn associated with that can change.

john21:02:46

If your multifn is just dispatching off of keywords, seems like mostly the same thing to me. You're just able to define your dispatch map across multiple namespaces and downstream projects.

john21:02:08

If all dispatch logic fits in one ns, a simple map would be just as good imo

hmaurer21:02:39

@john yeah… basically, when dispatching on some function of an argument, a multimethod seems natural (or when there is a need for extensibility). But in my project I found myself passing a keyword as the first argument to my multimethod (a dispatch key) which is then totally ignored by the body of the function. It seems odd / a map would be cleaner

john21:02:14

Yeah... IMO it would be cleaner, in some cases. The multifn approach is just so extensible - let's you build out that dispatch mechanism across namespaces.

john21:02:57

and let's downstream authors extend your dispatch map

hmaurer21:02:11

fair; makes sense. Ty 🙂

john21:02:19

np. Feel free to check out dispacio if you're looking for any more inspiration around dispatch strategies https://github.com/johnmn3/dispacio

john21:02:37

The finest-grain control might be to plug in interceptors, for manually constructing dispatch strategies

hmaurer21:02:56

Ah; I’ll definitely check this out! I had a similar question some time ago about using pattern matching v.s. multimethods; I think the answer is the same; it depends on how much you care about extensibility

hmaurer22:02:06

Why do a lot of clojure functions on sequences return lists? e.g. (rest [1 2 3]) returns (2 3), forcing me to write (into [] (rest [1 2 3])) to get a vector back. Am I doing something wrong?

emccue22:02:59

Well, more specifically they return sequences

john22:02:11

FWIU, the list is the underlying data type of the seq abstraction, which all functions that operate over sequences recognize.

emccue22:02:31

If you are using rest for your computations things will be more efficient with linked lists (probably, by guestimate)

emccue22:02:32

Either way there is always subvec

Alex Miller (Clojure team)22:02:38

@hmaurer @john protocols are implemented as a map of names to functions

Alex Miller (Clojure team)22:02:08

but special in having call-site optimizations

john22:02:23

Well, there ya' go 🙂

Alex Miller (Clojure team)22:02:05

there's plenty of places where a map of "name" to function is a better simpler choice than a multimethod or protocol

hmaurer22:02:30

Ah. Am I doing something wrong by assuming in my code that I have a vector? Because, say, if at some point in my code I assume that I have a vector and use conj to append an element to the end, I now have to add into [] or similar to all sequence operations I do on that vector, to ensure it remains a vector

hmaurer22:02:59

Thanks @alexmiller for the extra explanation on multi-methods

john22:02:09

You have to know, yeah. Otherwise conjing may do the different thing.

Alex Miller (Clojure team)22:02:19

why do you need to ensure it remains a vector?

hmaurer22:02:31

Well otherwise conj might behave differently on it

Alex Miller (Clojure team)22:02:54

in practice, I'd say this is not a problem I ever have

hmaurer22:02:48

hmm, maybe I am thinking of it as a problem but it wouldn’t be in practice. I’ll try to ignore it and see

emccue22:02:48

If you want to not worry about it you can always whip up your own fn

emccue22:02:13

I'm a bad programmer so I would

emccue22:02:21

But I would feel bad afterwords

hmaurer22:02:24

@emccue yeah, but I found myself bumping into it over and over and I thought that if the standard library behaves in that way then I might be doing something wrong

Alex Miller (Clojure team)22:02:24

as you will then just build your own bespoke collection handling that is different than what is done everywhere else

hmaurer22:02:35

since I doubt all clojure devs re-write those functions, otherwise they would be in the std lib

Alex Miller (Clojure team)22:02:46

if you're having this issue, back up and explain how you got there

Alex Miller (Clojure team)22:02:32

generally once I have sequences, I am never "conj"ing to them, I am manipulating them at the sequence level with sequence functions

hiredman22:02:43

it is usually a problem if you are trying write what is an imperative algorithm using arrays in clojure

hiredman22:02:57

which, if you are doing that, don't use vectors, clojure has arrays

Alex Miller (Clojure team)22:02:43

I think you're maybe off down a side path from where hmaurer started

Alex Miller (Clojure team)22:02:06

I rarely use conj directly

Alex Miller (Clojure team)22:02:52

https://github.com/clojure/tools.deps.alpha is probably something visible I work on regularly. it has tons of data structures and data structure manipulation. I use conj twice in the whole code base.

Alex Miller (Clojure team)22:02:08

or I should say, in two functions

john22:02:10

Rarely does a clojure programmer not know obviously what the type being passed to conj is, I suppose. I rarely find myself getting bit by this either.

hiredman22:02:18

for example, if you where directly translating some javascript code, js allows you to call push or whatever (I guess an arraylist would be a better analogue to that in clojure) to push a value on to the front of an array

hiredman22:02:00

and basically all the clojure collection code is not built to support that style of usage

hmaurer22:02:10

Well, I started like this. I have a map which I pass down some function, a “step”. This map contains a special key, e.g :remaining-steps [:a :b :c] with the steps left to execute. What I do is I take that map, and run it through all those steps until that array is empty (so first :a, look up the corresponding function, run it on the map, and keep going). Those functions can also push elements to the beginning of the vector under that key, adding more steps.

hiredman22:02:21

so if you were trying to use it like that, you would end up doing something silly like (into [] ...) everywhere

hmaurer22:02:16

I guess? I wasn’t thinking of it that way. It’s more like a bag of information that I run through a sequence of stages

hmaurer22:02:21

like a bunch of middlewares

hmaurer22:02:47

sequence of steps *

hmaurer22:02:35

basically like a middleware chain except that instead of directly calling the next one I add a keyword under a special key and some functions “above” is in charge of calling them in order until there are no more to call

Alex Miller (Clojure team)22:02:21

it's unclear from what you said whether you want a stack or a queue. if you want a stack, the fastest option is to use a list (and cons/peek/pop). You can also use a vector as a stack with insertion point at the end, but that's slower than a list.

john22:02:51

(update amap :remaining-steps into :extra-step) or conj. But you already know it's a vector, so you know which direction conj works in in that case.

john22:02:13

Oh you want to push it to the front?

hiredman22:02:20

just don't start with a vector and your stack will be great

hmaurer22:02:37

alright, fair enough

john22:02:54

or #queue

john22:02:24

But some seq ops will lose your queue type

Alex Miller (Clojure team)22:02:45

note that that syntax is cljs-only

Alex Miller (Clojure team)22:02:23

clj has a queue, but not the literal for it, you just use clojure.lang.PersistentQueue/EMPTY to get an empty one

hmaurer22:02:46

Folllow up question: is there a cleaner way to prepend a list of elements IN ORDER to a list than doing: (into some-list (reverse elements-to-prepend)) ?

hmaurer22:02:14

e.g. pretend '(4 5) to '(1 2 3) to get '(4 5 1 2 3)

hiredman22:02:52

concat (with caveats)

hmaurer22:02:35

said caveats being?

hiredman22:02:17

but what you are talking about is concat, the caveat with concat is becuase it builds lazy seqs if you are using it without care you will blow the stack when you go to realize it

hiredman22:02:53

you may want to reformulate how you keep your queue

hiredman22:02:10

instead of a list of items, keep a tree

hmaurer22:02:59

tbh performance matters 0 in my case

hiredman22:02:42

I would just use concat

hiredman22:02:03

one way to think of something like your remaining steps is as a program, and what you have is the simplest kind of program, a linear list of instructions

hiredman22:02:33

so you could support more complicated trees of instructions, so something like clojure's do

hmaurer22:02:00

sorry, what do you mean by this? (as a program)

hiredman22:02:36

it is a sequence of instructions for some abstract machine

hiredman22:02:00

you have `[do-x do-y do-z] and some program (the abstract machine) takes do-x off the control stack and does it, possibly pushing other things on to the control stack

hmaurer22:02:34

yep that’s pretty much what I have

hmaurer22:02:44

except that my do-x do-y do-z are keywords

hiredman22:02:49

so you might have something like [[:do-2-things do-x do-y] do-z] and have your abstract machine support do-2-things by pushing the 2 things back on to the control stack

hmaurer22:02:31

hmm, I see

hmaurer22:02:41

I don’t need that flexibility (for now) though, I think

hmaurer23:02:11

Basically at first I was just piping something through functions with ->, but I needed to add some machinery between every step, so I wrote this simple abstract machine because

Dustin Getz23:02:31

when reloading a namespace with a defprotocol, Is there a way to avoid the dreaded IllegalArgumentException No implementation of method: :foo of protocol: #'com.example.foo/IFoo found for class: com.example.foo.FooRecord clojure.core/-cache-protocol-fn (core_deftype.clj:527) by unloading the protocol somehow at the repl?

Dustin Getz23:02:16

the readme for toolsnnamespace says “To avoid this problem, always create new instances of records after a refresh.” which doesn’t make sense to me, defprotocol is the thing that is throwing on reload

Lennart Buit23:02:37

what they probably mean is that you need to refresh your instances of your FooRecord, because if they keep surviving post refresh they refer to the old protocol

👍 5
Lennart Buit23:02:01

one way this can happen is if you def them in a namespace, which you :refer :all.

Lennart Buit23:02:18

That said, I have very similar issues often, so there is definitely more going on

vemv05:02:16

I asked about this problem in this channel a couple times very recently. no luck... my conclusion (as suggested by someone else) is to resort to metadata-based protocol extension. I find the feature nifty anyway. You can vary-meta either defrecords ('thin' defrecords: implementing no protocols), or plain maps

hiredman23:02:48

if you support some kind of do operation in your machine, and add continuations, you can process trees of instructions without having to do annoying concats of instruction sequences

hmaurer23:02:06

@hiredman what do you mean by continuations in this context?

hiredman23:02:31

the continuation of your abstract machine

hmaurer23:02:46

as in? picking up evaluation again at some state?

hmaurer23:02:54

yeah I do support that

hiredman23:02:50

if you have that, then instead of having to implement do by pushing things back on to the control stack, you push a new continuation that continues with the old control stack after the new control stack executes

hmaurer23:02:27

:thinking_face: