Fork me on GitHub
#clojure
<
2016-07-15
>
Chris O’Donnell02:07:39

@josh.freckleton: I guess keywords can have spaces in them

Chris O’Donnell02:07:13

=> (map (fn [[k _]] (name k)) mapy)
("a" "a " "a  ")

seancorfield02:07:56

A lot of libraries rely on being able to call keyword on arbitrary strings...

seancorfield02:07:15

There’s a lot of keywords you can construct programmatically that are not legal in the reader.

seancorfield02:07:37

(keyword "") => : for example

cezar02:07:57

so is this a bug or a feature? Seems like it ought to be made consistent one way or another

seancorfield02:07:13

It’s a feature. It’s been discussed many times over the years.

seancorfield02:07:54

At one point it was discovered that the reader accepts a lot more keywords than it is documented to accept and there was a change to tighten that up — but it broke a LOT of libraries, including some very popular high-profile ones, that assumed you could write literal keywords with numbers etc (`:1`, :>) so that change was reverted.

seancorfield02:07:44

And Clojure/core have said several times that it’s intentional that keyword can be used to construct (pretty much) arbitrary keywords, that you can then call namespace and name on to turn back into strings.

seancorfield02:07:51

Consider :a/b = (keyword "a" "b") = (keyword "a/b") -> (juxt namespace name) => ["a" "b"]

seancorfield02:07:54

(similarly for the symbol function)

seancorfield02:07:32

And that’s enshrined in stuff like test.check and clojure.spec too:

