Fork me on GitHub
#beginners
<
2021-06-28
>
Jakub Šťastný02:06:18

Is this how one deals with local variables? Nested let's like this? (defn run [command & rest] (let [start-time (current-unix-time)] (print (str " " (colour :green "λ ") (colour :grey command)) "") (flush) (let [result (zsh command rest)] (let [duration (- (current-unix-time) start-time)] (format-duration duration (fn [duration] (println "... took" (str (colour :green duration) "."))) result)))))

solf02:06:00

You can make multiple bindings in a single let. In your example above, you can put the two last let together:

(defn run [command & rest]
  (let [start-time (current-unix-time)]
    (print (str "  " (colour :green "λ ") (colour :grey command)) "")
    (flush)
    (let [result (zsh command rest)
          duration (- (current-unix-time) start-time)]
      (format-duration duration (fn [duration]
                                  (println  "... took" (str (colour :green duration) "."))) result))))

solf02:06:15

And you can technically also merge the first let, although it wouldn’t be pretty as you need to call the first print before the rest of the code. Here’s a way, but it’s not recommended, I use it when I need to temporarily print something for debugging

(defn run [command & rest]
  (let [start-time (current-unix-time)
        _ (do (print (str "  " (colour :green "λ ") (colour :grey command)) "")
              (flush))
        result (zsh command rest)
        duration (- (current-unix-time) start-time)]
    (format-duration duration (fn [duration]
                                (println  "... took" (str (colour :green duration) "."))) result)))

seancorfield03:06:29

And just in case you're wondering @jakub.stastny.pt_serv the _ is just a convention for "a local binding we don't care about". You'll see it a lot as a placeholder for unused arguments in functions.

Jakub Šťastný03:06:40

Ah OK, perfect. I'm not too off then. @U01QD68A64T how do you make block of code in Slack? I'm on sure if it's just the iPad app, I was trying to go for 3 quasi-quotes, but it's not really working.

seancorfield04:06:10

Triple backticks -- but you need to change a setting in Slack so that Enter doesn't submit the partial code block as I recall.

seancorfield04:06:52

Preferences > Advanced:

seancorfield04:06:17

(or press Shift Enter inside a triple backtick code block I guess)

pithyless12:06:33

@jakub.stastny.pt_serv sometimes when writing imperative code (lots of side-effects and/or conditional short-circuits) I may reach for https://github.com/Engelberg/better-cond. Otherwise, just defining the steps in a single let (even with _) seems fine to me. I'd probably just move the formatting function out of the last form:

(defn run
  [command & rest]
  (let [start-time (current-unix-time)
        _          (do (print (str "  " (colour :green "λ ") (colour :grey command)) "")
                       (flush))
        result     (zsh command rest)
        duration   (- (current-unix-time) start-time)
        formatter  (fn [duration]
                     (println  "... took" (str (colour :green duration) ".")))]
    (format-duration duration formatter result)))

pithyless13:06:00

it's hard to make side-effecty code look pretty in Clojure; which is great, because it makes you stop and think 10x if you shouldn't solve the problem in a different way (eg. better separating the functional and imperative shell parts and/or hiding some of the non-essential side-effect ugliness in a macro).

Jakub Šťastný14:06:21

Thanks @U05476190. I'll definitely refactor at some point. Currently it's a quick and dirty shell script, it's not a big project. I do hate side-effects not being tidily isolated in one place. What do you mean by "hiding some of the non-essential side-effect ugliness in a macro"? That sounds interesting. Would you have an example?

pithyless14:06:02

Your code looks similar to the usage of clojure.core/time or clojure.core/with-open . Obviously, it doesn't make sense here (since run is just one function), but if you were thinking of this kind of logic "wrapping" arbitrary forms, it looks like something you may want to hide via a macro.

popeye06:06:59

what is the use of var https://clojuredocs.org/clojure.core/var when it is used?

seancorfield06:06:14

I think it's very rarely used in that form. Most people use the reader tag #' instead.

seancorfield06:06:07

You'll see #' used in two main situations: 1. to introduce indirection so that code is more REPL-friendly -- see https://clojure.org/guides/repl/enhancing_your_repl_workflow#writing-repl-friendly-programs 2. to access private vars (often for testing, but sometimes for other purposes) since using #'some-ns/some-var gets around the private/public check.

