Fork me on GitHub
#beginners
<
2020-06-23
>
jeffmk00:06:49

What’s the best way to sum up one field of a seq of maps, ‘grouped by’ the value of another field? e.g.,: [{:a "foo" :count 1} {:a "bar" :count 5} {:a "foo" :count 10}] -> [{:a "foo" :count 11} {:a "bar" :count 5}]

seancorfield00:06:20

@jeffmk Probably a group-by followed by reduce-kv...

seancorfield00:06:46

(def data [{:a "foo" :count 1} ...])
(reduce-kv (fn [acc k v] 
             (conj acc {:a k :count (transduce (map :count) + 0 v)})) 
           [] 
           (group-by :a data))

jeffmk15:06:12

@seancorfield Thank you, this worked beautifully and I think is a simple enough example to help me better learn how to use transduce.

seancorfield01:06:07

That assumes there's only :a and :count in each of the original maps.

seancorfield01:06:45

If you have other keys and you need them preserving, (assoc (first v) :count (transduce (map :count) + 0 v)) instead of {:a k :count ...} would do that -- but it assumes the same sets of keys are in every map.

seancorfield01:06:51

(that wouldn't use the k argument so it could be _ to indicate it is unused)

robertfw02:06:22

I just wrote this bit of code and my "there's probably a function for this" alarm is going off. Is there are more idiomatic way to accomplish the following?

(reduce (fn [_acc some-fn]
              (when-let [result (some-fn my-input)]
                (reduced result)))
            nil [some-fn1 some-fn2 some-fn3])

phronmophobic02:06:59

some seems to work fine, right? (some #(% input) [some-fn1 some-fn2 some-fn3])

robertfw02:06:35

yup, that's a fit! thanks. i've used some? , I forgot there was a less questionable variant

😆 6
stopa02:06:43

hey team, i am trying to set up an uberjar, which i could deploy to something like google app engine. To do this, I am following tonsky's uberdeps readme: https://github.com/tonsky/uberdeps#creating-an-executable-jar Here's my code so far: https://github.com/stopachka/clap When I run:

clj -e "(compile 'clap.core)"
clj -A:uberjar --main-class clap.core
java -jar target/clap.core
I get:
Exception in thread "main" java.lang.NoSuchMethodError: 'java.lang.Object clojure.lang.Util.loadWithClass(java.lang.String, java.lang.Class)'
	at clap.core.<clinit>(Unknown Source)
Am a bit unsure how to dig deeper. Thoughts much appreciated!

sova-soars-the-sora02:06:25

Unknown Source probably means it cannot find the file ... wish I could be more hepl.

👍 3
stopa02:06:30

(if i unzip the jar, i see this) i wish the error could tell me a bit more about what it's trying to find (if it's clap/core.class i think it's there

stopa02:06:59

another thing that's interesting: if i run: java -cp clj -Spath` clap.core` things seem to work. makes me think something is messing up when i create the uberjar

hiredman04:06:08

Is the code on GitHub there up to date with what you actually running?

hiredman04:06:53

That error is very odd, in that for the name of the missing method you are getting a quoted symbol, do my guess is some weird call to a macro that doesn't validate its arguments well

seancorfield04:06:50

@stopachka I took what you had on GitHub and used my depstar instead of uberdeps to build the JAR and it worked just fine.

seancorfield04:06:07

I cloned your repo, updated the deps.edn file as shown below @stopachka:

seanc@DESKTOP-QU2UJ1N:/mnt/c/Users/seanc/clojure/clap$ ls
Makefile  README.md  app.yaml  clap.iml  client  deps.edn  server
seanc@DESKTOP-QU2UJ1N:/mnt/c/Users/seanc/clojure/clap$ cat deps.edn
{:paths ["server" "resources"]
 :deps
 {ring/ring {:mvn/version "1.8.0"}
  ring/ring-json {:mvn/version "0.5.0"}
  compojure/compojure {:mvn/version "1.6.1"}}
 :aliases {:nREPL
           {:extra-deps
            {nrepl/nrepl {:mvn/version "0.7.0"}}}
           :uberjar {:extra-deps {seancorfield/depstar {:mvn/version "RELEASE"}}
                     :main-opts ["-m" "hf.depstar.uberjar"]}}}
seanc@DESKTOP-QU2UJ1N:/mnt/c/Users/seanc/clojure/clap$ clojure -Spom && clojure -A:uberjar clap.jar -C -m clap.core
Skipping paths: resources
Compiling clap.core ...
2020-06-22 21:14:34.978:INFO::main: Logging initialized @5082ms to org.eclipse.jetty.util.log.StdErrLog
Building uber jar: clap.jar
{:warning "could not find classpath entry", :path "resources"}
{:warning "clashing jar item", :path "about.html", :strategy :noop}
{:warning "clashing jar item", :path "META-INF/services/com.fasterxml.jackson.core.JsonFactory", :strategy :concat-lines}
{:warning "clashing jar item", :path "about.html", :strategy :noop}
{:warning "clashing jar item", :path "about.html", :strategy :noop}
{:warning "clashing jar item", :path "META-INF/services/com.fasterxml.jackson.core.JsonFactory", :strategy :concat-lines}
Processing pom.xml for {clap/clap {:mvn/version "0.1.0"}}
seanc@DESKTOP-QU2UJ1N:/mnt/c/Users/seanc/clojure/clap$ PORT=8888 java -jar clap.jar
2020-06-22 21:15:10.494:INFO::main: Logging initialized @1563ms to org.eclipse.jetty.util.log.StdErrLog
:rocket: app on 8888
2020-06-22 21:15:12.605:INFO:oejs.Server:main: jetty-9.4.22.v20191022; built: 2019-10-22T13:37:13.455Z; git: b1e6b55512e008f7fbdf1cbea4ff8a6446d1073b; jvm 11.0.5+10-post-Ubuntu-0ubuntu1.118.04
2020-06-22 21:15:12.695:INFO:oejs.AbstractConnector:main: Started ServerConnector@5ebbde60{HTTP/1.1,[http/1.1]}{0.0.0.0:8888}
2020-06-22 21:15:12.697:INFO:oejs.Server:main: Started @3776ms

seancorfield04:06:52

I removed "classes" from the :paths vector because it is not needed for depstar (and right now you don't need "resources" because that directory does not exist. You'll note that I created a dummy pom.xml file using clojure -Spom which is used for determining the version (and if you were creating a library JAR and deploying to Clojars, you'd need a pom.xml file too).

stopa04:06:01

Wohoo! Thanks @seancorfield! Tried just now and works like a charm : ) -- thank you!

seancorfield04:06:46

I don't know much about uberdeps but I've seen a few people post here recently, struggling to get it to work for their projects...

👍 3
stopa04:06:32

aand it's up on google cloud https://clap-281121.uc.r.appspot.com/ thanks for the support @seancorfield!

Mark-James M.05:06:16

What's the recommended way to chain if statements in clojure?

(if (= username "mj")
  (if (= password "m")
    (println "Welcome to the system!"))
  (println "Login attempt failed."))

phronmophobic05:06:02

this expression won't print anything if the username is correct, but the password is incorrect

phronmophobic05:06:48

for this particular example, you probably want:

(if (and (= username "mj")
         (= password "m"))
  (println "Welcome to the system!")
  (println "Login attempt failed."))

Mark-James M.05:06:47

This seems to always work when username and password are correct, but only sometimes when they're incorrect, and I suspect its a scoping issue

dpsutton05:06:53

(doc cond)

👍 6
dpsutton05:06:09

also, check out cond-> (check docs and also clojuredocs)

dpsutton05:06:20

forgot if you're in cursive or CIDER but you can see them in both of those

Mark-James M.05:06:23

Thanks! My book had gotten to if statements but not cond yet

dpsutton05:06:49

F1 or control-j is perfect. i wish there was an apropos command built in.

Mark-James M.05:06:15

alright, (doc cond) worked in REPL

Mark-James M.05:06:48

If a test returns logical true, cond evaluates and returns
the value of the corresponding expr and doesn't evaluate any of the
other tests or exprs.
Wouldn't I want the test to keep evaluating for both the username and password before returning a value?

phronmophobic05:06:22

for this example, I think you simply want:

(if (and (= username "mj")
         (= password "m"))
  (println "Welcome to the system!")
  (println "Login attempt failed."))

👍 3
Mark-James M.05:06:41

This works perfect, thanks!

bananadance 3
Jim Newton08:06:24

Does anyone know whether Clojure supports unnamed macros in a sense similar to anonymous functions? I looked at the macro expansion of defmacro and it appears that it works be defining a named function, then calling an undocumented method setMacro . Am I misinterpreting the macro expansion?

(defmacro foo "docstr" [a b] `(list ~a ~@b))

(do
  (defn foo
    "docstr"
    ([&form &env a b] (seq (concat (list 'list) (list a) b))))
  (. #'foo (setMacro))
  #'foo) 

noisesmith14:06:02

what setMacro does is set :macro true on the var metadata because this requires using a var, I don't think there's a good way to create or use an anonymous macro

Jim Newton08:06:20

Is there a way for me to programmatically determine whether a given sequence of lambda lists such as '([a b] [a b c] [a b &rest c] [x y]) correspond to valid lambda lists for a function? For example,

(fn  foo
  ([a b] (+ a b))
  ([x y z] (* x (+ y z)))
  ([l m n o &rest p] nil))
is a valid function, corresponding to the lambda lists ([a b] [x y z] [l m n o &rest p]) . However,
(fn  foo
  ([a b] (+ a b))
  ([x y z] (* x (+ y z)))
  ([l m n o &rest p] nil)
  ([x y] (- x y)))

Syntax error compiling fn* at (clojure-rte:localhost:50748(clj)*:67:19).
Can't have 2 overloads with same arity
corresponding to ([a b] [x y z] [l m n o &rest p] [x y]) is not valid because [a b] conflicts with [x y] . I'd like to write a function which takes the list of lambda lists and returns an indicator of whether there is a conflict, and ideally some indicator of what the conflict is. I'm not 100% sure whether "what the conflict is" really makes sense. Why do I want this? because I want to write a macro which can be called multiple times, each time augmenting the overloads in a hidden anonymous function. If the user calls the macro a second time with a conflicting lambda list, my code should not construct an invalid function, but rather it should replace the old conflicting entry with the new one.

noisesmith14:06:30

you can do this by hand by analyzing the forms, but there's nothing built in for it

andy.fingerhut15:06:15

Every different 'version' of the function must have a different number of arguments. There can be at most one ending with & rest (note the space is significant there - &rest is just a symbol starting with a funky character, not an indication of rest/more args)

Jim Newton09:06:36

It is unfortunate that defmethod does not allow a docstring. Is that really true, or is it just an oversite in https://clojuredocs.org/clojure.core/defmethod? Why would I want such a thing? Because I've https://gitlab.lrde.epita.fr/jnewton/clojure-rte#how-to-extend-the-type-system to define application specific types. To do so, the application programmer defines several aspects of the type by explicitly calling defmethod. The unfortunately precludes the ability for the programmer to attach a docstring to his application specific type.

noisesmith14:06:15

all a doc string does is bind :doc "some string" as metadata on the var, you can do this by hand but defmethod does not allow this as it does not create a var

noisesmith14:06:20

defmulti creates a var

Stuart11:06:55

Hi, can someone help please. I'm stuck going between iterate, cycle and repeat. I want a function like this that produces a lazy sequence of the form false false false true false false false true false false false true (defn boolean-seqeuence [n]) where n determines how many false their is before each true. My best attempt so far is

(defn boolean-sequence [n]
  (cycle (concat (repeat n false) [true])))

(take 7 (boolean-sequence 3))
=> (false false false true false false false)
Is their a nicer way?

Stuart11:06:20

the concat and [true] seems off to me

mloughlin11:06:14

You could (conj (apply vector (repeat n false)) true)

👍 3
Stuart12:06:40

yes, that seems neater! thanks

🙂 3
noisesmith14:06:04

@qmstuart @michael.e.loughlin you can use vec instead of apply vector but I don't really see the benefit being offered? concat looks like it accurately describes what you want. Alternately:

(->> (repeat n false)
      (cons true)
      (cycle)
      (rest))

Chase17:06:13

I am trying to solve this challenge: https://gist.github.com/ericnormand/235696a7f7cca803f772a2d1bd1b90c3 and came up with this:

(defn uniques [coll]                                                         
  (->> coll                                                                  
       (frequencies)                                                         
       (filter #(= 1 (second %)))                                            
       (map first)))
I suspect this does not meet the requirement of having the results appear in the same order as they were in the original collection but I can't figure out how to test properly for that. No matter how I'm setting up the tests they are passing. If I execute a function does it cache those results or am I truly getting a unique frequency hashmap every time I run the test? My test collection has more than 8 elements so I don't think it's ordered so maybe I am just coincidentally getting the correctly ordered hashmap every time?

Stephen Lester17:06:01

What are your recommendations for resources detailing how to work with the REPL; that is, the exact workflow. I use Cursive but I'm not sure what I should be doing in the REPL exactly. It seems like a pain to move code there or switch back and forth so I'm sure I'm missing something.

seancorfield17:06:59

@slester147 Stu Halloway talks about this a bit in two of his talks (REPL-Driven Development and Running With Scissors). The key thing is: never type directly into the REPL.

👍 3
💯 3
seancorfield17:06:59

Always type the code into a file, inside a (comment ,,,) form if you want to avoid it accidentally becoming part of your code base, then you can eval each expression directly from the editor (into the REPL).

seancorfield17:06:43

I have hot keys bound to "eval current form" and "eval top-level form" and I eval every single small change I make (without even needing to save files).

👍 3
Stephen Lester17:06:55

Great, I'll check those out!

seancorfield17:06:45

If you don't mind paying for an excellent course on the subject, check our Eric Normand's "REPL-Driven Development" course on http://PurelyFunctional.tv

👍 12
seancorfield17:06:07

(I went through that course and loved what he is teaching -- and I recommend it to everyone)

Stephen Lester17:06:30

I've wanted to but the personal license is still pretty pricey :( but I will consider it.

Michael J Dorian17:06:27

Once you get your editor set up to allow you to easily evaluate forms, this guide covers the essentials! https://clojure.org/guides/repl/introduction

👍 3
sova-soars-the-sora19:06:07

I do a query for some info and add in some keys to fill out the dataset, but i add the keys after query because looking them up is only necessary on the results

(defn get-latest-chats [] 
   (->>
      (d/q '[:find ?authorid ?content ?messageid ?timestamp
			       :in $ ?kind 
			       :where
			       [?m :message/kind      ?kind]
			       [?m :message/messageid ?messageid]
			       [?m :message/content   ?content]
			       [?m :message/authorid  ?authorid]
			       [?m :message/timestamp ?timestamp]]
			  					@conn "chat")
      (map (fn [[ aid content mid ts ]]
         { :authorid aid :messageid mid :content content :timestamp ts }))
      (into [])
      (sort-by :timestamp)
      (take 3)
      (map 
        (fn [m] 
          (assoc :displayname (get-display-name (:authorid m)) 
                 :votes-up    (get-vote-count-ups (:messageid m)) 
                 :votes-down  (get-vote-count-downs (:messageid m)))))))
however, the above does not work, namely the portion from (map (fn [m] (assoc .... and onward.

sova-soars-the-sora19:06:30

does it make sense what i'm trying to do? and how can I achieve it ?

ghadi19:06:15

-> #datomic @sova?

sova-soars-the-sora19:06:31

it's about thread-thru

ghadi19:06:02

did you try commenting out the parts of the thread-thru ?

noisesmith19:06:11

your first arg to assoc in the map is wrong

6
ghadi19:06:49

it's better to pass in an explicit database to functions

ghadi19:06:05

(get-latest-chats db)

sova-soars-the-sora20:06:43

@ghadi i do not often think to use more than one db ... but that makes sense if one does... it all goes to one place so i think it is okay for this, but you've got me thinking about multiple dbs now

ghadi20:06:08

multiple dbs are not what I'm talking about: need to pass a value, not a reference

ghadi20:06:33

database connections are like atoms database values (in datomic) are like persistent hashmaps

gibb20:06:24

@slester147 Found any resources? If not I was thinking of doing a quick screencast of my cursive workflow that I feel works pretty well.

Stephen Lester20:06:09

I have found some but I think such a video would be really helpful, if it's not much effort on your part.

gibb20:06:36

I'll do one real quick now just from lein new to defining a few functions and sending code to repl

Stephen Lester21:06:09

Thanks! Now I'm famous on YouTube! 🙂

Stephen Lester21:06:26

And the presenter mode helped with the shortcuts, I was wondering how you did some of those things

gibb05:06:35

Another generic IDEA tip is to use the Key Promotor X plugin which will nag you any time you don't use a shortcut for something

sova-soars-the-sora20:06:06

@ghadi and dereference the value that is passed in, rather than supplying a direct deref? i'm not sure how they are different, to me they seem indistinguishable... maybe you could elaborate?

noisesmith20:06:06

the antipattern here IMHO is using deref of a global stateful resource inside otherwise pure code

noisesmith20:06:24

if you deref at the call site you decouple the state usage from the application logic

noisesmith20:06:33

this makes it much easier to implement tests, for one example

noisesmith20:06:06

I would also separate the query code from the code that processes the result of the query, but that's a lesser concern

noisesmith20:06:34

@sova one way to describe the principle is that clojure lets you write code declaratively, here I specifically mean by "declaratively" that no code outside the block you are reading can change its behavior, aside from explicitly declared parameters

👏 3
noisesmith20:06:43

so by making the connection or the result of a query an argument instead of something embedded in the procedure, you now have code which is more limited in behavior - you can make promises about what it does, and assumptions about what it can't do, that don't really apply otherwise

rig0rmortis22:06:53

EDIT: solved 🙂 hey folks, I'm a bit confused using an optional (`&`) argument to a function that is a map - if I don't use optional I print {...} which is what I expect, but when I mark the map as option I end up printing [{...}] here's a gist with my code and a comment describing the outputs: https://gist.github.com/rig0rmortis/32c019b646149d099dc9b8e1b732df54 (copied in thread here)

rig0rmortis22:06:41

...
(defn current-epoch-time []
  (.getTime (java.util.Date.)))

(defn to-json [log]
  (json/write-str log))

(defn write-to-stdout [log]
  (.write *out* (str log "\n")))

(defn log [lvl msg & kv]
  (def structured-log
    {:msg msg
     :lvl lvl
     :ts  (current-epoch-time)
     :kv kv
     })

  (->> structured-log
       to-json
       write-to-stdout))

(defmacro debug [msg & kv] `(log :debug ~msg ~@kv))
...

noisesmith22:06:46

& doesn't create an optional argument, it sets varargs (any number of arguments, zero or more), and they are all put into a sequence

rig0rmortis22:06:47

with (defn log [lvl msg & kv] ... I get:

(clj-json-logger.core/debug "foo" {:foo 1 :bar "hehe"})
{"msg":"foo","lvl":"debug","ts":1592951290464,"kv":[{"foo":1,"bar":"hehe"}]}
nil
however with (defn log [lvl msg kv] ... (no & for optional args) I get:
(clj-json-logger.core/debug "foo" {:foo 1 :bar "hehe"})
{"msg":"foo","lvl":"debug","ts":1592951290464,"kv":{"foo":1,"bar":"hehe"}}
nil
Note the missing [ and ] when I don't use the optional.

noisesmith22:06:02

as I said above, & does not mean optional, it means "any number of args follow"

noisesmith22:06:31

also, def is not appropriate for creating local values, it always creates definitions at namespace scope, use let inside defn, not def

thanks 3
rig0rmortis22:06:07

ah thanks, sorry got stuck formatting the message before I saw your response got it, so it's giving me a list with n-many args inside it?

rig0rmortis22:06:29

thanks for the tip re: let inside defn, I'll be sure to do that

noisesmith22:06:33

you can use destructuring (defn foo [x & [y]] ...)

rig0rmortis22:06:26

aha, destructing does exactly what I need it to!

noisesmith22:06:32

but also there's a syntax for multiple arities, and that's how optional args actually work:

(defn foo
  ([x] ...)
  ([x y] ...))
here y is optional, and the argument count decides which code runs

👀 3
rig0rmortis22:06:38

that makes sense - I'll play around with both of these and compare

rig0rmortis22:06:48

thank you for the help! just (finally) picked up clojure today and playing around with a toy implementation for a logger as a learning exercise 🙂

noisesmith22:06:34

oh - that reminds me - usually people write their loggers as macros instead of functions, because macros don't show up in the runtime call stack / execution context, which makes logging more clear

noisesmith22:06:57

but for now you'll just notice the extra logging stuff / or the context is always your logging ns, and you can save that for later

rig0rmortis22:06:32

ah interesting, I've got a set of macros for each log call that's calling the method e.g. debug is a macro that calls log with :debug etc edit: only macros exposed to the user, but the macros are calling functions internally in the implementation

rig0rmortis22:06:46

or do you mean having all the functions just be macros?

noisesmith22:06:23

oh I see - you aren't using an underlying log library, so this doesn't come up (yet)

👍 3
noisesmith22:06:57

usually a logger will pick up the package / ns where it runs, and if you wrap logging in a defn the ns always shows up as the logging ns (which isn't very helpful)

👍 3