Fork me on GitHub
#beginners
<
2019-10-21
>
Sam Ferrell01:10:24

Is this pattern possible? I have many several namespaces under foo.bar and would like to avoid requiring them each.

andy.fingerhut01:10:10

require has no way that I am aware of to require multiple namespaces in a hierarchy, without listing each of their names explicitly.

andy.fingerhut01:10:26

There may be editor or other script-type assistance that people have created to generate such require statements for you, but I have not used them.

andy.fingerhut01:10:47

I recall a kind of 'abbreviated' form of require that I've only seen used rarely that may be helpful, if I can remember an example of it to show

hiredman01:10:51

Namespaces are not hierarchical like that, there is no "under" relationship between them

Sam Ferrell01:10:13

isn't foo.bar.baz/add available from foo.core without requiring it directly? or am i mistaken

Sam Ferrell01:10:07

i don't think i'm defining a hierarchy, only trying to imitate a pattern in Elixir which lets you alias namespaces like that

andy.fingerhut01:10:53

You still need to mention each 'leaf' namespace at least once, but the common prefix only needs to be mentioned once.

hiredman01:10:54

If a namespace is loaded, you can refer to names in it using fully qualified names

hiredman01:10:33

But in general it is bad practice to refer to names defined in code you don't have a explicit dependency on

Sam Ferrell01:10:41

maybe my approach of code distribution is flawed... would you say more numerous but smaller namespaces are discouraged in favor of fewer but larger ones?

andy.fingerhut01:10:45

That form of require is not very commonly used, so may confuse some people, I expect. Also not sure whether it works in ClojureScript at all (I have half a memory of some warning about this)

hiredman02:10:34

Namespace size to taste, I think more namespaces are better then fewer, but I don't think there is a good hard and fast rule

andy.fingerhut02:10:08

Are we talking on the order of 10, 100, or 1000? I don't think anyone bats an eye at a namespace that require's 10 others.

Sam Ferrell02:10:38

probably 10, so maybe just overthinking it 🙂

didibus02:10:23

Yes, this is very different from Elixir, which uses Erlang, where modules are more akin to classes

didibus02:10:48

The namespaces in Clojure shouldn't really group things in terms of encapsulation

didibus02:10:58

They're more like folders for your music library

didibus02:10:09

Just a way for you to find what you need where it makes sense

didibus02:10:55

They don't really serve any other purpose. You could write your whole app in a single namespace if you fancied that

didibus02:10:45

So I would not recommend you imitate this pattern at all

didibus02:10:24

Just put everything that would be under that hierarchy at the "top"

seancorfield02:10:02

My suggestion would be: if you're building a library, organize it so users could do "mainline" stuff with just one required namespace and anything that is extra/optional can be in separate namespaces that they can require if they want. For example, honeysql.core is all you need for basic stuff, but there are also optional honeysql.helpers if you want the functional DSL and honeysql.types if you want reader literals or "special" forms (designating inline SQL, raw SQL, specific function calls, etc). In next.jdbc, the primary API is in that top-level namespace, but there are optional "friendly" SQL functions in next.jdbc.sql and ways to extend handling of result sets in next.jdbc.result-set and ways to extend handling of parameters and do batch execution of prepared statements in next.jdbc.prepare and so on. @samcferrell

seancorfield02:10:06

The analogy from @didibus is a good one: folders for your music, with subfolders just indicating additional categories of music (code) within the main category.

Sam Ferrell02:10:54

thanks @seancorfield. I should specify I'm building an application which has, potentially, a wide variety of domain objects. I've got one big namespace right now but playing around with different namespace layouts to help organize those domains

Sam Ferrell02:10:38

generally I'm trying to keep things flat, maybe at most 2 directories from the root, ex. src/foo/bar/baz/file.cljc

Sam Ferrell02:10:01

but with the majority of code in just the first directory down

seancorfield02:10:47

Organizing an app by "domain concept" is reasonable. At work, our root ns segment is usually ws or worldsingles and we go down two more levels in several places, e.g., ws/admin/handlers/<domain>.clj We only have one namespace nested deeper than that. We have 300 namespaces. A third of them are at that root+2 level, half of them are root+1, and the missing 1/6th (approx) are in the root (and those do not have the ws or worldsingles prefix -- but probably should).

seancorfield02:10:48

