Fork me on GitHub
#clojure
<
2020-04-17
>
pinkfrog07:04:52

coding style question. to denote if an operation is successful, should we add the ? to the boolean flag? , e.g., success or success?

pinkfrog07:04:00

i find success? conflicts with a predicate.

bortexz07:04:09

Looks like success? might be a predicate for the operation

didibus08:04:37

Ya, I struggle with that too

pinkfrog08:04:48

i’d go without ? as everything has a logical truthiness.

βž• 4
hindol09:04:33

Do you sometimes secretly wish Clojure was a LISP 2? πŸ˜€

vlaaad09:04:21

can you remind me what’s the difference between lisp 1 and lisp 2?

hindol09:04:09

Function and variable names never clash! They are in separate namespaces, hence 2.

andy.fingerhut09:04:34

Not for me, personally. About the only thing that slightly annoys me about a Lisp 1 is remembering not to name parameters of functions so that they shadow common built-in functions, like map etc.

16
andy.fingerhut09:04:59

But I like that every Var has only one value, not two.

πŸ‘Œ 4
hindol09:04:28

Same for me. Naming map as m, count as cnt, reader as rdr seems unnatural. No way around it I guess.

fricze10:04:23

name is probably the sneakiest one for me πŸ˜„

πŸŽ‰ 8
David Pham10:04:38

key is also a good one

dorab21:04:27

cat (as in category) is the one that always trips me up.

andy.fingerhut21:04:18

I mean, it is not that you must avoid those names every single time -- it is avoiding them when you also want to call that function, but yeah, avoiding them all the time makes it less likely to accidentally use the wrong name when you do want to call one of those functions. The Eastwood linter can help catch such accidental uses of names, if you call them as a function.

hindol22:04:51

@U0CMVHBL2 Do you use both clj-kondo and Eastwood? How much do they overlap and how much are unique?

andy.fingerhut23:04:21

I have not used clj-kondo, and do not know precisely how much they overlap. I know that borkdude has looked at some Eastwood features to inspire similar clj-kondo features.

Setzer2209:04:43

Hi! I'm trying to extract a single project from a large monolithic codebase. What I'd like to have is something that, given a namespace, tells me all the namespaces that depend on it (so I can remove any ns outside this set). Can I do this in plain clojure, from the REPL? Or do I need some external analysis tool?

hindol09:04:44

I think tools.namespace is created for this purpose. Never used it myself. https://github.com/clojure/tools.namespace

Setzer2209:04:18

Thanks! I already tried to generate a graph and it was a huge tangled mess πŸ˜‘ Not the tool's fault thought: I think I have too many namespaces to make sense of any human-oriented visualization πŸ˜… I will check out tools.namespace!

Setzer2209:04:32

I ended up copying part of code from https://github.com/hilverd/lein-ns-dep-graph and adapting it to my use case with tools.namespace. Thanks again!

Setzer2209:04:36

I've built a small gist, for anyone finding themselves in the same situation: https://gist.github.com/setzer22/af1197b366f562408fae3e5aaa6a837a

πŸ‘ 12
Harold16:04:31

Hi, hope everyone is doing well. I had occasion for the first time to want to 'clear out' a delay. So, the next time I deref'd it the underlying function would be re-run. Looking at it (https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Delay.java) it doesn't seem like there is such a mechanism. Did I miss something? Or maybe there is a better construct to use?

noisesmith16:04:22

delays are immutable with a one time realization

noisesmith16:04:30

sounds like you just want an atom with reset!, there's nothing that directly has the behavior you describe

Harold16:04:55

Ok, that makes sense - I can see how to engineer what I want on top of an atom.

seancorfield17:04:04

@harold Perhaps this might do what you want? It's a variant/combination of atom/`delay` that we use at work:

;; copyright (c) 2015 world singles llc

