Fork me on GitHub
#beginners
<
2018-11-21
>
hiredman00:11:40

macros that combine def with the creation of something are always bad

reborg00:11:26

defprotocol for instance, is a macro following that principle. I guess it depends?

hiredman00:11:16

defprotocol really can't work any other way for a mix of reasons and if you had to lump them together you might just call "interop"

hiredman00:11:34

defprotocol both has compile time effects and interacts with jvm classes, which result in global names

hiredman00:11:10

they are bad because they assume everything being created should be named globally

hiredman00:11:25

they are bad because naming something is not the same as creating something, and we have both many ways to name things (def, let, fn application) and many different things to create

hiredman00:11:10

one thing missing from that spec definition is a generator, and you cannot add a generator because the macro combines s/def and creating the predicate

reborg01:11:16

(defmacro sspec [proto] 
  `(s/def ~(keyword (str (.getName *ns*)) (str proto))
    (s/with-gen
      #(satisfies? ~proto %)
      #(s/gen #{~(->SomeRecord)}))))

lennart.buit06:11:53

So generating the anonymous function with a macro but not the s/def from a macro is not a code smell then?

gchewie00:11:34

OK, walking through the Guestbook for Luminus, and running into a date filter formatting error with the following code:

(defn save-message! [{:keys [params]}]
  (if-let [errors (validate-message params)]
    (-> (response/found "/")
        (assoc :flash (assoc params :errors errors)))    
    (do
      (db/save-message!
       (assoc params :timestamp (java.util.Date.))
       (response/found "/")))))
Error is
On filter body 'item.timestamp|date:"yyyy-MM-dd HH:mm"' and filter 'date:"yyyy-MM-dd HH:mm"' this error occurred:2018-11-20T00:39:40.733 is not a valid date format.
SQL in resources/sql/queries is
-- :name save-message! :! :n
-- :doc creates a new message
INSERT INTO guestbook
(name, message, timestamp)
VALUES (:name, :message, :timestamp)

gchewie00:11:21

I'm sure I could change that to a formatted string before handing it off.

gchewie00:11:56

Any ideas on where I can look next to fix this?

hiredman00:11:29

that isn't a sql issue, that is a templating error

hiredman00:11:04

the timestamp value you are passing to the template is a string, not a date object

hiredman00:11:57

user=> (require '[selmer.parser :refer [render]])
nil
user=> (render "{{creation-time|date:\"yyyy-MM-dd_HH:mm:ss\"}}" {:creation-time (java.util.Date.)})
"2018-11-20_16:35:14"
user=> (render "{{creation-time|date:\"yyyy-MM-dd_HH:mm:ss\"}}" {:creation-time "2018-11-20_16:35:14"})
Exception On filter body 'creation-time|date:"yyyy-MM-dd_HH:mm:ss"' and filter 'date:"yyyy-MM-dd_HH:mm:ss"' this error occurred:2018-11-20_16:35:14 is not a valid date format.  selmer.filter-parser/apply-filters/fn--1342 (filter_parser.clj:135)
user=> 

jstaab01:11:52

Hey all, in clojurescript, why does this not work: (into {} '((2 1))), but this does: (into {} (into [] (map #(into [] %) '((2 1)))))? I get No protocol method IMapEntry.-key defined for type number: 2

jstaab01:11:00

Still curious though, and is there a better way than (into {} (map #(into [] %) '((1 2)))?

hiredman01:11:36

(into [] ...) is vec

noisesmith01:11:52

there's also (apply hash-map '(1 2)) if you control the data structure coming in

jstaab02:11:33

👍 that does it

lew.alexander.k02:11:22

Hi all! I have a Java project, and I'd like to invoke some Clojure from it. (Ideally, the Clojure doesn't need to be re-compiled to a jar each time it changes.) Following Method 1 here (https://push-language.hampshire.edu/t/calling-clojure-code-from-java/865), I can download a Clojure .jar file, put some Clojure code in a script, and use require.invoke(Clojure.read("my.namespace.core")); and Clojure.var to grab the IFn I want to execute. My question is: if I want the Clojure file to be able to pull in dependencies (e.g. from Github, as described in this documentation for deps.edn: https://clojure.org/guides/deps_and_cli#_using_git_libraries), is there a way to do that? Where would I put the deps.edn? Or would I use a different mechanism?

alexmiller02:11:41

that’s used to build a classpath (at the shell level)

alexmiller02:11:18

it does not dynamically add stuff to your java classpath

alexmiller02:11:50

there are ways to do so using various Clojure libraries, but that is probably overkill for what you’re doing

lew.alexander.k02:11:36

@alexmiller Thanks! Good to know. Yes, I should be able to avoid dynamically adding to the classpath... I know ahead of time what Git library I'll need my Clojure scripts to rely on.

lew.alexander.k02:11:53

I'm newish to JVM development and don't have a great mental model of dependencies / the classpath. Do you have a sense of what the best way to accomplish this might be? Maybe I can clone the Github Clojure repo locally, and somehow add it to the classpath for my java project? In that case, will require.invoke(Clojure.read("my.namespace.core")) succeed (if my.namespace.core uses (require ...) to pull in names from the Git library)?

lew.alexander.k02:11:58

(In that case, how can I handle the dependencies of the Github repo, which are specified in a deps.edn file of its own?)

lew.alexander.k03:11:21

Cloning the git repo and making sure it's on my classpath seems to have done the trick

minhnhat10bk09:11:18

can some body explain why this work

minhnhat10bk09:11:16

(every? #(Character/isSpace %)  "    ") 

minhnhat10bk09:11:36

(every? Character/isSpace  "   ") 

henrik09:11:57

@minhnhat10bk No, looks weird. This works fine:

(every? #(= \space %) "  ")
;; => true

(defn is-space [ch]
  (= \space ch))

(every? is-space "  ")
;; => true

(every? #(is-space %) "   ")
;; => true

schmee10:11:26

@minhnhat10bk Java static methods are not first-class in Clojure, so you can’t use them in higher-order functions like map without wrapping them like you did

masta10:11:06

I have seen somewhere that *name* notation is used. And it is called "ear muffs" or something. Could someone remind me when to use such convention

didibus10:11:35

Only when you make the Var dynamic

mseddon10:11:59

how can I create a deftype that I can apply? Is there a protocol I can extend for that for clojure and clojurescript?

mseddon10:11:04

oh, duh. IFn

mseddon11:11:10

huh, okay, that works for clojurescript only. So I suppose I will need a gen-class implementation for the jvm, or is there a more convenient way to define them?

didibus11:11:30

What do you mean?

mseddon11:11:55

ah, okay, I am creating (to learn more about clojure) a "vec3" class, which stores 3 floats in an array

didibus11:11:00

You're creating a custom datastructure and you want it to support being called like a function?

mseddon11:11:15

and I want that type to be available in both clojure and clojurescript

mseddon11:11:54

so I have solved the clojurescript part, because IFn is a protocol, but in clojure, it's an interface, and I wonder, do I just have to bite it and implement it as a gen-class on the jvm side, or is there a way I can define both versions at the same time?

didibus11:11:17

Oh, hum, well I'm not sure how to make it work for both. In Clojure you have to implement the IFn interface. And in ClojureScript extend the IFn protocol

didibus11:11:42

Deftype should be able to implement interfaces

mseddon11:11:44

Yeah, that's what I suspected. It's not the end of the world of course.

mseddon11:11:13

huh, okay, I probably misunderstand deftype in some way, I will read up, thanks

didibus11:11:49

You just have to implement it inline

didibus11:11:56

> Each spec consists of a protocol or interface name followed by zero or more method bodies: protocol-or-interface-or-Object (methodName [args*] body)*

didibus11:11:18

You can't extend it later

mseddon11:11:34

I figured it out, IFn on clojurescript defines -invoke, but it is invoke in clojure. confusing 🙂

didibus11:11:09

Ah, good to know

mseddon11:11:43

I just needed a little boost of confidence to pursue it further apparently 😉

mseddon11:11:52

so now I have my vec3 type, I have declared a VecOpts protocol on it supporting v+. What I'd like is to redefine + to invoke v+ on things implementing VecOpts, and fall back to + in the default case- is there a good way of doing that?

mseddon11:11:35

I suppose I want a catch-all protocol implementation.

schmee11:11:23

there’s no good way to do that, because + is not implemented through protocols

schmee11:11:44

it fact, by default you can’t redefine anything in core due to direct linking

mseddon11:11:56

yeah, so I want to export a new + to handle this

mseddon11:11:25

somehow it must do ((if (isVecOptsSupported? x) v+ +) a b)

mseddon11:11:29

or something along those lines

schmee11:11:07

yup, (satisfies? VecOpts your-thing) does the check

mseddon11:11:15

aha! perfect, thanks!

mseddon12:11:40

well. works. it's 650 times slower than +.. but hey 😉

schmee12:11:55

there are some known performance issues with satisfies? unfortunately: https://dev.clojure.org/jira/browse/CLJ-1814

mseddon14:11:35

@schmee it seems that protocols themselves have quite an overhead. ~3.5x that of a standard call on my tests (in cljs)

minhnhat10bk14:11:46

value from reduce is a map

minhnhat10bk15:11:07

(reduce 
 (fn [rela key] (into rela {key ({:composer "J. S. Bach" :country "Germany"} key)}))
 {}
 '(:composer  :country))
{:composer "J. S. Bach", :country "Germany"} 

minhnhat10bk15:11:29

but when I want to push in to set it become seq of vector

minhnhat10bk15:11:55

(set (reduce 
  (fn [rela key] (assoc rela key ({:composer "J. S. Bach" :country "Germany"} key)))
  {}
  '(:composer  :country)))
#{[:composer "J. S. Bach"] [:country "Germany"]} 

minhnhat10bk15:11:39

how can I push this map in to set

manutter5115:11:19

@minhnhat10bk What do you want in the set? The values from the map?

minhnhat10bk15:11:35

I want a set of map

dpsutton15:11:46

(set {:a 1 :b }) => #{[:b 2] [:a 1]}

dpsutton15:11:08

(doc set)
-------------------------
clojure.core/set
([coll])
  Returns a set of the distinct elements of coll.
nil

minhnhat10bk15:11:26

but I want #{{:a 1 😛 2}}

dpsutton15:11:29

so set treats its input like a collection. which for a hashmap is a seq of key value pairs

dpsutton15:11:39

yes. i'm just showing how we can understand what is currently happening

bronsa15:11:45

@minhnhat10bk you want hash-set instead

dpsutton15:11:58

so in the future when something confusing happens you can see what is going on.

minhnhat10bk15:11:20

that what I need

dpsutton15:11:23

as @bronsa mentions, there are usually a "collection" function and an "elements" function

dpsutton15:11:48

set/hash-set vec/vector

dpsutton15:11:44

and one way to "browse" around for things would be (clojure.repl/apropos "set")

minhnhat10bk15:11:22

tks you too @dpsutton 😛

somedude31417:11:23

Any recommendation on how to connect and fetch data from databases? Is HugSQL the way to go?

schmee19:11:28

depends on what type of database you’re talking about, but if it’s SQL then my tip is https://github.com/clojure/java.jdbc combined with https://github.com/jkk/honeysql

anantpaatra17:11:02

I have two lists, '(:a :b :c) and '(:x :y :z), and I would like to create a third list that intertwines them into couples, like '(:a :x :b :y :c :z) so I can use it with map in a X Y coordinate style. I have seen this somewhere but I can't seem to find now.

petterik17:11:18

user=> (interleave '(:a :b :c) '(:x :y :z))
(:a :x :b :y :c :z)

dpsutton17:11:25

look into (doc interleave) and also maybe (map vector col1 col2). the former will sequence them, the latter will create tuples for you

anantpaatra17:11:56

Thanks! Perfect.

rodger.scott18:11:09

Hello all: quick question. I am trying to compute a sum of a range of numbers.

lennart.buit18:11:08

Have you looked at reduce ^^

rodger.scott18:11:34

I can throw compute-sum into (reduce + (compute-sum 10))

dpsutton18:11:51

explain your strategy and we can help you amend it or point you how to correct it

rodger.scott18:11:22

and it will reduce, however, if I try to reduce within the compute-sum function, I get a "IllegalArgumentException Don't know how to create ISeq from: java.lang.Long clojure.lang.RT.seqFrom (RT.java:542"

dpsutton18:11:51

how are you trying to reduce from within the compute sum

jaihindh.reddy18:11:14

for executes the body form once for each assignment in bindings

jaihindh.reddy18:11:45

(reduce + x) will be executed once for each value of x from 1 to n

jaihindh.reddy18:11:58

But what you want is to reduce the range itself

rodger.scott18:11:09

ahhh. okay, that totally makes sense

dpsutton18:11:16

reduce there is binding x to an element of (range 1 n) not the sequence

jaihindh.reddy18:11:20

so (reduce + (range 1 (+ 1 n)))

jaihindh.reddy18:11:06

Also, + is variadic so you can just apply it to a sequence of numbers. (apply + (range 1 (+ 1 n)))

rodger.scott18:11:15

Thank you so much!

rodger.scott18:11:26

You all really cleared that up for me!

jaihindh.reddy18:11:21

Run (for [x (range 3) y [:a :b :c]] [x y])

lennart.buit18:11:26

in general, the first argument to reduce should be a function that takes two parameters

jaihindh.reddy18:11:34

That should help you understand what for does

lennart.buit18:11:50

one is the “current” running total (I like to think of it as a memo), and the second is a value from your sequence

lennart.buit18:11:41

so you could write it like so: (reduce (fn [memo, val] (+ memo val)) (range 1 (+ n 1))). But since + can also take two arguments, just like my anonymous function, you can just put it there: (reduce + (range 1 (+ n 1))).

lennart.buit18:11:12

Finally, (+ n 1) can also be written as (inc n), with inc standing for increment.

rodger.scott18:11:44

Very nice, @lennart.buit.

lennart.buit18:11:24

Yeah, higher order functions take some mental gymnastics to get your head around, but they are oh so helpful!

tkjone20:11:52

I have a collection like this [[1 2] [3 5]] and I want to generate combinations like [[1 3] [2 5] [2 3] [2 5]]. The catch is that the original collection can be [[1 2] [3 5]] or it could contain more than two sub-collections. On the surface for seems like a good choice, except that the number of sub collection is dynamic. Is there a way to accomodate for this in the for?

schmee20:11:48

@tkjone check out https://github.com/clojure/math.combinatorics, especially cartesian-product

somedude31420:11:27

I have a map like this { ... :subname "//" (env :DB_HOST) ":" (env :DB_PORT) "/" (env :DB_NAME) ... } I am getting "Map literal must contain an even number of forms". I understand what is wrong but how can I fix it?

somedude31421:11:41

Make it a vector and use (join)?

dpsutton21:11:08

{:key "value"} has two forms {:key "value" "extra"} has three forms and can't form a map of key and value pairs

dpsutton21:11:40

presumbably you want the key :subname to point at (str "//" ... all those things you wrote)

somedude31421:11:16

@dpsutton: correct

dpsutton21:11:44

well that's the answer

dpsutton21:11:29

call (str on those things) to get a single string { ... :subname (str "//" (env :DB_HOST) ":" (env :DB_PORT) "/" (env :DB_NAME)) ... }

dpsutton21:11:41

now subname just maps to a single string

somedude31421:11:36

@dpsutton: perfect thank you

chiamakanwolisa21:11:24

How about (reduce + 0 (#(range %) 5))

dpsutton21:11:58

(#(f %) x) = (f x). so you don't need to invoke range on 5, you can just use the invocation (range 5)

chiamakanwolisa21:11:25

Oh nice. Thanks

chiamakanwolisa21:11:04

Started learning a few days ago

jesse.wertheim21:11:55

@somedude314 there's also the format function if a printf-style format string feels more readable. e.g. (format "//%s:%s/%s" (env :DB_HOST) (env :DB_PORT) (env :DB_NAME))

somedude31421:11:17

Ah cool, good to know.

somedude31422:11:02

I generated a project using lein new compojure hello-world and installed [environ "1.1.0"]. How do I load enviroment variables that would show if I do (require '[environ.core :refer [env]]) ... (env :database-url) from REPL? I created a profiles.clj with `{:dev {:env {:database-url "jdbc:<postgresql://localhost/dev%22|postgresql://localhost/dev">}} :test {:env {:database-url "jdbc:<postgresql://localhost/test%22|postgresql://localhost/test">}}}` and it's not working (returning nil). Can someone please tell me what I am missing?

eggsyntax23:11:37

You may want to edit the above, surrounding the multiline code snippet with triple-backticks instead of single-backticks: ie:

eggsyntax23:11:50

{:dev  {:env {:database-url "jdbc:}}
 :test {:env {:database-url "jdbc:}}}

eggsyntax23:11:00

You may be more likely to get a reply 🙂

eggsyntax23:11:38

(no idea about the answer, I’m afraid; I haven’t used environ)

somedude31422:11:33

Also, if I export a variable from shell. This works (System/getenv "DB_HOST") but this returns nil (use 'environ.core) (env :DB_HOST) on the same REPL session!

seancorfield22:11:04

Try (env :db-host) -- it picks up UPPERCASE variables as lowercase keywords and converts _ to - in them.

somedude31422:11:51

Thank you @seancorfield. That worked 🙂

seancorfield22:11:18

No idea about the profiles.clj question -- it doesn't work for me either, on the first attempt, but I don't use Leiningen so I may be doing something wrong as far as the instructions are concerned.

somedude31423:11:54

Ah okay, thanks for checking.

mario.cordova.86223:11:19

@somedude314 Keep in mind that if you had your REPL running and changed anything in your profiles.clj and project.clj you will have to restart your repl

mario.cordova.86223:11:54

Are you using Cursive's REPL?

somedude31423:11:44

@mario.cordova.862 yes, cursive. I am trying to restart the entire IDE now.

mario.cordova.86223:11:26

Is your REPL config set up correctly?

somedude31423:11:41

@mario.cordova.862 probably not - I opened the directory and right clicked on project.clj and clicked Run REPL for ... - however I also tried starting the REPL from the terminal.

mario.cordova.86223:11:44

In Cursives menu click on Run then look for Edit Configurations. Then in the upper left hand side there should be a +. Click on that and set up a local REPL.

mario.cordova.86223:11:21

Check Run nREPL with Leiningen and make sure you have the correct profiles set up if it applies.

somedude31423:11:09

@mario.cordova.862 just tried it with dev,user,system,base,provided,default same issue. This is showing the variable (database-url) but still not accessible from Clojure code:

> lein show-profiles dev
{:env {:database-url "jdbc:},
 :dependencies (),
 :jvm-opts nil,
 :eval-in nil}

mario.cordova.86223:11:53

Thats neat! I didnt know about show-profiles

mario.cordova.86223:11:09

And not sure about your problem then

somedude31423:11:32

Thanks for the help