Actually, I'll take a little bit of that last assertion back... we have quite a few worldsingles/<domain>.clj namespaces in that 1/6th. Only eight have no World Singles style prefix (but they should).

Sam Ferrell02:10:39

that's really helpful to hear, most of the clojure code i see are libraries and not applications so its difficult to get a read on what people are doing in that space

didibus02:10:47

For me, we have more of a micro-service style set of systems. So we rarely have more than 5 or 10 namespace per service.

seancorfield02:10:48

To put things in context, those 300 namespaces are from src folders so they're our production code (well, with a few very small build-related things included) and they make up about 70,000 lines of code. We have about 20,000 lines of test code in just over 300 files (because we have at least one test namespace for every single source namespace).

didibus02:10:10

And they tend to all be the same.. You have a global namespace that hosts some configuration, and a few namespaces for the components handling the logic, and a core namespace which hosts the APIs, and a spec namespace for the domain objects that are persisted/exchanged to DB/other services

didibus02:10:29

I also need to ask, what do you mean by domain objects? It triggers my, I'm worried you're doing OOP with namespaces alert

didibus02:10:49

Do you mean domain components? Or domain state?

didibus02:10:17

Like is it something more like DataAccess.clj or is it more like Car.clj Car.Mercedes.clj Car.Bmw.clj , etc.

seancorfield02:10:25