(ns worldsingles.lazy-atom
  "Implements a variant of an atom that defers evaluation of its
  initial value until its first dereference -- like delay -- but
  can still be swapped/reset like a normal atom, allowing it to
  act as a cache that can be reset and made to re-run that
  initialization.

  Usage: (:require [worldsingles.lazy-atom :refer [lazy-atom]])

  (def thing (lazy-atom (some-expensive initialization)))

  @thing ;;=> evaluates value on first use
  ;; then returns that value cached until
  (reset! thing nil)
  ;; and now next dereference will re-evaluate that value")

(deftype LazyAtom [a init]
  clojure.lang.IAtom
  ;; these are just pure delegation
  (swap [this f] (swap! a f))
  (swap [this f arg] (swap! a f arg))
  (swap [this f arg1 arg2] (swap! a f arg1 arg2))
  (swap [this f x y args] (apply swap! a f x y args))
  (compareAndSet [this oldv newv] (compare-and-set! a oldv newv))
  (reset [this value] (reset! a value))
  clojure.lang.IDeref
  ;; this is the magic: if the atom's value is nil, reset it
  ;; using the initialization function (and return that value)
  (deref [this]
    (or @a (reset! a (init)))))

(defmacro lazy-atom
  "Do not evaluate initial value until first deref."
  [value]
  `(->LazyAtom (atom nil) (fn [] ~value)))

βš›οΈ 4
Harold18:04:53

Thanks @U04V70XH6 - this is a great start at codifying this idea. I appreciate the leg up.

seancorfield17:04:20

TL;DR: It acts like a delay based on the initial value. If you reset! it to nil, the next deref will re-run the original initial value again.

seancorfield17:04:46

(if you swap! or reset! it to anything else, it behaves like a regular atom)

seancorfield17:04:55

(I should probably move this to https://github.com/worldsingles/commons so it's officially open source but you're the first person I've seen asking for that sort of thing)

ryan echternacht18:04:12

Macro question, is it acceptable/reasonable/idiomatic for a macro to declare variables that are expected to be used by the resulting body? i.e. I’m trying to reduce some boilerplate, and there is a function declaration in it like

(defmacro my-macro [& forms]
  `(boiler
    (plate
      (fn [a b]
        (boiler
          (plate ~@forms))))))
Is that a reasonable/idiomatic thing to do? it feels slightly weird to have a and b β€œinjected” into the forms. This ultimately is generated a handler for something, which is why that the (fn ...) is for.

Alex Miller (Clojure team)18:04:54

it is generally frowned upon (you may find more info by searching for anaphoric macros)

ryan echternacht18:04:08

i figured it was, but also figured i should ask

Alex Miller (Clojure team)18:04:26

usually it's preferred to include the names you're making as part of the macro args

Alex Miller (Clojure team)18:04:22

really, doing so is a timebomb that future you will probably regret :)

ryan echternacht18:04:25

oh, ok so (defmacro my-macro [arg-1-name arg-2-name & forms) then I would use it as (my-macro a b (+ a b))

ryan echternacht18:04:50

ok. I definitely see the timebombliness

ryan echternacht18:04:27

not really necessary, more just wondering how far to push it

ryan echternacht18:04:17

is the form above still a β€œtimebomb” in your opinion? or just spitting names into scope the timebomb?

Alex Miller (Clojure team)18:04:20

there is only one case of this in core - proxy-super's use of the magic this symbol

didibus18:04:00

Sometimes it's nice to have implicit args, like how #() lets you implicitly use %, %1, etc. And so some people use it or <> sometimes as those placeholder names.

Alex Miller (Clojure team)18:04:31

and I'd point to as-> as an alternative to those forms that let's you name "it"

πŸ‘ 4
didibus18:04:44

But that's rarer I'd say, and some people might still dislike it, I know some people dislike #()

Alex Miller (Clojure team)18:04:13

#() is kind of a different case because it's reader syntax, not a macro

didibus18:04:15

Ya, but to me it suffers from all the same issues mostly. You can't nest it because the names would be ambiguous, the user can't use the names % anymore either because they would clash

didibus18:04:45

You have to remember what the implicit names are supposed to be

Alex Miller (Clojure team)18:04:47

yep, that's the tradeoff. really the most common tradeoff in dsls is implicit (shorter but less obvious, reduces what's allowed) vs explicit (longer, but more flexible). where you draw the line is a matter of taste :)

πŸ‘ 4
Alex Miller (Clojure team)18:04:27

(position has implicit meaning in the first one)

Alex Miller (Clojure team)18:04:19

positional = syntax = implicit = more complex (combines position and meaning)

Alex Miller (Clojure team)18:04:30

same thing with macros that introduce magic names

lvh18:04:43

I feel like there's a function for this and I can't remember what it's called, but... is there a last and butlast combo like split-at ? (I know they're finite but count would just be another way to spell that without making an extra pass)

noisesmith18:04:11

with a vector there's peek and pop

lvh18:04:38

yep; unfortunately specter really wants to pass it via apply and not a single argument

lvh18:04:44

that gives me an idea though

noisesmith18:04:46

