Fork me on GitHub

does anyone have an example of using etaoin? I've tried lein new app etaoinexample and then adding the dependency.

:dependencies [[org.clojure/clojure "1.10.1"]
                 [etaoin "0.4.1"]]
but I get this error:
Unable to resolve symbol: firefox in this context
where I try to call this in core.clj: (def driver (firefox))


In the instructions that contain that expression, several lines above is this one: (use 'etaoin.api). Did you perhaps skip that?


It might be preferable to create a namespace where you write your code, e.g. (ns my.project.namespace (:require [etaoin.api :as api])) and then you can use api/firefox instead of just firefox


this helps me, thanks. It's one of the hardest parts of clojure to me: trying to figure out the ceremony of :require I'm not sure where I'm missing the explanation in the docs


I appreciate your help.

Aviv Kotek10:11:40

with lein, there is :dev and :test profiles, it's not to use :test profile, should the :dev profile be used for test purposes? i.e include test-lib dep, etc? then "lein with-profiles dev test"? I find it more intuitive just to run "lein test"(which merges :test profile if declared), what are the differences between :dev and :test profiles? which should I pick?


lein test should already merge in the dev profile IIRC, and with-profile foo is almost always an error (it replaces the profile for the task), you usually want with-profile +foo (it merges new data to the profile for the task)

🙏 3

Each profile should be dedicated to some task, and you might want a supplimental profile to enable integration as opposed to just unit tests for example. You shouldn't need to manipulate profiles for normal tasks.

Aviv Kotek08:11:23

there is one disadvantage of "test" not able to run in REPL (running tests), so in conclusion- it's common to use the :test profile for tests?


the :test profile is always used for the :test task, if you want to run tests in your repl, you might want to add with-profile +test to your repl startup

Yosevu Kilonzo12:11:54

Which way of writing conditions is preferable in Clojure, in general? Should I rely on implicit, imperative order or explicit, declarative order?

;; Implicit, imperative order dependency. The most specific condition must be first.
(defn deaf-grandma [s]
    (and (question? s) (upper-case? s)) "Calm down, I know what I'm doing!" ; Must be first!
    (question? s) "Sure."
    (upper-case? s) "Whoa, chill out!"
    (str/blank? s) "Fine. Be that way!"
    :else "Whatever.")) 

;; Explicit, declarative order.  Conditions can be in any order.
(defn deaf-grandma [s]
    (and (question? s) (not (upper-case? s))) "Sure."
    (and (upper-case? s) (not (question? s))) "Whoa, chill out!"
    (and (question? s) (upper-case? s)) "Calm down, I know what I'm doing!"
    (str/blank? s) "Fine. Be that way!"
    :else "Whatever."))

Yosevu Kilonzo12:11:33

My concern with order is inspired by this passage from Out of the Tar Pit by Ben Mosely: > “The difficulty is that when control is an implicit part of the language (as it almost always is), then every single piece of program must be understood in that context — even when (as is often the case) the programmer may wish to say nothing about this. When a programmer is forced (through use of a language with implicit control flow) to specify the control, he or she is being forced to specify an aspect of how the system should work rather than simply what is desired.”

Alex Miller (Clojure team)14:11:48

cond short circuits and I think most people explicitly rely on that to simplify cond branches, so that would be the former. In this case the scope of the order is encapsulated in 5 lines so it's not a load


I had a recent case where I used the explicit branching method (not in Clojure but the principle applies), because the code in question was rather complex and important for the overall consistency of the application. I also annotated all the branches with sufficient comments to understand them in isolation. My takeaway: It depends.

Yosevu Kilonzo12:11:11

Makes sense about relying on short circuit for simplicity especially with a few simple conditions Alex.

Yosevu Kilonzo12:11:59

Thanks for the counter example dgb23. I agree it shouldn’t be black and white. So you made the conscious choice not to rely on the order of the conditions in your case because of their complexity AND the nature of the problem/app.

👍 3

What are my options when using NPM packages with Figwheel? I don’t have shadow-cljs as an option anymore, and figwheel’s NPM guide constantly leads to compilation errors for me.


I only need to use a single NPM package, is there a manual / hacky workaround with externs and the Closure Compiler?


The package is react-spring, I can’t find it on CLJSJS.


@seancorfield i look forward to your REPL presentation next month


Thanks @st3fan -- if there's anything specific you'd like to see/hear about, LMK via DM and I'll see whether I can incorporate it. I want it to be an interactive talk/demo so folks can get as much out of it as possible!

👍 12

Just show us the magic 🙂

👍 9

