Fork me on GitHub
#beginners
<
2018-06-27
>
Philip Hale00:06:57

Is it possible to use sequential destructuring to write multi-arity functions that do different things depending on the length of the input? Or is it necessary / better to rewrite the function to be variadic and call it with apply?

hiredman00:06:22

not sure what you mean

hiredman00:06:54

fn's out of the box support having different bodies for different argument counts, without destructuring

noisesmith00:06:17

depending on what you mean, this works

ins)user=> (defmulti arg-count-based (comp count list))
#'user/arg-count-based
(ins)user=> (defmethod arg-count-based :default [& etc] "?")
#object[clojure.lang.MultiFn 0x6622fc65 "clojure.lang.MultiFn@6622fc65"]
(ins)user=> (defmethod arg-count-based 2 [a b] (str "2 args" a b))
#object[clojure.lang.MultiFn 0x6622fc65 "clojure.lang.MultiFn@6622fc65"]
(ins)user=> (arg-count-based)
"?"
(ins)user=> (arg-count-based :x :y)
"2 args:x:y"
(ins)user=> (arg-count-based :x :y :z)
"?"

hiredman00:06:20

you don't need to use apply to invoke variadic functions

noisesmith00:06:33

but yes, you can also match directly on arg count as @hiredman says and that's simpler

hiredman00:06:03

"match" may be something of a misnomer

noisesmith00:06:34

that's fair, it can imply a lot more than I mean here

hiredman00:06:00

Clojure 1.9.0
user=> (defn f ([a] [a]) ([a b] [a b]) ([a b c] [a b c]))
#'user/f
user=> (f 1)
[1]
user=> (f 1 2)
[1 2]
user=> (apply f [1 2])
[1 2]
user=> 

noisesmith00:06:02

right the only difference with the multimethod is the ability to extend outside the definition and have a :default

Philip Hale00:06:20

Okay thanks, that's interesting. So a stupid example -- one using multi-arity methods and another using multimethods:

(defn juggle
  "I can only juggle three balls"
  ([b1] :trivial)
  ([b1 b2] :easy)
  ([b1 b2 b3] :hard)
  ([b1 b2 b3 & args] (throw (AssertionError. "Ouch, too many!"))))

(defmulti m-juggle (comp count list))
(defmethod m-juggle :default [& etc] (throw (AssertionError. "Ouch, too many!")))
(defmethod m-juggle 3 [& etc] :hard)
(defmethod m-juggle 2 [& etc] :easy)
(defmethod m-juggle 1 [& etc] :trivial)
Both are called with variable lists of arguments rather than a seq (which would need the apply)

noisesmith00:06:07

you could just use count as your dispatch if you want to dispatch on verious sizes of sequential input

noisesmith00:06:47

I may have misunderstood your question

Philip Hale00:06:24

no I think you're right...

Philip Hale00:06:19

what I'm actually working on is this fn:

(defn base64-expand-bytes
  "Base64 byte-expansion procedure, to reduce the number of possible values per
  byte from 256 to 64."
  [bs]
  (let [[b1 b2 b3] bs
        c1 (bit-shift-right b1 2)                             ; first 6 bits of b1
        c2 (bit-or (bit-shift-left (bit-and 2r00000011 b1) 4) ; last 2 bits of b1 plus first 4 bits of b2
                   (bit-shift-right b2 4))
        c3 (bit-or (bit-shift-left (bit-and 2r00001111 b2) 2) ; last 4 bits of b2 plus first 2 bits of b3
                   (bit-shift-right b3 6))
        c4 (bit-and 2r00111111 b3)]                           ; last 6 bits of b3
    [c1 c2 c3 c4]))