(note: that's why I said "domain concepts" 🙂 )

didibus02:10:26

You want your namespaces to be more like: Auth.clj, Search.clj, Login.clj, SQL.clj, Persistence.clj, API.clj, etc.

Sam Ferrell02:10:28

something like users, orgs, forms (like a form you submit to a government agency), etc

seancorfield02:10:40

Our "domain objects" are just hash maps.

seancorfield02:10:41

(gotta go pick up the wife from the airport -- this is an interesting discussion and I'll check back in later!)

didibus02:10:31

Hum... hard to say since I'm not fully aware of the domain. But I think a good trick is to start with just one giant namespace, and than as you start to see clusters of functionality, you can split them up into their own.

👍 4
didibus03:10:17

Cause its much more about someone else coming to look at the code, to be able to get a quick grasp of, what are the different logical pieces. So you know when you give someone a break down of an app? You generally draw big components and some arrows between them, and you rarely have hundreds, normally you draw 5 to 20 of them per app.

didibus03:10:45

Basically each of these are a good candidate for a namespace

didibus03:10:49

So like, if it makes sense to put all the forms and the form logic together you can do that. But sometimes, it makes more sense to break things not say by form, but by program instead, so you could have the GovernmentXHandlingComponent, and GovernmentYHandlingComponent. Or it might make more sense to break things in terms of functionality, like the FormSubmitter component, the FormDataAggregator component, etc

didibus03:10:42

And like Sean mentioned, your domain objects, or what you could call your data schemas, those don't really need to exist anywhere in Clojure 😛. Since you don't have to specify a schema for the data the app handles. Though, it can be good practice to do so using clojure.spec, in which case you can put all of the schemas into one big namespace which defines to full set of data and all their shapes and relations for the app in one place.

didibus03:10:25

So if your users, orgs and forms, are really just describing data, they might not be necessary. But if they are more like UserHandling, OrgHandling, FormHandling components, that seems fine to me

didibus03:10:32

Anyways, there's no real right or wrong answer. At the end of the day, it is whatever works best for your app. The important thing is to realize the namespaces only solve three problems: (1) They prevent name clash when importing code from somewhere else. (2) They allow you to share a set of code together easily for reuse. (3) They can help split up a code base, in case your file gets way too big to navigate.

didibus03:10:57

What they don't do is encapsulate and manage state and the life-cycle of data. Using them as such is what I would consider wrong.

Kazuki Yokoyama03:10:06

Hello, everyone. Since Clojure doesn't allow assignments as we know from other languages, what would be the most idiomatic workaround for this seemingly limitation? (For instance, "capturing" some value for later use in the same function)

didibus03:10:34

let works pretty much the same way as assignment does on other languages, except for two differences. (1) It is scoped to itself, so its not local to the function, but to the surrounding parenthesis of the let itself. Leave the block of the let and the variables defined by it are gone.

didibus03:10:08

(2) You can not mutate the bound variables that let has assigned. You can only shadow them, but not change what they point too.

didibus03:10:16

(let [x 10]
  ;; Prints 10
  (println x)
  (let [x 100]
    ;; Prints 100, but not because it mutated x, but this is another x that shadows the previous one
    (println x))
  ;; Prints 10 again, since the x of the outer let was never mutated, it is immutable to whatever it was assigned too originally.
  (println x))

didibus03:10:30

@yokoyama.km Hope that answers your question

Kazuki Yokoyama04:10:52

Thank you, @didibus for replying. Actually I'm think in a scenario like the following (in pseudo code):

op1()
op2()
x = op3()
op4()
op5()
...
opN(x)

didibus04:10:51

To add to that, this is generally the difference between what we call binding and what we call assignment. Assignment assigns to an existing variable defined in the nearest scope, thus implies mutability. Binding means creating a new variable in the current scope and assigning it a value at creation time. Generally, once bound, the value can not be assigned to later. Though in Clojure in some cases it can be re-bound... which gets a bit more confusing, and you can mostly ignore for now.

didibus04:10:01

(op1)
(op2)
(let [x (op3)]
  (op4)
  (op5)
  ...
  (opN x))

Kazuki Yokoyama04:10:56

Right, it certainly does the job. But it leads me to another question, is it ok to have all this code (from (op4) up to (opN-1)) in the let scope just because of x?

didibus04:10:18

Well, it is okay, if you absolutely need to have all this imperative side-effectful logic, which you do need too sometimes. But in a lot of cases, you don't need too, and can change things to be less imperative.

didibus04:10:52

To understand that, we have to talk about what each opN function do.

Kazuki Yokoyama04:10:26

I see. Maybe it can be a symptom of a too imperative way of thinking, right?

didibus04:10:44

Like, how come op3 and op5 can not be called before op3 ? It can only be because they are secretly modifyin state outside the function, which makes their order matter, and a source of possible bug as well.

didibus04:10:52

(op4)
(op5)
(opN (op3))
If you can't rewrite as above, its because you are dealing with state outside the function.

didibus04:10:07

State which you are mutating at every step

Kazuki Yokoyama04:10:23

Yes, rewriting the function may be the right solution in some cases

didibus04:10:48

So once you get more into the functional mindset, and start writing mostly pure, side-effect free functions. You won't have this problem as much, because you'll almost always be able to do what I just did.

didibus04:10:58

Which is why code will start to look like pipelines, or just composed functions nested one in another

(a (b (c)))

;; or

(-> (c) (b) (a))

didibus04:10:55

But, sometimes you need side effect. Mostly when dealing with I/O, like writing and reading to a file or the database, taking user input, etc.

didibus04:10:07

In those cases, it is fine to do what I did with the let

didibus04:10:27

For example

didibus04:10:28

(println "Hello!")
(println "What is your name?")
(let [x (read-line)]
  (println "Don't you worry though...")
  (println "I will keep your name a secret.")
  (println x))

Kazuki Yokoyama04:10:57

And would it be OK to have something like

(let [x (some-large-piece-of-code)]
 ...)
?

didibus04:10:16

Ya off course

didibus04:10:49

Especially when starting, people use let a lot. And even later, let is used I'd say in almost 50% of all functions at least once.

didibus04:10:12

But, functions that take no arguments in Clojure should be avoided as much as possible. Again, they are an indicator of side effect. And unless its a scenario like I described, where side effect is unavoidable, better avoid it.

didibus04:10:39

So it would be more like:

(let [x (some-large-piece-of-code y)]
  (* 2 x))
And in scenarios like this, the let is really useful just to give a name to the result of (some-large-piece-of-code).

didibus04:10:09

Because you could have wrote:

(* 2 (some-large-piece-of-code y))
as well

Kazuki Yokoyama04:10:27

I have another question that is killing me: what is the most idiomatic way to do error handling? My first experience with FP was with Elixir. There, is idiomatic to return tuples, where its first element is an Elixir's atom (Clojure's keyword) with value, e.g., :ok or :error

didibus04:10:54

The idiomatic way is to throw an (ex-info)

didibus04:10:31

Look for my answers didibus

didibus04:10:46

They explain how to do some of Elixirs error handling in Clojure

Kazuki Yokoyama04:10:33

Nice! Really thanks, @didibus!

didibus04:10:31

The exception handling in Clojure is not classic FP, it is done the way Java, C#, etc. do it. That's because Clojure is a hosted language, and adopts the host's exception mechanism. So it just uses Java style exceptions.

didibus04:10:05

You don't create new Exception types in Clojure though. Instead you use (ex-info) and (ex-data) functions.

didibus04:10:33

That way, there's one coherent way to do exceptions. If it had added its own, you would still have needed to deal with Java's exceptions, and then you just would need to learn and use two different ways to deal with errors all the time, which would have been annoying.

didibus04:10:21

Some people go out of their way and use a library that allows for more FP like error handling, but this is not idiomatic, and causes the problem I just described. So code like that is not portable, and other code doesn't know how to handle its errors.

didibus04:10:33

Since its not standard.

didibus04:10:55

Okay, good luck, and welcome. Going to bed.

Kazuki Yokoyama04:10:42

See you! And thanks again!

andy.fingerhut05:10:09

It is certainly reasonable for functions within your own code to return a map with a key like :error with values like true or false, or some keyword or nil, but there is no standard practice there that I am aware of across Clojure projects. If you do that, you would need a way to propagate those up multiple levels of calls if the handling of the error was several levels up the call stack.

Kazuki Yokoyama11:10:25

As a beginner, I must admit I'm very surprised with the lack of standard for error handling in Clojure. I'll stick with the exception approach for a while.

andy.fingerhut13:10:08

As someone else mentioned, exceptions that contains data in the form of maps are fairly standard, both on Clojure and ClojureScript. I just wanted to mention that you can use non-exception means if you wish.

👍 4
FiVo10:10:26

Is there a way to create an anonymous recursive function with memoize?

FiVo10:10:47

So for example:

(def levenstein
  (memoize (fn [x y]
             (cond (empty? x) (count y)
                   (empty? y) (count x)
                   :else (min
                          (+ (if (= (.substring x 0 1) (.substring y 0 1)) 0 1) (levenstein (.substring x 1) (.substring y 1))) 
                          (inc (levenstein x (.substring y 1)))
                          (inc (levenstein (.substring x 1) y)))))))

FiVo10:10:57

Without the def

danielneal11:10:11

((memoize (fn levenstein [x y]
              (cond (empty? x) (count y)
                    (empty? y) (count x)
                    :else (min
                            (+ (if (= (.substring x 0 1) (.substring y 0 1)) 0 1) (levenstein (.substring x 1) (.substring y 1))) 
                            (inc (levenstein x (.substring y 1)))
                            (inc (levenstein (.substring x 1) y))))))
   "asdf" "Asde")

danielneal11:10:19

You can name the anonymous fn by including a name before the arglist

delaguardo11:10:15

levenstein is not memoized in you case.

FiVo11:10:59

yes now only your first call is memoized

delaguardo12:10:35

(defmacro mmemoize [f]
  (let [n (second f)]
    `(do
       (def ~n (memoize ~(cons 'fn (rest (rest f)))))
       ~n)))

((mmemoize (fn levenstein [x y] ...)) "asdf" "Asde")

delaguardo12:10:53

and another possible solution without macro -

((let [levenstein (fn [mem-fn x y]
                    (let [levenstein (fn [x y]
                                       (mem-fn mem-fn x y))]
                      (prn "!!")
                      (cond (empty? x) (count y)
                            (empty? y) (count x)
                            :else (min
                                   (+ (if (= (.substring x 0 1) (.substring y 0 1)) 0 1) (levenstein (.substring x 1) (.substring y 1)))
                                   (inc (levenstein x (.substring y 1)))
                                   (inc (levenstein (.substring x 1) y))))))
       mem-fn (memoize levenstein)]
   (partial mem-fn mem-fn))
 "asd" "ASD")
The trick is to generate memoized function that should have itself as a first argument

💯 4
FiVo12:10:36

Thanks that works

jaihindhreddy15:10:38

I'm trying to write a max-key that returns a collections of things that are equivalent and maximum. But my code looks ugly. Any suggestions?

user=> (defn maxima
  #_=>  ([coll] (maxima identity coll))
  #_=>  ([key-fn [f & rst :as coll]]
  #_=>   (cond (empty? coll) []
  #_=>         (empty? rst) [f]
  #_=>         :else (second
  #_=>                (reduce (fn [[curr-max max-vals :as state] x]
  #_=>                          (let [k (key-fn x)]
  #_=>                            (cond (> k curr-max) [k [x]]
  #_=>                                  (= k curr-max) [curr-max (conj max-vals x)]
  #_=>                                  :else state)))
  #_=>                        [(key-fn f) [f]]
  #_=>                        rst)))))
#'user/maxima
user=> (->> (maxima val (zipmap [:a :b :c :d :e] [1 2 1 2 2]))
  #_=>      (map key))
(:b :d :e)
user=> (maxima val {})
[]
user=> 

jaihindhreddy15:10:48

Or is there something in core I'm missing?

jaihindhreddy15:10:53

I've been able to improve this slightly by removing the 0 size and 1 size handling by using a sentinel value:

(defn maxima-3
 ([coll] (maxima identity coll))
 ([key-fn coll]
  (first
   (reduce (fn [[ms m :as state] [x k]]
            (cond (or (identical? ::none m) (= m k)) [(conj ms x) k]
                  (> k m) [[x] k]
                  :else state))
           [[] ::none]
           (map (juxt identity key-fn) coll)))))

jaihindhreddy15:10:13

But the first after reduce still smells a bit to me.

jaihindhreddy15:10:07

Also, adding the sentinel, introduces the (identical? ::none m) check for each item, slowing it down a bit.

Lars Nilsson15:10:28

(defn maxima [k v]
  (let [m (zipmap k v)
        selector (apply max v)]
    (keys (filter #(= (second %) selector) m))))

👍 4
jaihindhreddy15:10:49

Trying to figure out a one-pass solution to this

jaihindhreddy15:10:11

Not quite one-pass but this works too I guess:

(defn maxima-4
 ([coll] (maxima identity coll))
 ([key-fn coll]
  (if (empty? coll) []
   (->> (group-by key-fn coll)
        (apply max-key key)
        val))))

danielneal15:10:13

can you group by into a sorted map or something like that

danielneal15:10:34

(defn maxima
  [key-fn coll]
  (last (reduce (fn [acc x]
                  (update acc (key-fn x) (fnil conj #{}) x))
                (sorted-map)
                coll)))

jaihindhreddy15:10:37

Yup. The last one is basically what it does. I'm satisfied with it in-terms of how the code looks but, it still requires more memory than is really needed to do the job at hand. I don't need to carry around all groups (which is what group-by does), just the one with max-value.

didibus16:10:40

I find sometimes if you are looking for maximum performance, good old loop/recur turns out to be the most readable code:

(defn max-equal
  [m]
  (loop [acc [], m m, curr-max (Integer/MIN_VALUE)]
    (let [e (first m)]
      (cond (empty? m)
            acc
            (> (val e) curr-max)
            (recur [(key e)] (rest m) (val e))
            (= (val e) curr-max)
            (recur (conj acc (key e)) (rest m) curr-max)
            :else
            (recur acc (rest m) curr-max)))))

(max-equal (zipmap [:a :b :c :d :e] [1 2 1 2 2]))
;; => [:b :d :e]

Cas Shun17:10:39

Time once again for monday morning macros with Cas. I've attached a code snippet showing the barest form of my problem. I'm confused as to why the lambda shorthand works, but the fn does not.

andy.fingerhut17:10:21

It appears you have attempted to define macro-test-fail inside of a namespace named converter. I guess that because part of the error message mentioned converter/m converter/k etc. Symbols inside of a backquoted expression are expanded to symbols with explicit namespaces, so that when the macro is invoked later from some arbitrary namespace, the names referred to are the ones active where the macro was defined, not where the macro was called.

andy.fingerhut17:10:15

To create a variant of macro-test-fail that works, you could write it so that this does not happen for the function parameter m, k, and v.

Cas Shun17:10:59

placing macro-test-fail in converter.clj gives the same result fwiw

andy.fingerhut17:10:58

I am experimenting at my own Clojure REPL to remember how to do this

dpsutton17:10:18

i think you need the args to be m#, k#, etc

andy.fingerhut18:10:23

Yeah, that is definitely one way to do it:

(defmacro macro-test-2
  [m f]
  `(let [m# ~m ]
     (reduce-kv (fn [acc# k# v#]
                  (assoc acc# k# (~f v#)))
       {} m#)))

Cas Shun18:10:57

why do I need to gensym inside the lambda? I'm slightly confused by this

dpsutton18:10:12

compare > <backtick> (fn [x] 3) vs <backtick>(fn [x#] 3)

bfabry18:10:13

otherwise you could redefine something f uses

Cas Shun18:10:57

Hmm, I believe I understand. I think that error message is throwing me off a lot

Cas Shun18:10:16

thank you for the help folks 🙂

dpsutton18:10:28

and check out <backtick>`(foo ['x] 3)` and <backtick>`(foo [~'x] 3)`

bfabry18:10:43

I just checked and the escape is 3-backticks like old csv style @dpsutton 🙂

bfabry18:10:22

np, I figured I’d need it someday

Cas Shun18:10:31

@dpsutton I'm not sure I understand that at all

Cas Shun18:10:53

the unquote quote bit ^

andy.fingerhut18:10:45

If you have not used macroexpand-1 before, that can be useful when understanding what macros are doing. e.g.

(pprint (macroexpand-1 '(macro-test-fail {:one 1 :two 2} inc)))

andy.fingerhut18:10:21

Sometimes the error messages (like in your you pasted above) make more sense when you look at the macroexpanded expression.

dpsutton18:10:09

i think (and could be wrong) that you need to put a symbol there rather than a var

Cas Shun18:10:00

I was using macroexpand-1, but pp'ing it I was not. That makes a huge difference!

dpsutton18:10:17

and the backtick makes things into vars but you need a symbol. if you type ``(fn [x] 3)` compare the output to ``(fn [x#] 3)` in a repl. no need for macroexpadn

bfabry18:10:57

oh dammit it only works for balanced ticks

dpsutton18:10:05

i gave up 🙂

bfabry18:10:52

using double backticks to surround the whole thing works kinda like double-quotes I guess is what they’re going for but oh geez is that ever not intuitive` I also give up

Cas Shun18:10:35

4 backticks, test, 3 backticks

Cas Shun18:10:37

@dpsutton I'm not sure how to read the [x] in the ~' version. I assume that's a symbol?

Cas Shun18:10:47

the other two are clear

hiredman18:10:11

it doesn't "make things into vars"

hiredman18:10:07

backquote is also called "syntax quote" in clojure, and one of the things it does is fully qualify symbols

Cas Shun18:10:06

so ~'x is qualifying x to the context of the lambda, in the example above

hiredman18:10:09

~ is un(syntax)quote, and ' is quote so ~' is "un-syntax-quote and then quote"

hiredman18:10:46

which is useful because while syntax quote resolves symbols, quoting does not

hiredman18:10:34

(`~` is often just called unquote, which is confusing because it only unquotes in syntax quote)

hiredman18:10:57

saying "resolves symbols" can also be confusing in this context, because typically when one talks about resolving symbols in clojure it is mapping symbols to vars, but in the syntax quote case when it says "resolves symbols" it means turning the symbol in to a namespace qualified symbol

Cas Shun18:10:03

so ~'x belongs to no namespace?

hiredman18:10:44

it really has no meaning by itself because ~ doesn't mean anything without an enclosing syntax quote

hiredman18:10:11

quotation means "give me this literal thing, not evaluated"

hiredman18:10:40

unquoting is the inverse of that

hiredman18:10:22

user=> `(+ 1 2)
(clojure.core/+ 1 2)
user=> `~(+ 1 2)
3
user=>

hiredman18:10:48

user=> `~'(+ 1 2)
(+ 1 2)
user=>

Cas Shun18:10:56

ok, I understand that I believe. However, I'm still partially lost as to where x "exists" in

`(foo [~'x] f)

hiredman18:10:56

symbols don't in and of themselves exist anywhere

hiredman18:10:16

a symbol sometimes can be the name of a var in a namespace

hiredman18:10:56

but a "namespaced" symbol is just a longer symbol, it doesn't mean the namespace exists or that there is a var with that name

Cas Shun18:10:40

I'm still fuzzy on the difference between a symbol and a var, but I think that just made it clearer for me

hiredman18:10:15

the compiler, when given a symbol either resolves the symbol to a local or a var

hiredman18:10:04

a "local" isn't a first class value, it is just some storage used internally in some code

hiredman18:10:06

on the jvm when a method is called it has basically an array of storage for values, and you can think of a local as just being one of those slots

hiredman18:10:28

a symbol is a name; a var is a little mutable cell you can fiddle with the contents of, usually part of some namespace and named by some symbol

hiredman18:10:09

namespaces themselves are also named by symbols

hiredman18:10:21

user=> (type (ns-name *ns*))
clojure.lang.Symbol
user=>

Cas Shun18:10:22

what is the term for when a symbol is associated with a var? (I assume "associated" is incorrect)?

hiredman18:10:48

I would say most discussions don't happen at that level of detail

hiredman18:10:29

you might talk about some symbol being "def-ed" or "defined" in some namespace

Cas Shun18:10:08

ahh, ok that makes sense

Alex Miller (Clojure team)19:10:20

symbols have both syntax and semantics. syntactically, a symbol is just a word-like name. it is given semantic meaning based on both its lexical context and the state of the Clojure runtime environment.

Alex Miller (Clojure team)19:10:20

symbols may be unqualified names that refer to "locals" only in a lexical context (a chunk of code that uses defn/fn/let etc to define a meaning)

Alex Miller (Clojure team)19:10:57

or they may be qualified or unqualified names that are resolved to a meaning - either a var in a namespace or a Java class instance

hiredman19:10:58

(when the compiler is determining the meaning)

Alex Miller (Clojure team)19:10:08

who else is determining meaning? :)

hiredman19:10:27

if I quote a symbol

Alex Miller (Clojure team)19:10:58

maybe you were making a different point than I read it as

hiredman19:10:21

or I was reading your description as overly restrictive, I just mean you can have unqualified names that don't refer to locals just fine as long as you don't ask the compiler to do something with them

Alex Miller (Clojure team)19:10:20

yes, that's why I said "qualified or unqualified names"

Alex Miller (Clojure team)19:10:51

the compiler can of course do stuff with unqualified non-locals

Alex Miller (Clojure team)19:10:03

by resolving them in the context of the current namespace

csd20:10:32

is there any way to lein ring uberjar verbosely? i'd like to see a log of the classes being added to the jar

andy.fingerhut20:10:47

I have not tried it for a lein ring uberjar command, but it should be quick to try the command DEBUG=y lein ring uberjar to see what, if any, extra output is produced.

seancorfield20:10:27

You can always run jar tvf <path to uber>.jar after the fact to see what is in it @csd

csd20:10:49

I'm curious to see why a particular class is being bundled into the jar

noisesmith20:10:08

to answer that, you could try lein with-profile +uberjar deps :plugin-tree - or whatever profile ring ubjerjar uses while building classpath...

Jason Kimmerling22:10:14

Hello all, I have a VERY noobish issue. I am reading "Clojure for the Brave and True" and I have gotten stuck early. I cannot figure out what the last three lines in this code block actually do: (defn my-reduce ([f initial coll] (loop [result initial remaining coll] (if (empty? remaining) result (recur (f result (first remaining)) (rest remaining))))) ([f [head & tail]] (my-reduce f head tail))) Could someone explain what the recur is actually doing here?

bfabry22:10:31

recur means “recurse”. it’s the same as if you had (my-reduce there

bfabry22:10:48

sorry that’s wrong

bfabry22:10:56

it recurses at the “loop” statement in this case

dpsutton22:10:37

the last two lines are distinct from the line with recur in it. that's how you create different arity versions of the same function

bfabry22:10:47

basically, it jumps back to loop but replaces the values of result and initial with the result of (f result (first remaining)) and (rest remaining)

dpsutton22:10:25

> if give two args, f and a list, split the list into head and the rest into tail and call the three arity version with (my-reduce f head tail)

bfabry22:10:13

“replaces the values of result and remaining“*

Jason Kimmerling22:10:02

dpsutton - that makes sense, i was not seeing it as a different arity

Jason Kimmerling22:10:16

and thanks bfabry, you cleared up the recur a good bit for me

tschady22:10:59

smaller example can help. from https://clojuredocs.org/clojure.core/loop:

(loop [x 10]
  (when (> x 1)
    (println x)
    (recur (- x 2))))

;;=> 10 8 6 4 2

👍 4
Jason Kimmerling22:10:38

It seems like im going to need MULTIPLE resources at once to learn this lol. I truly appreciate the help 🙂

dpsutton22:10:16

that's why we're in the channel 🙂 to help

👍 4
vaer-k23:10:26

At the cider repl in emacs, with evil mode on, can I somehow cycle through and run previous commands?

erwinrooijakkers15:10:12

Control with up and down arrows works