boot.user=> (require '[clojure.spec :as s])
nil
boot.user=> (s/def ::a keyword?)
:boot.user/a
boot.user=> (s/exercise ::a)
([:_ :_] [:!/Z :!/Z] [:J :J] [:k?q+/h+cX :k?q+/h+cX] [:-*w7 :-*w7] [:C- :C-] [:HO+1.E9n4vc.+?cy-x/wy :HO+1.E9n4vc.+?cy-x/wy] [:?y0?j!v.+cOru?6.B*.w9+.Vp?P8.++*VT*0-.i/pq :?y0?j!v.+cOru?6.B*.w9+.Vp?P8.++*VT*0-.i/pq] [:?k.K*.!dKX!9/-U0E0!s_9 :?k.K*.!dKX!9/-U0E0!s_9] [:-- :--])

cezar02:07:39

thanks for the explanation

seancorfield02:07:50

or with a conformer showing the namespace/name decomposition:

boot.user=> (s/def ::a (s/and keyword? (s/conformer (juxt namespace name))))
:boot.user/a
boot.user=> (s/exercise ::a)
([:* [nil "*"]] [:d [nil "d"]] [:u? [nil "u?"]] [:Dr0.-/V? ["Dr0.-" "V?"]] [:_b_.?!++0/L6 ["_b_.?!++0" "L6"]] [:V+O!c_.gY5D.G6o.v!Av/R ["V+O!c_.gY5D.G6o.v!Av" "R"]] [:W-p?D.!/Ou+O7s ["W-p?D.!" "Ou+O7s"]] [:W*9.D1F2.s?531/fm ["W*9.D1F2.s?531" "fm"]] [:!?f2 [nil "!?f2"]] [:y.ylfs!ow.T_?R*RL-._e??4vQ/fjc4! ["y.ylfs!ow.T_?R*RL-._e??4vQ" "fjc4!"]])

Chris O’Donnell03:07:33

@seancorfield: interestingly, the clojure.spec generator for keyword? doesn't seem to generate keys with spaces in them

seancorfield03:07:01

@codonnell: yeah, it only uses these special characters (elements [\* \+ \! \- \_ \?])

seancorfield03:07:23

(took me a while to find that deep down in clojure.test.check.generators)

Chris O’Donnell03:07:47

that seems arbitrary

Chris O’Donnell03:07:54

nice find, by the way

lfn303:07:18

I’m trying to write a stateful transducer that builds up a list of stuff and then during the flush pushes that collection through the downstream transducers. Something like this:

(defn delay-events-xf [xf]
  (let [events (volatile! [])]
    (fn
      ([] (xf))
      ([result]
       (loop [event events]
         (xf result (first event))
         (recur (rest events))))
      ([result {:keys [delay] :as event}]
        (if delay
          (do (vreset! events (conj @events event))
              result)
          (xf result event))))))
But obviously the result should change during the flush step, so doing that loop is probably wrong? I have a suspicion this is just totally out of scope of what transducers are supposed to do...

seancorfield03:07:32

It’s from the definition of symbols here https://clojure.org/reference/reader#_symbols > Symbols begin with a non-numeric character and can contain alphanumeric characters and *, +, !, -, _, ', and ? (other characters may be allowed eventually).

Chris O’Donnell03:07:38

That makes sense. So the same generator is used for symbol? as well, probably.

seancorfield03:07:49

@lfn3: If each event is a map, shouldn’t events be an empty vector to start with?

seancorfield03:07:19

Also, your loop has no recur so it just runs the first delayed event through the xf?

lfn303:07:30

Yeah, sorry I hacked apart some ‘working’ code to produce that.

lfn303:07:45

Edited the code above.

seancorfield03:07:58

Maybe (reduce xf result events) is what you’re looking for?

lfn303:07:56

Yeah that makes sense. I’ll give it a go.

lfn303:07:23

So for the record, that worked, but you can also just keep calling xf:

(defn delay-events-xf [xf]
  (let [events (volatile! [])]
    (fn
      ([] (xf))
      ([result]
       (when (seq @events)
         (xf result (first @events))
         (vreset! events (rest @events))))
      ([result {:keys [delay] :as event}]
        (if delay
          (do (vreset! events (conj @events event))
              result)
          (xf result event))))))

seancorfield03:07:42

Interesting...

josh.freckleton04:07:02

@seancorfield: @codonnell Aw sorry I missed out on the keywords discussion, your answers were both enlightening and unintuitive, so, insightful! 🙂

esirola07:07:30

hi! I have a newbie question

esirola07:07:08

I’ve just read a form like `

esirola07:07:30

(let [cancel-ch (a/chan)
        times-fn (^:once fn* [] times)] … )

esirola07:07:55

what do ^:once mean? and what’s fn*?

esirola07:07:46

I still don’t understand why not

(let [times-fn (fn [] times)] …)

amashi07:07:11

There's a long history of Lisps accepting a lot in Symbols. You can do some truly perverse things in CL (which gets even more confusing because of the reader- I thought CL was case-insensitive for longer than I'd like to admit.)

amashi08:07:08

There are some legitimate reasons to be permissive in this regard. Whether they are good enough to justify CL's level of permissiveness is another question.

amashi08:07:14

@esirola If you are a beginner you might not want to worry too much about this. And I'm very much a beginner myself, so take this with a grain of salt, but...

dominicm08:07:43

@esirola: Doesn't sound newbie to me! I've certainly not encountered code using fn*. It's possibly to do with performance. The :once metadata is an advanced feature to do with garbage collection. I haven't read this too deeply, but if you'd like to do deeper, cgrand covers it here https://web.archive.org/web/20160403170217/http://clj-me.cgrand.net/2013/09/11/macros-closures-and-unexpected-object-retention/

amashi08:07:45

fn* is, if I understand correctly, used in bootstrapping the clojure compiler.

amashi08:07:19

That is to say that clojure is largely written in Clojure, but it has to start somewhere.

amashi08:07:22

And the once metadata is as domincm says.

amashi08:07:46

So if I had to guess I'd guess that the person who wrote the piece of code you are looking at really cared about erformance in that bit of code.

amashi08:07:13

Oh- OK, that makes some sense, actually.

esirola08:07:20

definitely not for a newbie, but it’s good to know there’s a potential leak in async/onto-chan, I’m going to use channels for data management pipelines

amashi08:07:45

OK- also, sorry- thought I was in the beginners channel.... where I thought this was an odd conversation.

jarohen08:07:25

@esirola, @amashi - yep, was definitely to do with the memory leak rather than performance - that code's used for potentially infinite sequences so needs to not hang on to the whole sequence of times

jarohen08:07:26

IIUC (@bronsa?) it's because when (fn [] ...) closes over values, it can't garbage collect them until the function itself is garbage collected

esirola08:07:39

@dominicm: thanks for the pointer, I’ll read it

bronsa08:07:57

yep, correct

bronsa08:07:09

so that work around closes over the function rather than the value

jarohen08:07:14

whereas supplying the :once metadata means that it's set to null after it's first used, meaning we don't hold on to the head of the lazy seq, so it can be GC'd as it's consumed

jarohen08:07:41

so yes, not a newbie question at all 🙂

amashi08:07:13

Yeah- I was talking in that channel and failed to realize I had changed channels.

esirola08:07:31

@jarohen, @bronsa oh ok now I think I see the point, thanks a lot for the help!

adamw11:07:43

folks, possibly a newbie question to do with types. (warning, evil stuff ahead). I've been playing with net.cgrand.spreadmap, mainly because it's fun, but there is a pretty big problem I've found - it doesn't follow cell references to different sheets inside the same workbook. The code hasn't been updated in ages so it's probably just an api change, but I'm trying to work out how to fix it. The error message doesn't give me a decent stack:

java.lang.AbstractMethodError: net.cgrand.spreadmap$workbook$reify__1921.getExternalSheet(Ljava/lang/String;Ljava/lang/String;I)Lorg/apache/poi/ss/formula/EvaluationWorkbook$ExternalSheet;
which is pretty useless. I would -guess- it's failing looking up the sheet name in java but the funny thing is, I can't for the love of me find out how to set breakpoints where it does this. Is there a way to get the full stack trace for this? Like 'last exception' somewhere?

mccraigmccraig12:07:46

@adamw: try (.printStackTrace *e)

mccraigmccraig12:07:15

or indeed, just *e 🙂

adamw12:07:32

beautiful, thank you!!

josh_tackett12:07:50

I am using the salesforce RESTful API and I am trying to download 130,000 records. Problem is the API limits each request to 500 records so obviously this is taking a LONG time to download. Does anyone know a work around for this with salesforce?

adamw12:07:21

Another really obvious quesiton I'm sure - where are all these G__33 variables coming from? Is this some kind of special reify thing?

mpenet13:07:29

prolly (gensym), meaning the code was macro generated or something

adamw13:07:27

@mpenet: oic, so that means that basically that file is a clojure type presenting all the methods exposed in the Java class 'EvaluationWorkbook'?

adamw13:07:41

that explains a lot, the error is otherSheetIndex = targetEvaluator.getSheetIndex(externalSheet.getSheetName()); but there is no getSheetIndex on the reify EvaluationWorkbook in the clojure source

mpenet13:07:47

kinda yes, that function returns an anonymous class instance that implements it

adamw13:07:58

thank you!

adamw13:07:29

oh no I lie

adamw13:07:43

these two exist

adamw13:07:54

perhaps the type is different. Hmm, back to the debugger

adamw13:07:33

oh of course. It's not an EvaluationSheet it's an EvaluationWorkbook.ExternalSheet, right, got it.

adamw13:07:11

nope that;s not it. Never mind

mokr14:07:37

Experimenting with clojure.spec and reading Datomic docs. Is spec'ing :artist/name some kind of bad practice? Might seem like a strange question, but all examples I've seen use namespace-qualified keywords like ::name, but I feel like I would rather start out with specs for the problem domain rather than mixing in namespaces of my implementation domain everywhere. Any advice appreciated.

Lambda/Sierra14:07:45

:artist/name is namespace-qualified.

mpenet14:07:23

Well, if spec ever become popular we ll have collisions between kw. Ex an auth framework using :auth/ or :user/ as a dependency... Prolly better to use project specific ns (same as lein project ids).

mpenet15:07:33

That s one of the reasons I dislike the whole registry concept in spec. But it seems entirely designed around it so...

semperos15:07:45

a common way to preserve concision on your end is to use the combination of namespaced keywords with namespace aliases, so even if you have a project-qualified namespaced keyword, you can use an ns alias

candiedcode15:07:41

if i want to log the time a function takes, does anyone have a preferred method/framework recommendation

gsnewmark15:07:52

@candiedcode: clojure.core/time (https://clojuredocs.org/clojure.core/time) could be enough for simple checks, otherwise try Criterium https://github.com/hugoduncan/criterium

mateus.henrique.brum15:07:25

guys, what is the best book about clojure and web ??

mateus.henrique.brum15:07:54

I finished the joy of clojure, I would like to read a book about web and clojure.

candiedcode15:07:55

cool thanks for sharing

mateus.henrique.brum15:07:58

@akiva: Thanks, that is the best ?

akiva15:07:33

@mbrum, not only is it the best, I’m pretty sure it’s the only one.

Chris O’Donnell16:07:57

I'm having a bit of trouble properly constructing a lazy seq. I have a function f that pulls a list of things from the database. I tried (def aseq (apply concat (repeatedly f))), but the value of (f) seems to get cached and doesn't update with the database. Any ideas?

Chris O’Donnell16:07:45

To be clear, the intended behavior is, supposing (f) returns [:a :b :c], (:a :b :c :a :b :c ...), and if a new item :d is added, the seq will update to ... :a :b :c :d :a :b :c :d ....

Nicolas Boskovic16:07:44

How are you calling aseq?

Chris O’Donnell16:07:08

I think the problem is that lazy seqs are implemented using chunks. So even if I only take 10 elements, more than that may be realized.

Chris O’Donnell16:07:10

My lazy seq is eventually, but not immediately, updating to a new return value of (f).

Nicolas Boskovic16:07:35

That might be it

Nicolas Boskovic16:07:56

You'll have to eval the entirety of the return of f to check

Chris O’Donnell17:07:43

I'm working with a test database, so (f) just returns 1 or 2 elements.

Nicolas Boskovic17:07:54

And it's not updating at all? What does f do?

Chris O’Donnell17:07:52

;; (f) returns ["1"]
=> (def aseq (apply concat (repeatedly f)))
=> (take 2 aseq)
("1" "1")
=> (add-item-to-db "2")
=> (f)
["1" "2"]
=> (take 4 aseq)
("1" "1" "1" "1")
=> (take 6 aseq)
("1" "1" "1" "1" "1" "2")

Chris O’Donnell17:07:41

I want (take 4 aseq) to update immediately and give me ("1" "1" "1" "2")

mccraigmccraig17:07:31

@codonnell: lazy-seqs cache their realized values... after you took 4, the first four values are forevermore fixed

mccraigmccraig17:07:33

clojure.core/lazy-seq
([& body])
Macro
  Takes a body of expressions that returns an ISeq or nil, and yields
  a Seqable object that will invoke the body only the first time seq
  is called, and will cache the result and return it on all subsequent
  seq calls. See also - realized?

mccraigmccraig17:07:23

@codonnell: if you are after a query-result-log of sorts, you could conj your results to a vector held in an atom

Chris O’Donnell17:07:38

@mccraigmccraig: First I took 2 elements, which become cached. Then I update the database, and so (f) evaluates to ["1" "2"]. It seems to me that the 3rd and 4th elements of the seq should be realized as "1" "2" when I do my (take 4 aseq), as they are not yet cached.

Chris O’Donnell17:07:30

What I'm after is a stream that cycles through the result of a database query. When one cycle finishes, the query is run again and we cycle through the updated result of my query.

mccraigmccraig17:07:44

ah, yeah, i see what you mean - iirc that's probably down to apply not being completely 100% lazy - you will have pain if you try and depend on exactly how many items have been realized from a lazy-seq

Nicolas Boskovic17:07:46

Why would you want a lazy seq then?

Nicolas Boskovic17:07:18

If you're going to be constantly running that it would make more sense to just have evaluated data

Chris O’Donnell17:07:32

@mccraigmccraig: yeah, apply could be the problem

Chris O’Donnell17:07:44

@nnbosko: I'm nto quite sure what you mean by "just have evaluated data"

Nicolas Boskovic17:07:10

I might have expressed myself incorrectly

Nicolas Boskovic17:07:02

Laziness isn't too useful in this particular use case

Chris O’Donnell17:07:35

Well the seq I'm creating is infinite; I'm not sure how I could construct it without laziness.

Nicolas Boskovic17:07:36

If you're going to have a sorts of query->eval->query loop you should probably handle the data eagerly

mccraigmccraig17:07:25

@codonnell: why not accumulate the query responses in a atom<vector> and then repeat the concatenation of those responses

Chris O’Donnell17:07:18

@mccraigmccraig: Once I take elements from the seq, I throw them away.

mokr17:07:56

@stuartsierra: :artist/name it is indeed namespaced, but in lack of a better word, it is “explicitly” chosen for the problem at hand, not inherited from the ns you “happen” to implement the spec in. If my perspective is wrong, then should I go with ::name (eg. :myproject.something.artist/name), ::artist-name (eg. :myproject.specs/artist-name) or something else? More explicitly, I feel that I’m coupling my data to the implementation if I use the ”::” notation. I’m not talking about a library here, more like decoupled blocks (files) of code performing actions on some problem domain entity.

Chris O’Donnell17:07:09

that very lazy apply does look promising, though

Chris O’Donnell17:07:22

I need to step away right now, but I'll give it a whirl later when I have a bit more time

Lambda/Sierra17:07:48

@mokr What matters is the context in which those keys are going to be used. If you know that the keywords in your database schema are only going to be used in your application, then it is safe to use “global” namespaces such as :artist/. If you intend to share that data with other applications which do not agree on a shared schema, then your keywords should be “namespaced” under your application.

Lambda/Sierra17:07:51

The Datomic MusicBrainz example assumes the former.

mokr17:07:44

@stuartsierra: Thanks, that confirms to me that I’ve gotten this right. The Datomic example seems like a good fit for the application I have in mind at the moment. The reason I asked is that (s/valid? …) kept returning true no matter what I gave as input or defined as spec using s/keys. Now I know that there is probably a bug in the code, not in my understanding. Thanks again!

mokr18:07:09

The rather embarrassing bug was me typing :rec instead of :req in clojure.spec/keys.

Chris O’Donnell18:07:18

@mccraigmccraig: A slightly altered very-lazy-apply-concat worked for me. Thanks for the suggestion!

rattboi18:07:37

does clojure not end up evaluating let bindings if that binding is never used?

rattboi18:07:59

I have a strange case where I need to create a java object, then do some method calls on it, and I've encapsulated it in a 2 let bindings, but the method calls don't seem to be happening

rattboi18:07:06

because I end up returning the object

jjfine18:07:56

you might need a doall around the map

rattboi18:07:56

comp-config has nothing in it, but (first added-config) does. They are references to the same object though.

jjfine19:07:58

since map returns a lazy sequence that's never used, the anonymous fn won't be called

rattboi19:07:13

that makes sense

rattboi19:07:16

first forces the eval

rattboi19:07:29

thank you, that was it

danburton19:07:22

Has an arity of 0 for atom been proposed? nil seems like the obvious default: (atom) #=> (atom nil)

Nicolas Boskovic19:07:57

you'd have to change jvm/clojure/lang/Atom.java

Nicolas Boskovic19:07:10

plus the core.clj

Nicolas Boskovic19:07:16

or just the core.clj

danburton19:07:53

Looks like a lot of hoops to jump through; maybe this weekend I'll try jumping through a few.

harold21:07:04

This idiosyncrasy of every-pred caused some confusion this morning:

user> ((every-pred (constantly false)))
true
user> ((every-pred (constantly false)) 1)
false
Is this a bug?

hans21:07:50

harold: looks intentional

harold21:07:04

hans: from the code, or some other piece of reasoning?

dg21:07:06

([p]
     (fn ep1
       ([] true)

dg21:07:09

In the implementation

dg21:07:20

No arguments = intentionally true

hans21:07:32

similar to

user> (and)
true

harold21:07:32

why not just call p with no args there?

harold21:07:36

It could be a closure.

dg21:07:59

Because predicates require an argument

danburton21:07:08

You can't always call it with no args. e.g. even?

harold21:07:28

In that case it should throw...

harold21:07:47

(and would, if (p) were the code instead of true)

harold21:07:19

Also,

user> (and ((constantly false)))
false

dg21:07:59

That seems correct

harold21:07:39

Yeah, so this is still confusing to me:

user> ((every-pred (constantly false)))
true
No predicate returned a truthy value, but every-pred did...

danburton21:07:07

I think I see what you're saying, and I think I agree that the ([] true) branch of code seems wrong.

dg21:07:36

You have to make a decision one way or another. No predicate was even executed, so claiming they returned either true or false is wrong either way.

harold21:07:52

Am I right in thinking that a predicate is any function that returns truthy/falsy, or is there a more rigid definition somewhere?

harold21:07:43

dg: in that case it seems weird for every-pred to just not execute its arguments... 😛

dg21:07:17

But it can't execute them. Your (constantly ...) is special in that it has a "no arguments required" version, but in general a predicate requires 1 argument.

dg21:07:33

So just (p) should throw.

harold21:07:54

Is that specified somewhere? The notion of a predicate requiring arguments?

dg21:07:35

It's a typical definition that a predicate has type signature p : X -> Boolean.

harold21:07:48

Zero-argument functions that produce boolean values can be interesting in the face of closures or mutable state. (defn before-noon? [] ...) for half-price before noon, etc.... (not super-purely functional, I know)

danburton21:07:54

> Takes a set of predicates and returns a function f that returns true if all of its composing predicates return a logical true value against all of its arguments Ah I see. The way every-pred works is it calls each predicate with 1 argument at a time.

danburton21:07:27

((every-pred even?) 2 1) does not translate to (even? 2 1), it translates to (and (even? 2) (even? 1))

danburton21:07:24

The predicates are never invoked with anything other than arity 1.

harold21:07:29

oh, that makes sense.

harold21:07:03

Still seems like the ([] true) case could have been elided (if throwing were the intention) or just replaced with ([] (p)), ([] (and (p1) (p2)), etc.

harold21:07:11

Nice chat though, appreicate the thoughts danburtton, dg, and hans. 🙂

Nicolas Boskovic21:07:23

We have the best community, don't we, folks?

dg21:07:53

Again, we know for a fact that (p) should throw an exception, so it would be a mistake to do that.

dg21:07:08

A zero-argument function is not a predicate.

danburton21:07:27

It's less the fact that it should throw an exception, and more a promise that it will never be called that way.

harold21:07:45

and a silent assent of truth, even in the face of (constantly false).

dg21:07:25

It seems like you could make a case that every-pred shouldn't have a 0-argument arity. Maybe it was added as a form of nil-punning when you apply a list to it?

harold21:07:14

Or, rather the function returned from every-pred shouldn't have a zero-argument arity.

dg21:07:26

It's sort of like asking "What's the product of no numbers?" In math that's typically defined to be 1.

harold21:07:42

The sum of no numbers is typically 0. 😉

dg21:07:19

That's the convention for addition, yeah

harold21:07:45

I see what you're saying about (and) then.

Alex Miller (Clojure team)21:07:54

@danburton I would not bother with a jira for (atom), I think that’s unlikely to get ok’ed

Alex Miller (Clojure team)21:07:48

@harold also see (+), (*), etc. and and every-pred are defined according to logic conventions for no 0-arity and that is intentional, not a bug.

harold21:07:27

Makes sense, and seems good for the empty-list punning case w/ apply that dg raised as well.

bcbradley23:07:52

I'm still having a hard time understanding eduction. Do I have to call reduce on it?