👀 3
seancorfield06:06:44

See https://clojure.org/reference/reader#_dispatch for equivalence between #'some-var and (var some-var) (also https://clojure.org/guides/weird_characters for a more general guide to Clojure's "weird" bits of syntax)

3
dgb2308:06:03

Given that you don’t care about performance, have a single, reasonably sized collection: Is map/filter/reduce composition generally prefered over list comprehension?

lsenjov08:06:46

What’s the difference between map/filter/reduce and list comprehension?

dgb2308:06:33

The former (except reduce) returns lazy seqs or can be composed to transducers. The latter has special utilities to quasi iterate over multiple collections and to declare conditions.

dgb2308:06:32

But in the most simple, ad hoc case I don’t typically need any of that.

lsenjov08:06:11

If it’s simple enough to map/filter/reduce, compose it using that and a nice ->>

👍 3
lsenjov08:06:05

Tbh I think I’ve had to use a for as a list comprehension maybe twice for a couple particularly complicated things, but 99% of the time you’ll probably be fine without it

octahedrion08:06:21

use transducers

dgb2308:06:23

So we use list comprehension when we want to express something more involved for clarity?

lsenjov08:06:15

More when the simple things won’t work nicely without some seriously hard to write steps

lsenjov08:06:28

Default to map/filter/reduce

octahedrion08:06:31

for is for when you need to make products of things, otherwise use map

👍 6
dgb2308:06:04

That clicked for me

octahedrion08:06:52

for is the thing that confuses most people new to Clojure, who naturally start using it as they would in Python, but in Clojure it has a different meaning

Mattias09:06:41

I’m interested but confused - what is the definition of list comprehension here? 🙂

sheluchin11:06:57

I'm trying to sort out a DOM-related bug in my code. I can get the DOM element in my REPL after some browser interaction, and it looks something like #object[HTMLDivElement [object HTMLDivElement]] when I print it. I want to save this object to my tests so I can make assertions against it there, instead of having to interact with the browser. Could someone suggest how I could do that?

dgb2311:06:58

if you want to test against DOM elements then you might want to create them, put them into a specific state and interact with them: https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement

sheluchin11:06:26

I already have the element I'm interested in, so I don't think createElement is relevant. I can interact with the element in my REPL, but now I want to print it in my REPL in some format that I could copy/paste into my tests instead of a representation like this #object[HTMLDivElement [object HTMLDivElement]]. It seems like it should be a possible REPL-driven workflow.

jkxyz11:06:08

@alex.sheluchin If you print an HTML element with (js/console.log el) most browsers will log a HTML representation of the object. You can copy that and convert it to e.g. Hiccup

dgb2312:06:58

.outerHTML also provides a string representation

Simon12:06:11

if i don't have a project.cljs do i have to create one or can i use the deps.edn?

Simon12:06:50

I am trying to use cljsjs/mixpanel but this doesn't work:

Simon12:06:49

{:deps {org.clojure/clojure {:mvn/version "1.10.0"}
        org.clojure/clojurescript {:mvn/version "1.10.773"}
        reagent/reagent {:mvn/version "0.10.0"}
        cljsjs/react-chartjs-2 {:mvn/version "2.7.4-0"}}
 :dependencies [[cljsjs/mixpanel "2.22.4-1"]] ; <--- THIS
 :paths ["src" "resources"]
 :aliases {:fig {:extra-deps
                 {com.bhauman/rebel-readline-cljs {:mvn/version "0.1.4"}
                  com.bhauman/figwheel-main {:mvn/version "0.2.11"}}
                 :extra-paths ["target" "test"]}
           :build {:main-opts ["-m" "figwheel.main" "-b" "dev" "-r"]}
           :min   {:main-opts ["-m" "figwheel.main" "-O" "advanced" "-bo" "dev"]}
           :deploy   {:main-opts ["-m" "figwheel.main" "-O" "advanced" "-bo" "deploy"]}
           :test  {:main-opts ["-m" "figwheel.main" "-co" "test.cljs.edn" "-m" "move-nation.test-runner"]}}}

Lu13:06:04

You need to require mixpanel just like you’re doing for reagent or react charts .. you have them right there.

