Fork me on GitHub
#beginners
<
2018-02-10
>
steven kent00:02:39

if you want to use jdbc & postgresql in your app, how do you know which exact versions to use when you define the dependencies?

steven kent00:02:22

like this [org.clojure/java.jdbc "???"] [org.postgresql/postgresql "???"]

steven kent00:02:39

the version of postgres i have running locally is 9.6

noisesmith00:02:37

@stevenpkent you can see the latest version of org.postgresql/postgresql on maven central, and the latest version of org.clojure/java.jdbc on http://clojars.org, also if you already have deps you can look for newer versions by using lein ancient

seancorfield00:02:08

Contrib libraries are not on Clojars. They're on Maven.

steven kent00:02:18

@noisesmith thank you i will check it out

justinlee00:02:46

when people talk about “vars” in clojure/clojurescript, does that mean something more term-of-art than just a shortening of the word “variables”?

noisesmith00:02:03

in clojure there’s a class clojure.lang.Var, each namespace owns a mapping from symbols to Var instances

noisesmith00:02:10

a var is a mutable container holding a value

noisesmith00:02:29

bindings created directly in let binding blocks are not Var instances though

noisesmith00:02:32

#’foo returns the var holding the value you would get by using foo in the current scope (if it’s a var and not a local), and deref, @, or var-get will access the value it holds

noisesmith00:02:27

that’s all in jvm clojure - last I checked clojurescript namespaces were not reified in that way, but I forget the specifics, perhaps someone else can fill in

justinlee00:02:27

interesting. i went down this rabbit hole precisely because of the #'foo thing that I read about in a github issue on code reloading in clojurescript. for some reason remounting a component when you reference it as #'foo works when just foo does not. it doesn’t quite make sense to me why regular old foo would ever refer to something different than #'foo (assuming foo is not let bound anywhere)

noisesmith00:02:51

consider what happens here:

