Fork me on GitHub
#beginners
<
2017-12-08
>
solf10:12:58

How ugly is it to nest multiple when-let?

solf10:12:20

I can't find a good alternative, short of writing a when-let* macro that behaves like when-let but accepts multiple bindings

pithyless11:12:40

@dromar56 I use https://github.com/Engelberg/better-cond and also, consider if you should be using when-let or whether when-some is more appropriate

catlion11:12:33

Hey there, I'm totally new to Java and Clojure, sorry. Just trying to use this lib https://github.com/hiteshjasani/clj-dnsjava in repl to get nameservers of a domain name and map over returned collection

catlion11:12:15

clj.core=> (dns/ns-lookup "" :ns)
[{:type :ns, :name "slack.com.", :ttl 86201} {:type :ns, :name "slack.com.", :ttl 86201} {:type :ns, :name "slack.com.", :ttl 86201} {:type :ns, :name "slack.com.", :ttl 86201}]

catlion11:12:39

but if I try to get Target:

catlion11:12:43

clj.core=> (map #(.getTarget %) (seq (dns/ns-lookup "" :ns)))                                                                                            
                                                                                                                                                                  
IllegalArgumentException No matching field found: getTarget for class clojure.lang.PersistentArrayMap  clojure.lang.Reflector.getInstanceField (Reflector.java:271
)                                                                                                                                                                 

New To Clojure11:12:29

@catlion I would do (map #(:target %) (dns/ns-lookup "" :ns)) for example. But I don't see :target in this list.

catlion11:12:49

yeah, I wonder if clj-dnsjava "lost" some fields or just hiding them 😄

New To Clojure12:12:13

Try (.run (Lookup. "" :ns)) and (.run (Lookup. "" :mx))

New To Clojure12:12:32

I looked at dnsjava examples.

catlion12:12:30

Yeah thanks looks like I'm better off using dnsjava without wrapper

rcustodio12:12:49

good morning

rcustodio12:12:59

any good projects for mysql?

New To Clojure12:12:12

Just found that (mod -1 60) returns 59. Quite useful function when it's needed to have 0..60 repeating sequence.

solf13:12:58

lein uberjar fails with an ArrayIndexOutOfBoundsException, any idea why?

Empperi13:12:04

@dromar56 if I would have to guess your project.clj is invalid

solf13:12:18

lein run works though

solf13:12:31

And the ns declaration:

(ns sample-project.core
  (:require [cheshire.core :as json]
            [clojure.string :as string])
  ("gen-class"))

solf13:12:12

.... Found it. For some reason I wrote ("gen-class") instead of (:gen-class)

solf13:12:21

My only explanation is that I did it late last night

solf13:12:26

but I really don't remember

Empperi13:12:03

@dromar56 if that project.clj was the full file then it is definitely broken, it is missing the closing )

solf13:12:50

Are you sure? Doesn't seem so

Empperi13:12:02

in the snippet at least, dunno about your real file 🙂

Empperi13:12:13

oh no, it’s slack bugging

Empperi13:12:15

apparently

Empperi13:12:35

wtf really, it doesn’t expand properly

Empperi13:12:12

had to open the raw file into browser to see it completely

Empperi13:12:40

and yeah, now that I see it it looks ok

New To Clojure15:12:45

(var-set x {1 :f}) throws ClassCastException clojure.lang.PersistentArrayMap cannot be cast to clojure.lang.Var. Isn't x a variable?

manutter5115:12:02

I’m not sure, I think you might need 'x or #'x there

manutter5115:12:40

What you’re seeing is var-set attempting to set the value of the value of x

New To Clojure15:12:33

> (var-set 'x {1 :f})
ClassCastException clojure.lang.Symbol cannot be cast to clojure.lang.Var  clojure.core/var-set (core.clj:4203)

> (var-set #'x {1 :f})
IllegalStateException Can't change/establish root binding of: x with set  clojure.lang.Var.set (Var.java:221)

manutter5115:12:06

Cool, I think that second one is closer

New To Clojure15:12:14

var-set
function
Usage: (var-set x val)
   
Sets the value in the var object to val. The var must be
thread-locally bound.

Added in Clojure version 1.0

New To Clojure15:12:34

It's supposed to work but it doesn't!

manutter5115:12:57

I’m a little out of my depth here, but I believe that the second error message is because the var you’re trying to set has not been marked as volatile

manutter5115:12:57

(def ^:dynamic x 1)

New To Clojure15:12:41

I guess that's the case. But documentation doesn't say it needs to be volatile.

manutter5115:12:03

I used the wrong word, it’s dynamic rather than volatile

manutter5115:12:12

By the way, I’ve been using Clojure since 2011, and I’ve never written code that modifies the value of a defed var.

manutter5115:12:03

Just mentioning that in passing because I know some newcomers to Clojure are used to having global variables to work with, and that’s an anti-pattern in Clojure

manutter5115:12:43

I use atom if I need mutable state.

New To Clojure15:12:56

I misunderstood the task requirements I got so I actually don't need to re-`def` a variable. But I guess in some cases global variables are actually needed. For example, to keep a list of currently processed connections (say, when using asynchronous HTTP request handling).

manutter5115:12:19

Yeah, you have to have some way to manage/modify state, so either a db connection (for persistent state) or most of the time atoms for holding data like that

sam17:12:36

Can I get some more help on this? Seems like it should be pretty basic.

seancorfield17:12:54

@ghsgd2 Re: global state -- a better pattern is Stuart Sierra's Component library. Having mutable singletons, like a def'd atom, can lead to a lot of problems.

seancorfield17:12:04

@sammy.castaneda90 First off, I'd recommend implementing it in functions, not a macro.

seancorfield17:12:19

(defn converter [map-for-conversion default-fn] 
  (fn [key] 
    (if (contains? map-for-conversion key) 
      (get map-for-conversion key)
      (default-fn key))))
and then you can do:
(defn my-inc [n] (inc n))
(def my-special-inc (converter {0 -1} my-inc))
and then (my-special-inc 0) will produce -1 but (my-special-inc 1) will produce 2

seancorfield17:12:53

Macros really should be considered a "last resort" -- they don't compose and they have all sorts of subtle corners to be wary of.

sam18:12:22

Alright, thanks @seancorfield. I know I've used a web library that has the 'defroutes' fn or macro, I was just curious of how to accomplish something similar.

seancorfield18:12:24

defroutes is a thin macro wrapper over the routes function.

seancorfield18:12:26

(defmacro defroutes
  "Define a Ring handler function from a sequence of routes. The name may
  optionally be followed by a doc-string and metadata map."
  [name & routes]
  (let [[name routes] (macro/name-with-attributes name routes)]
   `(def ~name (routes ~@routes))))

seancorfield18:12:32

(and that's clojure.tools.macro/name-with-attributes which is a built-in helper for defining macros, that takes care of docstrings and attribute maps etc)

sam18:12:28

Yeah that's kind of what I was going for. Something that defines converters as fns in one swoop.

sam19:12:42

I guess takeaway from this is to only use macros when absolutely needed.

seancorfield21:12:53

FWIW, here's our code base stats so you can see how few macros we use

Clojure build/config 43 files 2151 total loc
Clojure source 242 files 55971 total loc,
    3097 fns, 713 of which are private,
    374 vars, 37 macros, 47 atoms,
    440 specs, 11 function specs.
Clojure tests 145 files 19354 total loc,
    23 specs, 1 function specs.
and nearly all of those are with-{some-context} style macros that provide a minimal shim around a function.

sam07:12:14

that’s neat. Thanks for sharing. What are specs? And vars are namespace global variables? Or do you pass them around to other namespaces as well?

New To Clojure17:12:09

Thank you very much, @seancorfield! Dependency injection is a good old friend 🙂

New To Clojure17:12:23

@seancorfield Please elaborate what problems could be caused by def'd atom

seancorfield18:12:47

@ghsgd2 Well, there's the general mindset of "global mutable state is bad" in FP. Then there's the fact that a def is "run" each time the namespace is loaded (so if you have a DB connection stored in a Var and you reload the namespace, you'll "leak" connections). You can partially work around that with defonce but you can still lose the reference if the namespace is cleared (which some "reloading" libraries do in order to remove old, unused definitions). There's also the issue of composition: if you have a global singleton, you cannot have more than one instance of your code running (which can be a deal-breaker for libraries and is inconvenient in your application sometimes). There's also testability: if you have global mutable state, your code is harder to test because you need to ensure the global environment is setup correctly for each test.

seancorfield18:12:11

When we first got started with Clojure at work, we hadn't taken that message on board and we had several global atoms containing state (caches, DB connections, etc) and it's been a pain in the @$$ to deal with over the years as the code base grew. We use Component for all new code now and that really helps, but we still have quite a bit of code that still relies on those old global atoms -- we've papered over some of it by having the start function in several Components mirror their internal state to the legacy global state. We expect to get rid of all the legacy global state eventually but hardly a day goes by that I wish we'd never done it that way (despite how very convenient it was at first).

noisesmith18:12:21

yeah - one thing I like to emphasize is that of all the ways of providing a value to a piece of code, a function argument is the only flexible one

noisesmith18:12:44

if you decide you need a def, or a dynamic var, or a shared atom, or an environment var, or a value sucked down from an API… - you can easily take a function arg and put it into one of those contexts

noisesmith18:12:24

but it’s the only flexible one - if you try to convert consistently between the others, you’ll have a bad time (and usually the right solution ends up being…. turn it into a function arg first somewhere)

seancorfield18:12:12

Yup. This ☝️:skin-tone-2:

Drew Verlee18:12:11

what channels would be appropriate to post an idea about a library to improve the readability of a projects?

manutter5118:12:27

If it’s about projects in general, I’d put it in #clojure (or #clojurescript if it was cljs-specific)

admay20:12:23

@drewverlee you can also post on #docs

admay23:12:38

Hey guys, I’m looking for your ideas on how to approach DB rows and Spec. I have a Postgres DB table with a bunch of columns, name, title, role, etc... which, when queried come through as :name, :title, :role, etc.... I have a spec that requires keys to look like, :reference/name, :reference/title, etc.... How would you guys go about converting to matching those keys so that the row (given it’s properly formed) matches the spec? The first thing that came to mind was to just rename all of the keys and add the :reference prefix to all of them but that seems forced.

seancorfield23:12:53

@admay clojure.java.jdbc has a :qualifier option so you can get namespace-qualified keys back from queries...

admay23:12:32

Any idea if HugSQL has a parallel?

seancorfield23:12:39

(jdbc/query my-pg-spec ["select * from the_table where ..." ...] {:qualifier "reference"})

seancorfield23:12:55

No idea. Never used HugSQL.

admay23:12:36

Fair enough, I think I can use that either way though. Thanks!

admay23:12:57

Got it working! Thanks for the heads up on the :qualifier option @seancorfield! HugSQL allows you to pass in clojure.java.jdbc options as a parameter to all of the generated functions allowing keyword qualification!

rymndhng23:12:45

@seancorfield @admay i think it would work as expected so long as you use the same version of clojure.java.jdbc, see https://www.hugsql.org/#using-advanced > Each underlying database library and corresponding HugSQL adapter may support additional options for the execute/query commands. Functions defined by def-db-fns have a variable-arity 4th argument that passes any options through to the underlying database library. doh just saw the mssage above 😊