Simon14:06:06

okay, yeah I see. What is the difference between the project.cljs and the deps.edn files?

Simon14:06:00

I still get this error:

[Figwheel:WARNING] Compile Exception   src/movenation/analytics.cljs   line:3  column:5

  No such namespace: cljsjs/mixpanel, could not locate cljsjs_SLASH_mixpanel.cljs, cljsjs_SLASH_mixpanel.cljc, or JavaScript source providing "mixpanel" (Please check that namespaces with dashes use underscores in the ClojureScript file name) in file src/movenation/analytics.cljs

  1  (ns movenation.analytics
  2    (:require
  3     [cljsjs/mixpanel]
         ^---
  4     [cljsjs/firebase]))
  5  
  6  (defn init-analytics []
  7    (println "analytics initializing")

Simon14:06:53

I have updated deps.edn as mentioned by @UE35Y835W to

{:deps {org.clojure/clojure {:mvn/version "1.10.0"}
        org.clojure/clojurescript {:mvn/version "1.10.773"}
        reagent/reagent {:mvn/version "0.10.0"}
        cljsjs/firebase {:mvn/version "7.5.0-0"}
        cljsjs/mixpanel {:mvn/version "2.22.4-1"}}
...

Lu14:06:15

How are you running your project ?

Simon14:06:06

okay, yeah I see. What is the difference between the project.cljs and the deps.edn files?

Karo16:06:21

Hi team, is it possible to mock the value of a specific local variable instead of the whole function response as in case of "with-redefs". For example I need to mock only the value of "x" in this function.

(defn foo []
  (let [x 5
        y 3]
    (+ x y)))

West16:06:21

I'm not sure I understand what you're trying to do this for. Usually I would do (def x 5) and paste it into a repl. Or I would write it inline.

dpsutton16:06:29

if you make x an argument to the function you would achieve this. then just call foo with whatever value of x you like

West16:06:06

(defn foo [x]
  (let [y 3]
    (+ x y)))

Karo16:06:39

we use "with-redefs" in unit tests in order mock response of a function, in my case I need to mock only the value of one local variable of the function not the whole function response.

dpsutton16:06:59

the answer is you cannot. One way around this is to make that local an argument. Another way around that is to have that local be the result of a function call, and you can redef that function to inject a different value. There are many other ways around this, and those probably get into more structural changes to get out of this problem

Karo16:06:50

I see, thank you

Franco Gasperino17:06:50

do function docstrings support variable expansion? (def timeout 5000) (defn func (str "I'll wait " timeout "in this func...") [x] ...)

dpsutton17:06:43

No they don't

borkdude17:06:45

They do when use ^{:doc ...}

borkdude17:06:37

user=> (defn ^{:doc (str "foo" "bar")} dude [])
#'user/dude
user=> (doc dude)
-------------------------
user/dude
([])
  foobar

Fredrik17:06:44

Borkdude's solution uses the metadata reader macro ^{:key val}, which assigns this as metadata to the next value read. The reason Borkdude's solution works is that function docstrings are simply the value of the :doc key of the metadata of the var pointing to the function. In other words, this is how (doc fn-name) works under the hood. Using ^:{doc x} is equivalent to supplying a docstring when the (string? x) = true, but for any other types of values the defn form will give you an error. Note that ^:{doc x} lets you assign any value to the doc of the metadata, not just values of type String , but it's probably best to stick to strings.

Franco Gasperino18:06:32

thanks, was a passing curiosity while creating one

Santiago18:06:24

if I have “global variables” such as env vars or an atom used as shared state, is there some best-practice or nomenclature on how to make their origin visible inside functions? Should functions “always” get such variables as arguments instead of a direct reference inside their definition?

ghadi18:06:15

pass arguments, don't reach "outward" for global state from inside a function

ghadi18:06:30

in general

Jakub Šťastný19:06:16

@borkdude wow that's cool! Thanks @frwdrik for explaining in detail.

Jakub Šťastný19:06:04

I didn't know CJ has reader macros though? I searched for it some time ago, but didn't get much. Are there any good resources on the matter?

borkdude19:06:26

that's the specific part about metadata, the rest of the document should explain what other special dispatch characters there are.

Jakub Šťastný19:06:32

But can I define reader macros?

borkdude19:06:55

you can define data readers, which is not the same, but offers similar benefits

borkdude19:06:54

e.g. you could define a data reader foo/bar so you can write #foo/bar{:a 1} in your programs and your reader function will transform the literal value into something else

👍 3
Jakub Šťastný19:06:18

Data readers. Never heard of these. OK, will read up on it :thumbsup:

borkdude19:06:32

that is described in the same document I linked

Fredrik20:06:27

Arbitrary metadata in a function definition can also be supplied as a map before the arglist. So you could do (defn foo {:doc (str "bar" baz), :more-meta 123} []) , see https://clojuredocs.org/clojure.core/defn

greg22:06:16

Hi, I'm trying to figure out some idiomatic way of transforming one data structure to another. This:

{:AAA {:prop1 ["A"] :prop2 ["a"]}
 :BBB {:prop1 ["B"] :prop2 ["b"]}}
Into this:
{:prop1 [:AAA ["A"]
         :BBB ["B"]]
 :prop2 [:AAA ["a"]
         :BBB ["b"]]}
Two question to you gents: 1. Is the custom reduce the only option here, or there is some existing building blocks, methods I could combine and use here? 2. Who do you usually tackle this problems when you haven't done such a type of transformation yet? I mean, do you go and implement it or you start checking first the Clojure cheatsheet and funs in libs such as medley and plumbing?

pithyless08:06:05

@U023TQF5FM3 Aside from helper functions everyone ends up writing (ala medley and plumbing), there are at least 2 libraries that define more declarative languages for describing data transformations: 1. https://github.com/noprompt/meander 2. https://github.com/redplanetlabs/specter I don't recommend pulling them into a codebase where a reduce and some helper functions will suffice (especially since they have a learning curve and add friction for future code readers), but it's good to know these kind of libraries do exist when you're doing lots of data transformations and maybe you're starting to lose the forest for the trees. (I'm also a big fan of transducers and https://github.com/cgrand/xforms when it comes to writing more composable building blocks)

👍 2
greg11:06:09

These are great resources. Thanks a lot!

greg17:06:49

I end up with a reduce:

(->> {:AAA {:prop1 ["A" "B" "C" "D"] :prop2 [1 2]}
      :BBB {:prop1 ["X" "Y"]         :prop2 [7 8 9]}}
     (reduce (fn [{:keys [prop1 prop2]} [k v]]
               {:prop1 (assoc prop1 k (:prop1 v))
                :prop2 (assoc prop2 k (:prop2 v))}) {}))

=> {:prop1 {:AAA ["A" "B" "C" "D"], 
            :BBB ["X" "Y"]}, 
    :prop2 {:AAA [1 2], 
            :BBB [7 8 9]}}
@U05476190 I checked briefly the libraries you mentioned and in my case, meander sounds like something really nice. I watched one video and briefly checked the docs. I'm working on a side project that is heavy in data processing. So I juggle data and the idea behind meander sounds very appealing to me. Still, I tried to use it in my case and it wasn't obvious how to make such a transformation. On one hand it makes sense, but on the other it is yet another library, new notation, heavy in macros, therethrough not looks good using it from Cursive. What is your thoughts on meander? Is it worth to invest in it? Do you use it often?

pithyless18:06:54

I think meander is a power-tool. Sometimes you just need a chisel, but sometimes you may find a use for a laser-guided-space-gun. If my current project was doing a lot more data juggling between formats, I would probably consider investing in it heavily (and doing the work of getting my team onboard). As it stands, I use it only occasionally but I know it's there - waiting to pounce. If I were doing data-intensive side-projects without needing to get others onboard, I may actually be using it more often now. I definitely see more use-cases for meander than the search capability of specter; but, again, the people behind specter are building compilers with it... so they're obviously getting their money's worth. I think @U0BBFDED7 may have more insight on whether to invest in meander; and also be sure to check out the #meander channel! There is an upcoming zeta branch that is also looking promising.

ribelo19:06:20

Meander is not difficult at all when it comes to basic stuff. I would say it is simpler than anything else. Often the code accurately reflects the structure of the data we are working on.

ribelo19:06:44

the difficulty starts when we want recursion