...which needs to behave slightly differently when passed a list of length 1 or 2 (it's going to return nil for bytes that should be padding). I was just wondering if it was possible to keep the argument a seq rather than changing the signature to be a list of args?

noisesmith01:06:50

a multimethod with count as dispatch would do this without needing apply

noisesmith01:06:04

but I wouldn't be reluctant to use apply

noisesmith01:06:37

and the "special logic for 1 or 2 items, something else for anything greater" matches arg count dispatch perfectly

👍 4
Philip Hale01:06:37

Thanks 🙂 -- this is just a pet project so I have no qualms rewriting / using apply, just interested to see what's recommended. I haven't used multimethods much yet, will look into that. Appreciated!

noisesmith01:06:19

personally I would only consider a multimethod if the pattern of dispatch doesn't match n, n+1, n+2, n+x pattern which function arg count dispatch does

Philip Hale01:06:06

yes that makes sense

Philip Hale01:06:31

i think that's right -- would rather keep it as a multi-arity fn and add apply in a couple of spots than use a multimethod, at least for this. although it probably doesn't matter too much either way

pez07:06:13

I would like to use embedded tests using cljs.test. (Embedded like this)

(defn- split
  "Splits text at idx"
  {:test (fn []
           (is (= [" " " "]
                  (split "  " 1)))
           (is (= ["(foo\n " "\n bar)"]
                  (split "(foo\n  \n bar)" 6))))}
  [text idx]
  [(subs text 0 idx) (subs text idx)])
It works using clojure.test, and I am told I could write a macro that makes something like this work with cljs.test as well. But I have no clue how to do it. Would it be a macro, maybe defnt that will do both the defn and the deftest forms for me? Also, I have never made a macro. 😄

blance15:06:21

Is there a way to with-redefs multiple deftests? When I wrap deftest with with-redefs, it doesn't seem to work for me

noisesmith16:06:51

with-redefs is in effect for the body inside it. (with-redefs [...] (deftest ...)) says "redefine these globals while defining my test, then reset them". Thanks to clojure's rules for looking up global vars, the test that deftest creates looks for the var, not the the binding the var held when deftest ran

noisesmith16:06:22

(deftest ... (with-redefs [...] ...)) almost works, as long as you never run tests in parallel

noisesmith16:06:33

It's cleaner to use explicit arguments in the function being tested, instead of globals. Second best is to use dynamic vars and binding. with-redefs is a hack and has many gotchas

blance17:06:12

thanks for explaining! i see what the scope is now

blance17:06:09

sadly i'm just rewriting test for a already complex system, and the test is relying on lots of external resources which needs to be redefined..

blance17:06:35

previously it's just using midje, so it works

blance17:06:18

wish there's some fixtures that works for a subset of tests in namespace

blance17:06:11

i thinki I got it working by doing (deftest (with-redefs (deftest ...))) really hacky, but the only way I can get it working

noisesmith17:06:40

one option is to make separate test namespaces (likely they would be named for scopes, eg. "foo.bar.integration" "foo.bar.unit" "foo.bar.disaster-recovery")

joelv15:06:14

so I'm using [com.novemberain/langohr "5.0.0"] in a project but it's logback configuration from one of it's dependencies com.rabbitmq/amqp-client is trumping my log4j2 configuration in my project. I've tried [com.novemberain/langohr "5.0.0" :exclusions [ch.qos.logback/logback-classic]], but still no dice. Help !!!!

joelv15:06:47

i've also set log4j.configurationFile="resources/log4j2.xml" in system.properties

hiredman15:06:46

what makes you think it is a logback configuration issue?

joelv15:06:09

whenever I remove the function calls that i'm using from langohr and the dependeny from my project, the logging turns back to my log4j2 config instead of the default logback

hiredman16:06:06

but that doesn't imply a logback configuration issue, right? that just implies something is pulling in logback (ruling out a dependency has a logback configuration file in its jar)

hiredman16:06:16

I forget how exclusions are applied, but you may need to specifically exclude the logback dependency from the library that is bringing it in, not the library that is transitively bringing it in

dguay18:06:25

Really beginner here... how should I loop through a string list? doseq? for?

noisesmith18:06:29

doseq if you are doing something for side effects, for if you are generating a sequential result and don't want to perform any operation which isn't consumed

dpsutton18:06:11

@d.guay what are you trying to accomplish. a lot of times the result will drive how you get there

dguay18:06:48

@dpsutton I'm trying to add strings to database

dpsutton18:06:04

ok. so you want to side effect for each one. for and map are lazy and not what you want. doseq sounds like a good bet. also check to see if your database interaction layer handles collections and let that handle it for you

dguay18:06:08

We use faraday

dpsutton18:06:04

looks like this is where you want to look then

dguay18:06:52

I'll look at this thanks !

Mario C.18:06:36

If I have a quoted function '(some-fn arg1 arg2). Is there a way to call it without using eval?

noisesmith18:06:43

if it's a function and not a macro (apply (resolve (first l)) (rest l)) will work if some-fn actually exists in scope

noisesmith18:06:09

but why do you end up in that situation and what are you actually trying to do? usually there's a better option

noisesmith18:06:58

pedantically there's no function there - it's a list of symbols. resolve uses a symbol to look up a var

noisesmith18:06:17

(yes a symbol is technically a function, but not the function you want)

Mario C.18:06:22

The quoted function is actually a constructor for a defrecord.

dpsutton18:06:30

you need a mapping form symbol to function. You could use your own mapping for it i suppose {'constructor ->MapConstructor ....} or you can use eval to look them up

dpsutton18:06:24

(let [constructors {'record hash-map}
      [constructor & args] '(record :a 1)]
  (apply (constructors constructor) args))
{:a 1}

noisesmith18:06:32

resolve already does that lookup

noisesmith18:06:52

but I do like using an explicit map if you are using user data (that way you have a whitelist of what they can run)

noisesmith18:06:14

a constructor function for defrecord does work with resolve, as it is in a var

dpsutton18:06:20

right. like making your own interpreter without exposing arbitrary eval

dpsutton18:06:25

i've been reading lisp in small pieces and this is exactly how you make a lisp 2

Mario C.18:06:24

Thanks guys! Going to see what I can come up with

noisesmith19:06:06

to be clear - my first example does work as shown

Clojure 1.9.0
(ins)user=> (def l '(+ 1 2 3))
#'user/l
(ins)user=> (apply (first l) (rest l))
ArityException Wrong number of args (3) passed to: Symbol  clojure.lang.AFn.throwArity (AFn.java:429)
(ins)user=> (map type l)
(clojure.lang.Symbol java.lang.Long java.lang.Long java.lang.Long)
(ins)user=> (apply (resolve (first l)) (rest l))
6

👍 4
noisesmith19:06:32

(error is intentional to show why resolve is needed)

dpsutton19:06:28

ah and it prevents arbitrary eval. (apply (resolve '(do (prn "hi") +)) 3 4) throws an error. wasn't sure what it would do

noisesmith19:06:49

yeah, resolve only makes sense for a symbol - but an explicit lookup map is better if possible

dguay19:06:01

Is there a way to create a map from the map function by getting key from coll?

(def my-coll [{:key1 "value1" :key2 "value2"} {:key1 "value1" :key2 "value9"} {:key1 "anothervalue" :key2 "value6"}])
then I can do that:
(map #(get % :key1) my-coll)    ;returns ("value1" "value1" "anothervalue")
(map #(get % :key2) my-coll)    ;returns ("value2" "value9" "value6")
I'd like to have another object that would look like this:
{:value1 ["value2" "value9"] :anothervalue ["value6"]}
not sure if that make any sense 😕

Mario C.19:06:11

Lets say I have something like this (def db-engines {"postgres" '(->PostgreSQL url)}) and I want to instantiate by calling a function say (defn db [] (get db-engines "postgres")

Mario C.19:06:56

@noisesmith By using your example would I be doing this (defn db [] (apply (resolve (first (get db-engines "postgres")))) ?

noisesmith20:06:17

that could work but why not a mapping from string to function of no args, and call that function?

Mario C.23:06:55

Hmm didn't see this for some reason but what do you mean?

noisesmith23:06:12

instead of (def db-engines {"postgres" '(->PostgreSQL url)}) (def db-engines {"postgres" #(->PostgreSQL url)})

noisesmith23:06:30

that way the thing you get back from the map is just a function you can call

Mario C.23:06:55

I am going to try this tomorrow!

dpsutton19:06:41

create a map != map just be aware @d.guay

👍 4
dguay19:06:59

Yes I know

dguay19:06:59

Is there a way to use the result of a function as a key in a map? (def my-map {:key "value"}) ;replace :key by a function result

lilactown19:06:26

update I think

dguay19:06:38

isnt that to update a value in a map?

dguay19:06:24

I want the key name to be the result of a function

sundarj19:06:55

@d.guay

=> {(inc 1) :two}
{2 :two}
you mean like this?

dguay19:06:37

{:thisistheresultofmyfunction "value"}

lilactown19:06:59

there's not a built-in fn. you could do something with map or reduce

lilactown19:06:15

https://clojuredocs.org/clojure.set/rename-keys is also available but I'm not sure it fits your use-case

dguay20:06:04

Actually I'm trying to go from this:

({:id "id1", :name "name1"} {:id "id1", :name "name2"} {:id "id2", :name "name3"} {:id "id2", :name "name4"}  {:id "id3", :name "name5"})
to this
{:id1 ["name1" "name2"] :id2 ["name3" "name4"] :id3 ["name5"]}
or something similar to work with

mg20:06:35

(reduce (fn [grouped m] (update grouped (keyword (:id m)) conj (:name m))) {} your-collection)

dguay20:06:49

That look like it's working. I must say I don't understand much of it lol

mg20:06:50

Do you know how reduce works?

dguay20:06:36

it applies a function on every item of a collection by accumulating the result?

mg20:06:46

yes, right

mg20:06:18

so that uses the 3-arity version of reduce that specifies the initial accumulator: in this case {}

mg20:06:22

empty map to start with

mg20:06:34

then for each of your input maps, it’s going to call update

mg20:06:33

update takes: 1. the map to update - grouped which is what your accumulator is called in the reduce function 2. key to update, in this case we take the value of :id in the input map and turn it into a keyword, so {:id "id1"} becomes :id1 3. the function to call on the current value of that key. In this case it’s conj. current value will be the first arg to this function 4. Any subsequent args to the function. In this case we get the value of :name from the input map to conj on

mg20:06:23

So the first time this is called, the accumulator is an empty map, and we’re calling the reducing function with {:id "id1" :name "name1"}. So the call to update will look like: (update {} :id1 conj "name1") Since there is no :id1 key in the accumulator map, the effect of this update is to set :id1 key to be equal to (conj nil "name1")

dguay20:06:16

Very interesting

dguay20:06:22

thanks a lot for your help !

dguay20:06:15

@michael.gaare would there be a way for the key to be string instead?

dguay20:06:38

like... {"id1" ("value1" "value2") ...}

mg20:06:48

sure. Just don’t call keyword on it.

mg20:06:59

(update grouped (:id m) ...)

mg20:06:28

I just put the keyword call in there so the output would match your example

dguay20:06:53

Clojure is so powerful

kennytilton15:06:10

How about

(into {}
  (for [[k v] (group-by :id m1)]
    [k (map :name v)]))

kennytilton15:06:49

m1 being the OP’s map

mg01:06:58

not bad either

mg01:06:08

ends up doing more iterations though I think

dehli20:06:10

Hi all, I've followed the guide here https://github.com/clojure-emacs/cider/blob/master/doc/up_and_running.md to get cider running for my clojurescript project. I was able to successfully connect and the repl does show up however when I try to evaluate cljs code I get that functions don't exist.

dehli20:06:04

I'm using deps.edn with an alias like:

:repl {:extra-deps {cider/cider-nrepl {:mvn/version "0.18.0-SNAPSHOT}
       :main-opts ["repl.clj"]}
and then my repl.clj looks like what's on the guide
(require (quote cider-nrepl.main)) 
(cider-nrepl.main/init ["cider.nrepl/cider-middleware"])

dehli20:06:57

Never mind 🙂 looks like I just had to run cider-jack-in-cljs

jeremy21:06:03

I'm looking to rewrite the structure of a map and I have something like this

(map #(do {:foo (:a %) :bar (:b %)}) '({:a 1 :b nil}))
How would I write this if I wanted to exclude :bar if : b was nil?

noisesmith21:06:44

for starters (fn [m] ...) is better than #(do ...)

mg21:06:41

@jeremy642 could do this a couple ways. Here’s one simple one: (map #(let [{:keys [a b]} %] (cond-> {:foo a} bar (assoc :b bar)) ...)

noisesmith21:06:58

I'd use cond-> (cond-> {:foo (:a m)} (:b m) (assoc :bar (:b m)))

noisesmith21:06:38

@michael.gaare’s version is good too

mg21:06:29

I’ve seen this kind of pattern before too, although I have mixed feelings: (merge {:foo (:a %)} (when-let [b (:b %)] {:bar b}))

jeremy21:06:05

Okay, Looks like I have a few things to look into then but the basics are the same cond-> and assoc. Excluding the merge example.

mg21:06:24

I kinda agree with @noisesmith and generally disfavor #() except in the simplest cases

jeremy21:06:44

I'll keep the fn vs #() in mind. I figured it was an accepted shorthand but I don't have a preference.

noisesmith21:06:10

right, #() is good as a shorthand, but when you add noise it stops being a shorthand

noisesmith21:06:17

the difference in character count between #(do) and (fn []) is just two characters, and the do is only there as a hack

jeremy21:06:25

Yeah, I can see it.

dpsutton21:06:33

also #(let [destructuring .... will always trigger me to switch to fn form

👍 4
noisesmith21:06:15

yeah, to me any compound form inside #() is suspicious, including let (I will make exception for a meaningful usage of -> or ->> in there though)

dpsutton21:06:15

unless the notion of % is painfully clear i will almost always write a (fn [customer] .. or put some kind of context in there with a var name

Mario C.23:06:50

Quick question: Lets says I have a two local Clojure projects in my directory, Project A and Project B. How can I use Project B in Project A? To give more context, Project B is a published Clojar that local Project A is using but I would like to change some inner workings of Project B so now I want my local Project A to reference my local copy of Project B.

Mario C.23:06:00

Hopefully that make some sense

Mario C.23:06:49

In my project.clj I have {:dependencies [ [projectB "x.x.x"] ]}