Hi! I’m not sure if this is an appropriate question for the channel, but was wondering if y’all had advice on “code smells”, or moving toward more idiomatic clojure. I have a couple functions that map a map of a map, and the repetition is making me feel like i’m doing something incorrect…but am not quite sure how to best phrase the incorrectness beyond “doesn’t feel clojurey”.


For example, a fn “text->env-map” which takes text like CLOJURE=cool\nREPL=awesome\n and converts it to ({"CLOJURE" "cool"}{"REPL" "awesome"})


I have it written, and working, as so:

(defn text->env-map
  (map #(conj {} %)
        (map #(clojure.string/split % #"=")
             (clojure.string/split-lines text))))

Lennart Buit21:11:47

You can use a transducer here, or threading


for all f,g (map f (map g x)) can be replaced with (map (comp f g) x)


But i am still pretty new to clojure, and am not sure if i’m taking too blunt of an approach.


it’s just feeling increasingly unreadable.


so in your case (comp #(conj {} %) #(string/split % #"=")) or just #(conj {} (string/split % #"="))


Ah! I had not used comp before, that’s really nice!


Check out the threading macros for making deeply nested chains of calls like that more readable, too:


(->> x (map f) (map g)) is worse than comp IMHO


(though I see it more often)


Good point. I’ve used threading macros, but maps trip me up because of the order of the variable being put in.


(e.g. i have a set of functions where (-> ) works great, as the second position is perfect, but then i want to pass that to a map, and it now needs to be the last position)


Agree in this specific case, comp is a better way to go


I still prefer the threaded version. for me, it's easier to read and extend


Thank you!


There is also ->> to put the thread in the last position


and some-> to short circuit in case a nil happens

👍 3
Lennart Buit21:11:20

Yeah, there are two ‘styles’ of threading operators, thread-first -> and thread-last ->>, the latter works great with collections sequences 🙂


Being nit-picky: ->> works great with sequence functions. Functions that take collections tend to have them as the first argument (such as conj, nth).

Lennart Buit21:11:08

Haha yeah, correct lingo is important here, thanks for the correction 🙂!


don't map ,`filter`, etc. take both collections and sequences? don't conj, nth,etc. also take both collections and sequences?


@U7RJTCH6J map, filter etc call seq on their argument and return sequences -- and are considered "sequence functions"; conj preserves the type of its first argument (as do several collection functions). Per


so is mapv a sequence function or a collection function?


Technically still a sequence function. It can take multiple sequence arguments and in that case calls map on them, and then pours the result into a [].

👍 3

The single sequence argument version is an optimization that reduce's it into a (transient) [] via conj!


The distinction isn't 100% consistent but it's a useful rule of thumb in most cases.


:thumbsup: , makes sense


It's also a useful guideline when thinking about your own functions (one I often used to violate, and then curse my younger self for when doing maintenance on said code some months or years later! 🙂 ).


> conj preserves the type of its first argument with small caveats. I believe maps might change classes, but not the "abstract" type


it always amazing how human made categorizations become fuzzy very quickly as soon as you leave mathematics (or maybe even while still in mathematics, IANAM)


"type" is such a fuzzy concept 🙂


depends on what the definition of is is


(I am a mathematician by training and did a bunch of category theory etc -- but I mostly have to "forget" that when programming, except occasionally)

😁 3

I like comp more for named functions: (comp foo bar) reads nice and clean. With literal functions, I may go with -> and multiple maps for readability. Puts each unit of work on different lines.


I suppose you can do that with comp, too


or just do that inside the function literal


yeah, or that


#(-&gt; % (string/split #"=") (-&gt;&gt; (conj {})))


OK never mind - the nesting works but it's dumb

😄 3

comp plus named functions seems to perhaps lend itself more nicely to spec and testing too? Or at least it’s encouraging a number of small, easy to reason about functions.


It really depends on what exactly you’re doing.. judgement call which is better in a particular case


Being nit-picky: ->> works great with sequence functions. Functions that take collections tend to have them as the first argument (such as conj, nth).


Over-testing is as bad as under-testing IMO


The natural question is then what’s the right balance? Well.. hard to answer in a general way.


Adding ever more tests is diminishing returns, after some point. Each unit of resources to add a new test prevents fewer bugs. Bugs are, by definition, problems nobody thought of. Problems for which there was no test case. There are plenty of code bases with a million micro-unit tests for every (+ 2 2) level function, which still have large bugs anyway. It depends a lot on how much damage happens if there’s a failure. If you are launching a Space Shuttle or doing life-critical code like building a pacemaker, you should probably go overboard on testing every possible code path. Most commercial software has much less harsh failure modes, though.


hahaha, yah my scenario is a bit less mission critical.