user=> ((juxt pop peek) [:a :b :c :d :e])
[[:a :b :c :d] :e]

lvh18:04:57

i can just wrap it in a vec

lvh18:04:01

and then I get what I want sorta

lvh18:04:12

oh, hang on, these are function args so I guess they'll ... always? be an ArraySeq and so they'll be counted? and so count is not actually another pass

Alex Miller (Clojure team)18:04:51

there is no contract that it is an ArraySeq

lvh18:04:36

womp, oh well

lvh18:04:40

at least it's not a correctness issue

Alex Miller (Clojure team)18:04:28

or at least, I can not think of any place such a contract is specified (even though it might actually be an ArraySeq in practice)

didibus18:04:36

Yup, that's why I pointed it out. There's rare cases where the extra convenience might be worth the trade off for possible accidental name shadowing and having to know the implicit rules. Might be because I've been doing some Elisp lately as well. It think in mutable land, there is more often an it that's clearly what you're going to operate over. On OOP they call it this, so on those case for example I think it make sense. Like say you had a (with-some-resource ...) it would make sense to then say ok it is implicitly going to be that resource. But then once in a while you want to nest those, and you want to have access to both the inner and outer resource? And you can't because it is now shadowing. So ya, there's just a continuum of trade-off between convenience and clarity/utility

Alex Miller (Clojure team)18:04:34

yeah, and I don't think that example is "bad"

Alex Miller (Clojure team)18:04:11

culturally, I think Clojure programmers have decided generally though we prefer to draw the line to avoid that in general

Alex Miller (Clojure team)18:04:38

in a narrowly scoped dsl, it could still be the right choice

abrooks18:04:32

