Fork me on GitHub
#beginners
<
2018-08-03
>
ryan.russell01100:08:07

so a couple of noob questions... 1. why when rand-nth is used in-line, it consistently returns the same value? Is clojure caching the result? For example, (def rand-alpha (rand-nth (map char (range 65 91))) returns the same character everytime. However,

(def rand-alpha (map char (range 65 91)))
(rand-nth rand-alpha) (rand-nth rand-alpha)
Gives the desired results?

ryan.russell01100:08:23

second question, why does the entire function call need to be dereferenced in the following example?

(defrecord Person [age])
(defn set-age [] (Person. (atom (10)))
(defn get-age [person] @(:age person))
(defn reset-age [person] (reset! (:age person) 12))
I'm not understanding the dereferencing of the @(:age person) line, and why it doesn't need to be dereferenced in the reset-age line?

seancorfield01:08:15

Because def defines a global that is evaluated just once when the ns is loaded @ryan.russell011

seancorfield01:08:56

So (def rand-alpha ...) evaluates the ... once and that's the value of the global rand-alpha.

seancorfield01:08:40

For the second Q, the age in the Person record is an atom so it needs to be dereferenced to get the value.

seancorfield01:08:13

(:age person) returns the value for the key :age, which is an atom, and the @ reaches into the atom and gets the current value.

seancorfield01:08:21

Hope that helps @ryan.russell011?

seancorfield01:08:45

(the first Q indicates why you need to be careful not to do anything with side-effects in a def)

sundarj01:08:31

reset! is a function that takes an atom as the first argument, not a number, so dereferencing the :age atom before passing it to reset! wouldn't be correct

seancorfield01:08:58

(thank you @sundarj I'd missed that additional question!)

james08201:08:30

If you wanted to make a rand-alpha then it would need to be a function, e.g. (defn rand-alpha [] (rand-nth (map char (range 65 91)))). Then you could do (rand-alpha) and it'll evaluate that each time.

ryan.russell01101:08:46

it does... especially for the def, I wasn't aware of that. For the Person, I was thinking that it should have been (:name @robot), and through playing around with it got it to work. I get what it is doing now.. :name is a function into robot which is returning an atom value... so the entire thing needs to be dereferenced.

ryan.russell01101:08:17

I am guessing there is very few reasons to use def and I should just make that defn instead?

ryan.russell01101:08:04

and @james082, thank you.. that makes total sense.

seancorfield01:08:24

def is fine for constants (technically they create Vars and those are still "variable" but it really helps to not think of them that way -- pretend they're constants!).

ryan.russell01101:08:28

I will do that. thank you @seancorfield, @sundarj, @james082 as well for your responses!

james08201:08:46

So you could do (def letters (map char (range 65 91))) and then (defn rand-alpha [] (rand-nth letters)). Where letters is your constant and rand-alpha is a function that uses that constant.

seancorfield01:08:57

user=> (def foo 42)
#'user/foo
user=> (* foo 2)
84
user=> (alter-var-root #'foo dec)
41
user=> (* foo 2)
82
user=>
Just to show that def'd Vars can be updated... But don't do this (in general) 🙂

james08201:08:41

Technically speaking functions are constants that you just happen to execute. So (defn foo [n] (+ 3 n)) could be done as (def foo (fn [n] (+ 3 n))).

sundarj01:08:34

indeed, that's what defn expands in to

jasonpepas05:08:01

Thanks again for the help earlier guys, I got the roman numerals program working: https://gist.github.com/cellularmitosis/567d7fb89b35e1e8cf7743ad5a278b4e

jasonpepas05:08:22

(code style critique is of course welcomed!)

jaihindh.reddy07:08:14

The table might be better stored as an array-map

(def table (array-map 1000 "M" 500 "D" 400 "CD" 100 "C" 50 "L" 40 "XL" 10 "X" 9 "IX" 5 "V" 4 "IV" 1 "I"))
A reduction is more suitable here than using recur or looping IMHO.
(defn roman [num table]
  (reduce
    (fn [{:keys [result num]} [amt rom]]
      (if (= num 0)
        (reduced result)
        {:result (apply str result (repeat (quot num amt) rom))
         :num (rem num amt)}))
    {:result "" :num num}
    table))
Destructuring and reduced are useful things. quot and rem give you quotient and reminder while still being checked math (protects you from overflows, not that you'll encounter many here 😉 ) Because tail recursion is its own reward, here's a recursive version.
(defn roman
  ([num] (roman "" num (array-map 1000 "M" 500 "D" 400 "CD" 100 "C" 50 "L" 40 "XL" 10 "X" 9 "IX" 5 "V" 4 "IV" 1 "I")))
  ([num table] (roman "" num table))
  ([result num table]
    (if (or (= num 0) (empty? table)) result
        (let [[[amt rom] & next-table] (seq table)]
          (recur (apply str result (repeat (quot num amt) rom))
                 (rem num amt)
                 next-table)))))
This one also shows off clojure's multi-arity functions. Because the JVM doesn't eliminate arbitrary tail calls, you'll blow the stack if you call a function recursively. You can use recur instead.

tobias.krauthoff06:08:40

is here someone familiar with yada and bidi? i want to add a 404-ressource-not-found page instead of the fiven default 204.

alee12:08:12

So I was looking at (source defn) as you do and I saw (fn defn [&form &env name & fdecl] ...

alee12:08:35

I'm familiar with & for variadic functions but please can someone tell me what &form means?

vinicius.quaiato12:08:39

Being a beginner, I checked the Clojure doc, and found this: https://clojure.org/reference/macros > &form - the actual form (as data) that is being invoked And this blog post with a little bit more of explanation: http://blog.jayfields.com/2011/02/clojure-and.html

alee12:08:01

Cracking, thank you! Only Googled for ampersand, not the whole &form, doh

alee12:08:22

Secondary question - I'm using (binding [*ns* ...] (defn ...)) and when I eval in that ns later, my function isn't available. intern works. Any ideas please? binding definitely works as I had to (refer-clojure) this way (why is another mystery)

bronsa12:08:39

@alee &form and &env are special variables that are available automatically within macros, you're seeing them as explicit arguments to that fn because defmacro hasn't been bootstrapped yet by the time defn is implemented

bronsa12:08:07

&form is bound to the original form of a macro (e.g. if you (defmacro foo [& _] (list 'quote &form)) and you (foo (1 2 (3 4 )), you'll get '(my-ns/foo (1 2 (3 4))) back

bronsa12:08:27

&env is a bit more internal, it's the compiler environment at the time of macroexpansion of that macro

bronsa12:08:34

re: your binding question

bronsa12:08:52

binding is a runtime construct, defn is a macro that expands to 2 special forms evaluated at compile time

bronsa12:08:13

intern OTOH is a runtime construct and that's why binding "works" with intern and not with def

bronsa12:08:25

they operate at two different evaluation levels

alee12:08:39

That's fantastic, thank you

bronsa12:08:52

when I say runtime construct I mean "a regular function"

bronsa12:08:59

vs a macro or a special form

alee12:08:21

I guess there's something special about (the first?) ns form which tells defn where to put stuff?

bronsa12:08:02

kinda but probably not in the way that you're thinking of

bronsa12:08:24

the ns form (eventually) expands to a call to in-ns

bronsa12:08:38

which is a special form

bronsa12:08:58

but it being a special form is not the point

bronsa12:08:10

the point is that it's executed in a different compilation unit

alee12:08:23

I did originally use (try (in-ns ..) ... (finally (in-ns ..))) but I got an exception saying I couldn't set *ns* outside a binding

bronsa12:08:41

and compilation units that are compiled/evaluated afterwards can see the effects of previously evaluated ones

bronsa12:08:48

what you're trying to do can be achieved either by binding *ns* + using runtime intern, or by doing the entire thing in a macro

bronsa12:08:15

(the macro expanding to e.g. (do (in-ns ..) (defn ..) (in-ns ..)))

alee12:08:53

Interesting! Is there any advantage to the latter? EG getting errors at compile time?

bronsa12:08:19

depends on what you're trying to do

alee12:08:33

Set up a disposable namespace to eval code in

alee12:08:43

I'm writing a light literate testing system

bronsa12:08:05

maybe check how the tests in clojure do that, IIRC they do something similar

bronsa12:08:28

(I'm talking about the tests in the clojure repo, not clojure.test)

alee12:08:11

Cool, thanks for all the help! Very interesting

lockdown-15:08:42

How do I list required namespaces?

lilactown15:08:23

in your namespace declaration you do:

(ns my-ns.thing
  (:require [other-namespace]
            [some-other-namespace]
            [more-namespace]))

lockdown-15:08:34

yeah, I want to see from the repl which are required, does the result of all all-ns shows all that are required or just in the classpath?

lilactown15:08:53

ah, sorry. it looks like it’s all required?

dpsutton15:08:39

(ns-aliases *ns*) can show you the aliases in your current namespace.

lilactown15:08:59

when I start a REPL in my project, (all-ns) lists mostly just clojure.*. after I require a ns, the list returned by all-ns grows

juan45815:08:47

Hi everyone! I'm trying to use https://github.com/venantius/ultra. I've added it to my ~/.lein/profiles.clj and am starting a repl with lein repl :connect 6005. Lein installed the dependency, but I'm still not seeing any of the features, like syntax highlighting. Is there something else that I still need to do to initialize it?

eggsyntax18:08:17

@juan458 are you using Clojure 1.9? Per the ultra docs, they won't play well together.

drewverlee18:08:28

is there a reason to prefer defaults being added through destructing the function or being passed in as a map to the function? i suppose the use case might matter...

noisesmith18:08:55

what does "destructuring the function" mean?

drewverlee19:08:19

providing an or option when destructing

andy.fingerhut18:08:12

If you have a function where the options will be passed down to one or more others, perhaps through multiple levels of function calls, and perhaps with the options map augmented or changed down the call tree, it is a bit less code to pass it as a map argument.

andy.fingerhut18:08:00

Passing the options as [arg1 arg2 & opts] throughout that call tree would require doing (apply child-fn x y opts) in potentially many places.

vise89010819:08:13

Hey all! is this a good place to ask for some code review / nitpicking ? I made this for a project at work https://gitlab.com/vise890/parseq and i'd love to get some feedback on ways to make it better / faster

seancorfield19:08:09

Sure, either here or in #code-reviews

seancorfield19:08:00

@vise890108 That looks way beyond #beginners code by the way 🙂

vise89010819:08:58

haha ok thank you @seancorfield, i'll ask in #code-reviews

tkjone20:08:03

Hey all, curious if there is a more idiomatic way of doing this...can't think of it off the top of my head:

(if (or (= x true) (= x nil)) ;; better way to write this or?
  something-1
  something-2)

andy.fingerhut21:08:35

If the condition was instead (or (= x false) (= x nil)), then a more idiomatic way would be (not x). But there isn't anything built into Clojure that represents the combination of conditions you have.

andy.fingerhut21:08:29

Some people might write (or (true? x) (nil? x)) instead of what you have, but I don't see it as being any clearer.

mfikes21:08:56

The only other variation I can think of is (contains? #{true nil} x) but I think that is less clear.

mfikes21:08:15

I guess there is also ((some-fn true? nil?) x)

tkjone21:08:56

yeah, I think the last one comes closest to what I was thinking, but all of these suggestions are good to know.

dpsutton21:08:57

I think the if or version you have is the most clear of the conditions that trigger something-1. I would leave it there

alexmiller21:08:25

note that when something like this is hard, it may indicate that you should re-think whether it was a good idea to get to the point where you need to check those two things in the first place

alexmiller21:08:17

why is nil special? can you make it not be and/or avoid having it as a possibility?

alexmiller21:08:44

(not looking for an actual answer, but these are the questions I would ask myself)

tkjone21:08:58

For sure. I am running through those questions myself. This is a front end component, so right now I am running through edge cases - trying to handle defaults in a sane way.

noisesmith21:08:32

with interface option type logic, you probably want (def default-options {:easy true}) instead of having code that looks like (if (or (= (:easy opts) true) (= (:easy opts) nil)) ...)

noisesmith21:08:56

(the piece in the middle is merging the options explicitly set onto the defaults)

dawran621:08:27

Hi are hyphens (`-`) allowed in project names/namespaces? I created a rum project using this command lein new shadow-cljs rum-workshop +rum and couldn’t get the app to load. Then I created another project, lein new shadow-cljs rumworkshop +rum and it worked.

noisesmith21:08:42

- is allowed in the name of a namespace, but the file will have _ instead of - in the file name

noisesmith21:08:20

what does "couldn't get the app to load" entail - can you give some detail of what went wrong?

dawran621:08:03

Thanks @noisesmithfor responding. When I launch the app with command shadow-cljs watch app the app did not mounted on the div.

dawran621:08:26

It remained as the index.html.

noisesmith21:08:28

no error messages?

dawran621:08:59

Right I saw a message [Show/hide message details.] ReferenceError: workshop is not defined

noisesmith21:08:07

this might be some bug in the shadow-cljs template, clojure definitely accepts names like this

noisesmith21:08:33

(I mean, even shadow-cljs itself has a hyphen in the name...)

dawran621:08:20

Yeah you’re right hahaha. I’ll submit an issue for the shadow-cljs. Thanks so much for replying!