(ins)user=> (def foo :a)
#'user/foo
(ins)user=> (def bar foo)
#'user/bar
(ins)user=> (def baz #'foo)
#'user/baz
(ins)user=> bar
:a
(ins)user=> @baz
:a
(ins)user=> (def foo :b)
#'user/foo
[A(ins)user=> bar
:a
(ins)user=> @baz
:b

noisesmith00:02:21

when you capture the var, you see updates even when it was passed as an arg (rather than a compiled form, which automatically uses the var for you)

noisesmith00:02:38

if you capture the value and not the var, you don’t see updates if you don’t compile a form using the name

noisesmith00:02:42

@lee.justin.m so in cljs, consider what happens if you mount a component using something passed as an arg - if it’s just the value, you don’t see changes made by def unless you remount or otherwise recalculate, but if you used the var you would automatically use the new value without having to recreate anything

justinlee00:02:38

got it. yea that makes sense. that’s precisely the issue: namely that the top level component would not be re-rerendered though its children would be. thanks again!

noisesmith00:02:19

also, you can call a var - it will automatically deref itself and attempt to call the value it currently holds

noisesmith00:02:35

which is why you can eg. pass a var to a first class function

noisesmith00:02:20

(ins)user=> (@#'+ 1 1)
2
(ins)user=> (#'+ 1 1)
2
equivalent

noisesmith00:02:36

fun fact, refs do this too (but atoms and agents do not)

(ins)user=> (def plus (ref +))
#'user/plus
(ins)user=> (plus 2 2)
4

noisesmith00:02:15

(ins)user=> (def add (atom +))
#'user/add
(ins)user=> (add 2 2)
ClassCastException clojure.lang.Atom cannot be cast to clojure.lang.IFn  user/eval198 (NO_SOURCE_FILE:38)

justinlee00:02:45

i suppose that’s why you can just create [#'component] instead of [component] and it works, because somewhere the hiccup interpreter calls #'component as a function

justinlee00:02:56

huh i didn’t even know about ref

noisesmith00:02:18

refs predate atoms and agents, but are much less popular now

justinlee00:02:24

editor question: parinfer infers parens based on indentation. does the reverse mode exist? i.e., infer indentation of the remainder of the current form based on parens? often i add some code in the middle of the function and it requires me to manually reindent the remainder of the function to keep the structure the same

noisesmith00:02:52

I don’t know about a mode that does it automatically, but every editor should reindent current form with a simple keystroke

noisesmith00:02:18

(in my experience both emacs and vim do this with the barebones clojure mode packages)

justinlee00:02:28

i guess i could turn parinfer off and then ask for a reindent

noisesmith00:02:26

oh I misunderstood - you want this to happen while using parinfer… I’m surprised there isn’t some convenience to reindent children as you type

noisesmith00:02:46

(similar to insert vs. replace mode)

justinlee00:02:14

maybe atom’s parinfer is not as capable

noisesmith00:02:12

I’ve been skeptical of parinfer since a repeated problem involving one co-worker who never indented correctly and another who let parinfer re-parenthesize code on every file they opened

noisesmith00:02:35

of course there’s at least three problems here, even if parinfer is one of them 😄

justinlee01:02:34

zomg there IS a mode just like that. i just can’t read.

justinlee01:02:15

well i am the master of my own domain. i also go really used to prettier in javascript and now i can’t stand to do anything myself 🙂

noisesmith01:02:38

there’s cljfmt if you like auto-formatters, I swear by it

noisesmith01:02:27

clojure puzzle website idea: it gives you the code but without the parens (or in advanced problems, the parens in the wrong places) and you fix the code by inserting the right delimiters

justinlee01:02:48

worst. game. ever. 🙂

noisesmith01:02:16

unlike 4clojure it could be beaten by a trivial ai using trial and error

justinlee01:02:50

i think i tried cljfmt and terrible things happened. but it might have been when i was using a library with big weird macros (rum) and it didn’t get them

noisesmith01:02:23

that makes sense - you can tell it how to indent certain macros

feihong05:02:59

i generated a new re-frame project using the command lein new luminus demo +re-frame. it works fine when i run it as is, but when i ^:export the main function and try to run it directly in my html file using demo.core.main, it doesn’t work. the error i get is Error: _registerComponent(...): Target container is not a DOM element..

feihong05:02:53

it’s confusing why this doesn’t work since the examples in the re-frame repo all do it this way, i.e. https://github.com/Day8/re-frame/blob/master/examples/todomvc/resources/public/index.html

ahungry05:02:07

Is there an issue with cljs.ajax/GET and the handler not working well with a re-natal (reagent/re-frame) project ? I can't seem to set up a handler callback that does anything other than error out after the XmlHttpRequest state changes

ahungry06:02:41

Appears there is, using js/fetch worked

gklijs07:02:59

@feihong.hsu if you only want re-frame without a backend in your project, you need to use the re-frame template. Luminus always includes a clojure backend, which you don't seem to need.

roelof08:02:26

Why this error message :

Exception in thread "main" java.io.FileNotFoundException: Could not locate guestbook/db/migrations__init.class or guestbook/db/migrations.clj on classpath., compiling:(guestbook/test/db/core.clj:1:1)
	at clojure.lang.Compiler.load(Compiler.java:7526)
	at clojure.lang.RT.loadResourceScript(RT.java:379)
	at clojure.lang.RT.loadResourceScript(RT.java:370)
	at clojure.lang.RT.load(RT.java:460)
	at clojure.lang.RT.load(RT.java:426)
	at clojure.core$load$fn__6548.invoke(core.clj:6046)
	at clojure.core$load.invokeStatic(core.clj:6045)
	at clojure.core$load.doInvoke(core.clj:6029)
	at clojure.lang.RestFn.invoke(RestFn.java:408)
	at clojure.core$load_one.invokeStatic(core.clj:5848)
	at clojure.core$load_one.invoke(core.clj:5843)
	at clojure.core$load_lib$fn__6493.invoke(core.clj:5888)
	at clojure.core$load_lib.invokeStatic(core.clj:5887)
	at clojure.core$load_lib.doInvoke(core.clj:5868)
	at clojure.lang.RestFn.applyTo(RestFn.java:142)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$load_libs.invokeStatic(core.clj:5925)
	at clojure.core$load_libs.doInvoke(core.clj:5909)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$require.invokeStatic(core.clj:5947)
	at clojure.core$require.doInvoke(core.clj:5947)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$apply.invoke(core.clj:652) 

roelof08:02:54

on this code :

(ns guestbook.test.db.core
  (:require [guestbook.db.core :as db]
            [guestbook.db.migrations :as migrations]
            [clojure.test :refer :all]
            [clojure.java.jdbc :as jdbc]
            [guestbook.config :refer [env]]
            [mount.core :as mount]))

(use-fixtures
  :once
  (fn [f]
    (migrations/migrate ["migrate"])
    (f)))

(deftest test-messages
  (jdbc/with-db-transaction [t-conn db/conn]
                            (jdbc/db-set-rollback-only! t-conn)
                            (let [timestamp (java.util.Date.)]
                              (is (= 1 (db/save-message!
                                         {:name "Bob"
                                          :message "Hello World"
                                          :timestamp timestamp}
                                         {:connection t-conn})))
                              (is (=
                                    {:name "Bob"
                                     :message "Hello World"
                                     :timestamp timestamp}
                                    (-> (db/get-messages {} {:connection t-conn})
                                        first
                                        (select-keys [:name :message :timestamp])))))))
`

noisesmith08:02:42

if there's a migrations.clj file, it isn't in the right place relative to your classpath

roelof08:02:58

pff, i begin to think that clojure on Windows is a no-go

noisesmith08:02:50

I don't think this has anything to do with windows - where's your migrations.clj file?

roelof08:02:18

there is never a migrations.cli made when I search in my code : https://github.com/RoelofWobben/guestbook

noisesmith08:02:55

well that would explain the error

noisesmith08:02:44

you can't call code in a file that doesn't exist

roelof08:02:49

I think I give up on clojure. First problems with the template that files are missing and still running into that

Denis G10:02:17

Hey, I’m just have started play around with Clojure and transducers. I’ve implemented the “string compression” function, which is just a simple problem from hackerrank. I’ve decided to implement it with function chaining ->> and transducers and measure the speed. At the beginning I was thinking, that I should always favor transducers wherever possible, but now after watching Rich’s video on transducers I’m kinda confused. Code

(defn string-compression-1
  [s]
  (->> s
       (partition-by identity)
       (mapcat #(if (> (count %) 1)
               (list (first %) (count %))
               (list (first %))))
       (apply str)))

(def xform-str-compr
  (comp (partition-by identity)
        (mapcat #(if (> (count %) 1)
                (list (first %) (count %))
                (list (first %))))
        ))

(defn string-compression-2
  [s]
  (transduce xform-str-compr str s))
Question/Problem My functions are not completely the same, right? I’ve been watching a transducer video by Rich and he showed the abstraction of the map with the help of step function. So in my case, the step function is str, which means, that I will call str function on all my intermediate results, right? Whereby the example with ->> will call the function str only once which should be definitely faster, right? My point is, it wasn’t smart using str as a step function, right? What are your thoughts on this?

noisesmith14:02:49

you could try two things here: (fn ([] []) ([x y] (conj x y)) ([xs] (apply str xs))) or (fn ([] (StringBuilder.)) ([x y] (.append x y)) ([sb] (str sb)))

noisesmith14:02:29

(each of those could replace str, that is)

noisesmith14:02:34

oh scrolling down I see these points have been made

Vincent Cantin10:02:29

Why do people use variables named post-id instead of postId in Clojure? Where does this convention/habit come from? Do I need to do the same?

tstelzer10:02:24

@vincent.cantin when people learn new languages, they often like to carry over conventions -- i think its useful though to fully embrace existing conventions in the new language

tstelzer10:02:30

and just assume that they are "good"

tstelzer10:02:19

i started with php, then python, then javascript -- all three with different naming conventions -- every time i tried to carry over the conventions from the former

tstelzer10:02:12

in my experience its not worth it

tstelzer10:02:34

1) you have to assume that other coders follow the languages mainstream conventions, so when you ask them to read your code, you make it harder for them 2) reading other peoples code makes it harder for you 3) you end up in these absolutely pointless debates about naming conventions

rauh10:02:32

@denisgrebennicov Yes correct, your str function will be called over and over and a new JVM string is constructed on every step of the transducer

rauh10:02:06

Though, quote "should be def. faster" isn't true. Since your runtime isn't dominated by constructing the string

joelsanchez10:02:33

@vincent.cantin I think snakeCase sucks and in lisps I have the opportunity to use the much better kebab-case instead. You're free to disagree and write your code like that though!

rauh10:02:44

It's most likely because you're constructing data structures and intermediate lists/seqs in those steps. The transducers avoid that, hence they're likely faster in your case

rauh10:02:22

You could further speed up your example easily. Probably making it 2-3x faster

Denis G10:02:24

@rauh according to benchmark they are 2x as fast.

Denis G10:02:50

@rauh but of course, what is more important is the general understanding of what I am doing 😅 My assumption was that applying str on a collection is relatively fast, because of the String Builder appending it to a buffer and then returning a string (don’t know how it’s really done, but rather my intuition) Whereby creating a new String on my intermediate results just doesn’t sound right. Can I somehow make the code better, clearer?

rauh10:02:41

@denisgrebennicov You're absolutely correct. But creating the final str isn't your 80% time of the algorithm. It's all the other stuff it's doing. Clearer? Unlikely, but certainly faster is possible. You can do use a StringBuilder as reducing function and make it faster, but that's extra (coding) work

Denis G10:02:10

@rauh perfect! Thank you

rauh10:02:33

@denisgrebennicov It only matters ~25%:

(transduce xform-str-compr str-reducer (StringBuilder.) s)
(defn str-reducer
  ([] "")
  ([x] (str x))
  ([^StringBuilder sb x] (. sb (append (str x)))))

Denis G10:02:56

@rauh nice! Thank you!

rauh10:02:18

@denisgrebennicov Also you could avoid the mapcat:

(def xform-str-compr
  (comp (partition-by identity)
        (map #(if (> (count %) 1)
                (str (first %) (count %))
                (str (first %))))))

noisesmith15:02:17

FYI @rauh @denisgrebennicov I just benchmarked the StringBuilder vs. the str versions, and the naiive version was 7.728926 µs, transducing with str was 3.031921 µs, and transducing with StringBuilder was the worst: 111.232498 µs

rauh15:02:09

@noisesmith No way, type hinted?

noisesmith15:02:11

oh, I left out a type hinting - trying that again

noisesmith15:02:54

OK - type hinting brought the StringBuilder version down to 2.582512 µs

noisesmith15:02:07

which is much closer to expected, but still a very small improvement

noisesmith15:02:56

I benchmarked with a relatively short string though, you'd see bigger differences for longer strings I'd assume

noisesmith15:02:27

oh, and the solution switching mapcat/list to map/str is negligibly faster - the difference is statistical noise

nijk15:02:56

@here I’m looking for some advice on tooling setup for a Reagent/Reframe ClojureScript project I’m working on. I think it will be using boot, although that could change. I’d like to understand what this channel would recommend for basic front-end stuff like Sass/CSS Modules compilation, Sass/CSS linting, hot reloading and a component library?

Denis G16:02:14

@noisesmith @rauh thank you for your help! Learning something new everyday. PS. what have you been using for benchmarking? [Criterium?](https://github.com/hugoduncan/criterium)

noisesmith16:02:38

yes, that was all using criterium

noisesmith16:02:45

also, in real code, before even using criterium or speculating about speedups, you should usually 1) wait until you discover that things are not fast enough and 2) profile (eg. with yourkit or visualvm) to see what the actual speed bottlenecks are

Denis G16:02:13

of course, > premature optimization is the root of all evil

Denis G16:02:21

but this was done for learning purposes

noisesmith16:02:28

yes, I assumed that

noisesmith16:02:48

I just see a lot of time wasted on speculative optimizations in a program that doesn't even run yet, or in a part of the program that isn't slow

noisesmith16:02:56

(in my professional experience that is)

noisesmith16:02:37

like the time I wasted weeks on streaming html parsing to replace an eager scraper, when the actual problem was (doseq [x input] (future (scrape-html x))) which is just about guaranteed to be pathological and quite easy to fix

Denis G16:02:33

@noisesmith so if you were in my shoes, you wouldn’t be using transducers in this case? (I’m not talking about optimizing the code). Assuming that you were able to pick which implementation to choose, which would it be?

noisesmith16:02:04

often a transducer is a drop in 1 for 1 replacement for ->> surrounding collection operations

noisesmith16:02:35

in that case, it's a trivial choice - the arrow macros are severely overrated, the transducing is as easy to read and think about, and easier to decompose and abstract, so always do it

noisesmith16:02:29

even if performance were not an issue, (comp (map f) (filter g)) is empirically better than #(->> % (map f) (filter g))

noisesmith16:02:50

and it turns out, as a bonus, that also performs better

rauh16:02:22

Personally I always use transducers unless I require laziness (which is rare)

noisesmith16:02:27

and you can still use transducers inside a lazy process, via eg. sequence or eduction, though the translation isn't always worth it

Denis G16:02:28

@rauh but you can use transducers with sequences right? making it lazy. Or am I wrong?

noisesmith16:02:04

sequence has some drawbacks depending on what you are trying to do - eg. it chunks which isn't always what you want (though arguably if chunking is a problem you might not want laziness anyway)

noisesmith16:02:40

so yeah, I should correct what I said above, using transducers is a no-brainer if eagerness suffices, for laziness some other questions come into play

Denis G16:02:41

but what do you mean by chinking?

noisesmith16:02:42

this might help illustrate it

+user=> (let [s (sequence (comp (take 3) (map #(doto % print))) (range))] (doall s) (println) s)
012
(0 1 2)
+user=> (let [s (take 3 (sequence (comp (map #(doto % print))) (range)))] (doall s) (println) s)
01234567891011121314151617181920212223242526272829303132
(0 1 2)

noisesmith16:02:02

fixed the paste, it was very ugly before -- in the first case, the take transducer controls consumption so we don't process more than 3 items, in the second case the take is outside, so sequence chunks the processing into blocks - the minimum number of items processed is 32

noisesmith16:02:26

and if you asked for 34 items, it would process a total of ~64, etc.

noisesmith16:02:42

this is done because for most cases, processing 32 items at a time performs better than 1 at a time

opsb18:02:50

Hey all! I came across '(dracula dooku chocula) in https://github.com/functional-koans/clojure-koans, what exactly is it that's created there?

opsb18:02:01

My first guess was a list of strings but that's apparently not the case

andy.fingerhut18:02:38

a list of symbols

noisesmith18:02:09

a nice thing with clojure is that with a repl, it makes it easy to investigate this thing interactively

user=> (map type '(dracula dooku chocula))
(clojure.lang.Symbol clojure.lang.Symbol clojure.lang.Symbol)

opsb18:02:24

@andy.fingerhut of course! (list 'one 'two 'three) == '(one two three)

opsb18:02:54

facepalm, (= (list 'one 'two 'three) '(one two three)) even

andy.fingerhut18:02:36

turn that facepalm into a bright lightbulb shining over your head! 🙂

opsb18:02:12

this behaviour seems surprising to me

user=> (subvec [:peanut :butter :and :jelly] 1 3)
[:butter :and]
user=> (nth [:peanut :butter :and :jelly] 3)
:jelly

opsb18:02:44

if :jelly has an index of 3 then shouldn't the subvec call include it?

alexmiller18:02:34

the end index is exclusive, not inclusive

alexmiller18:02:45

so a range from 1 to 3 will include indexes 1 and 2

noisesmith18:02:46

user=> (doc subvec)                                                                                                                                            
-------------------------
clojure.core/subvec
([v start] [v start end])
  Returns a persistent vector of the items in vector from
  start (inclusive) to end (exclusive).  If end is not supplied,
  defaults to (count vector). This operation is O(1) and very fast, as
  the resulting vector shares structure with the original and no
  trimming is done.
nil

opsb18:02:41

fair enough, it does seem odd to be provding an index that's outside of the vector to retrieve it's contents but ¯\(ツ)

alexmiller19:02:12

this is really common across a lot of languages for getting array slices. one benefit is that the exclusive “end” == count. another is that you can take subsequent slices and use the exclusive end value as the next inclusive begin value, so turns out to be good for walking through a structure and taking slices.

opsb18:02:55

(`(subvec [:peanut :butter :and :jelly] 1 4)`)

noisesmith18:02:04

you don't need to provide an end index for that

user=> (subvec [:peanut :butter :and :jelly] 1)                                                                                                                
[:butter :and :jelly]

opsb18:02:38

right, that makes sense

opsb18:02:22

"Maps can be used as functions to do lookups"
  (= 1 ({:a 1 :b 2} :a))
this is interesting, does this idea of using a data structure as a function (or other non functions) pop up in other places?

noisesmith19:02:22

hash-maps, keywords, vectors, symbols and sets all act as functions doing lookup as if you called get

noisesmith19:02:59

it's also easy to create a custom data object using defrecord or deftype that acts like a function in any way you prefer

noisesmith19:02:44

user=> [(:a {:a :keyword})
        (:a {:b 1} :not-found)
        ('a {'a :symbol})
        ({"a" :hash-map} "a")
        ([:x :vector] 1)
        (#{:set :y} :set)]
[:keyword :not-found :symbol :hash-map :vector :set]

opsb19:02:31

thanks @noisesmith that's very helpful

opsb19:02:50

Is this koan written wrong or am I missing something? Given

"Higher-order functions take function arguments"
  (= 25 (___
          (fn [n] (* n n))))
then shouldn't the solution be
"Higher-order functions take function arguments"
  (= 25 (
          (fn [n] (* n n)) 5))
?

justinlee19:02:24

i think they want you to write a 2-argument function in the blank that applies the function to the argument

opsb19:02:42

right that makes much more sense, thanks!