Is there a Slack channel for using Clojure for GIS/Geo? If not, does anyone have any experience with this? I'm amazed at (A) how little I'm finding (factual/geo looks great but there's no real examples or public projects that seem to be using it) and (B) what I do find is really, really old and incomplete.

Alex Miller (Clojure team)18:04:10

@abrooks I have not ever seen much about it

Alex Miller (Clojure team)18:04:35

factual is the biggest company example I can think of too

Alex Miller (Clojure team)18:04:01

I guess climate corp does a bunch of geo stuff, not sure if they have anything public

abrooks18:04:31

@alexmiller Ditto... but now I'm getting to. πŸ˜„ I know I've seen people demoing projects using Clojure for GIS. I'm realizing that all of the cases I've seen are proprietary. I think there's just not much happening in FOSS / open projects.

Alex Miller (Clojure team)18:04:47

what are you trying to do? there's definitely stuff out there for handling various geo formats

Braden Shepherdson18:04:15

what happens to futures that go out of scope but are still running?

mafcocinco19:04:18

IIRC, they run to completion but you can’t deref the result.

Braden Shepherdson19:04:34

ah, they also swallow exceptions and that's what was really going on. I've got it working now. I'm using a future as a way to do a bulk import asynchronously, so I don't need the result.

Alex Miller (Clojure team)19:04:31

you can set the default uncaught exception handler to catch those probably

noisesmith19:04:16

the future itself swallows the exception, so you either need to deref the future (which rethrows) or use try/catch

noisesmith19:04:40

or maybe you just don't care about the exception Β―\(ツ)/Β―

Braden Shepherdson19:04:40

I've built a try-catch into the import now, yeah.

bortexz20:04:53

I have some code that uses bigdec here and there, sometimes I need to check for equality with another number (in my current case 0), but (= 0 0M) is false. Does someone has any advice to handle this kind of situations in big codebases?

hindol20:04:42

Double equals? (==Β 0 0M) ;; => true

bortexz22:04:44

Yes, that’s what i was looking for, thanks @UJRDALZA5

bortexz20:04:24

Was looking for something that doesn’t rely on comparing with zero, applied to other numbers

noisesmith20:04:40

==

user=> (== 0 0M)
true

bortexz20:04:16

Didn’t know about ==, exactly what I was looking for, thanks!

noisesmith20:04:00

idiomatically I'd use zero? for the 0 case still

πŸ‘ 8
bortexz20:04:47

Yeah I’d usually use zero when I know for sure it’s zero, but in this case it was 0 because nothing got summed up, but could

charch23:04:19

In defprotocol I know that it's not possible to have variadic functions. That being said, can there be functions with required keyword arguments (all required)? e.g.

(defprotocol A
  (foo [this {:keys [:bar :baz]}]))

(defrecord a
  [stuff]
  A
  (foo [this {:keys [:bar :baz]}] ...))
^ is that possible?

seancorfield23:04:09

@nicholas.charchut That looks like a 2-arity function with the second one being a hash map.

charch23:04:30

indeed. however, the defrecord fails to compile on something more complex than the minimal example above. It hadn't even occurred to me to try to get said minimal example because I thought it would fail. Thank you!

seancorfield23:04:41

Keyword arguments -- & {:keys [the args]} -- are inherently variadic so you can't do that on a protocol.

seancorfield23:04:03

(you don't need the : inside the [ ] BTW)

charch23:04:47

got it. Thank you!

charch23:04:50

Is it possible to call another method within a defrecord ? that is,

(defrecord a
  [stuff]
  (foo [this] ...)
  (bar [this] ... (foo this) ...))

seancorfield23:04:43

You mean if you have a protocol with both foo and bar and you're providing implementations for both?

charch23:04:24

Yes. Given that you've defined both methods, how can one method call another in a corresponding defrecord?

seancorfield23:04:38

If you're in the same ns, that call to foo inside bar is calling the protocol which in turn will dispatch to the a.foo implementation.

charch23:04:53

That makes good sense.

seancorfield23:04:36

This is why it's often a good idea to have your protocols in a separate namespace, so the calls are explicitly into those versions.

seancorfield23:04:04

Have a look at seancorfield/next-jdbc for something that heavily protocol-based and implements several of them in terms of calls to others, e.g., https://github.com/seancorfield/next-jdbc/blob/master/src/next/jdbc/result_set.clj#L684-L690

charch23:04:58

Perfect, that contained exactly what I was looking for. Thank you!

seancorfield23:04:04

That library has API functions that are variadic which delegate to the protocol functions (which are not). That's also a useful technique for fdef (Spec) since you cannot spec protocol functions, only regular functions.

didibus06:04:15

Oh, I didn't know protocols couldn't be variadic. Seems like a weird restriction, are Java interfaces not allowed to be variadic as well? I guess I can't remember one ever being variadic, but varargs are rare in Java anyways

seancorfield17:04:11

Multiple arities are supported, but not variadic. Even so, the recommendation is typically to write one arity in the protocol and provide a wrapper for multi-arity use (unless the different arities mean specific different things).

didibus19:04:35

Hum, ok right, so not a limitation of interfaces, just that the feature isn't deemed worth implementing. Though I feel there's a piece I'm missing here. The low-level ness of protocol to me sounds like its the "use for performance" aspect of it. Which would be its fast dispatch. Wrapping the methods in a function seem counter-intuitive for that. At that point, why not keep using multi-methods?

seancorfield19:04:46

@U0K064KQV Maybe do some performance benchmarking to satisfy your curiosity and answer that question? (I think I know what the answer will be but I don't care enough about that level of performance to bother doing those tests myself).

didibus21:04:55

So I did some quick bench-marking. It seems direct protocol dispatch, through the protocol function is 33% faster than through a wrapped fn calling the protocol method under the hood. But the JVM seem to be able to optimize this, so that in hot-paths they both end up as fast.

didibus21:04:23

But, they're both about 96% faster than using a defmulti with dispatch method being "class"

didibus21:04:17

So, still very much worth using a protocol with a wrapper fn over multi-methods πŸ˜„

seancorfield21:04:04

Thank you for confirming my hunch πŸ™‚ And it also explains why that's a practice that members of the core team recommend.

didibus21:04:06

Yup. In retrospect, I hadn't really thought about everything defmulti does, calls the predicate function, compare it one by one for equality against the options, etc. Hum, actually I'm not sure of the latter, maybe it does a hash lookup, but anyways, just calling class on some input is probably adding a ton of overhead

seancorfield21:04:05

If the wrapper is just there for convenient arities and/or allowing instrument in dev/test, performance-sensitive code can still call the underlying protocol version directly (see the top-level next.jdbc namespace API, for example), but performing that shortcut with multimethods is a lot harder (but still possible, I believe).

seancorfield21:04:45

Multimethods definitely have their place -- when you need to dispatch on values and/or multiple types -- but that flexibility trades off against performance.

πŸ‘ 4
didibus21:04:26

That's a good point, if the implementation needs to call a lot of protocols within itself, you can even skip the wrapping fn. But to be honest, I was surprised how little overhead it added, only 33%, and very quickly optimized away by the JVM.

seancorfield23:04:34

(foo my-a {:bar 42 :baz "test"})