Fork me on GitHub
#beginners
<
2020-06-17
>
pntripathi941703:06:44

I have following code-snippet:

(def ^:dynamic *env*
  (into {} #?(:clj (System/getenv)
              :cljs (js->clj (.-env js/process)))))
When I run my tests for nodejs target I get following errors:
Exception: clojure.lang.ExceptionInfo: ClojureScript Exception
{:via [{:type clojure.lang.ExceptionInfo, :message "Execution error (Error) at (<cljs repl>:1).\n[object Object] is not ISeqable\n", :data {:type :js-eval-exception, :error {:status :exception, :value "Execution error (Error) at (<cljs repl>:1).\n[object Object] is not ISeqable\n"}, :repl-env #cljs.repl.node.NodeEnv{:host "localhost", :port 54832, :path nil, :socket #object[clojure.lang.Atom 0x37abe814 {:status :ready, :val {:socket #object[java.net.Socket 0xfadd58 "Socket[addr=localhost/127.0.0.1,port=54832,localport=61160]"], :in #object[java.io.BufferedReader 0x6ba88a47 "[email protected]"], :out #object[java.io.BufferedWriter 0x35f18100 "[email protected]"]}}], :proc #object[clojure.lang.Atom 0x335be91 {:status :ready, :val #object[java.lang.ProcessImpl 0x51386308 "Process[pid=45561, exitValue=137]"]}], :state #object[clojure.lang.Atom 0x18f98236 {:status :ready, :val {:listeners 0}}], :debug-port nil}, :form (require (quote com.vadelabs.grid.env-test)), :js "goog.require('com.vadelabs.grid.env_test');\n'nil';\n"}, :at [cljs.repl$evaluate_form invokeStatic "repl.cljc" 578]}], :trace [[cljs.repl$evaluate_form invokeStatic "repl.cljc" 578] [cljs.repl$evaluate_form invoke "repl.cljc" 499] [cljs.repl$eval_cljs invokeStatic "repl.cljc" 693] [cljs.repl$eval_cljs invoke "repl.cljc" 686] [kaocha.cljs.queue_eval_loop$start_BANG_$fn__3689$fn__3694 invoke "queue_eval_loop.clj" 78] [kaocha.cljs.queue_eval_loop$start_BANG_$fn__3689 invoke "queue_eval_loop.clj" 69] [cljs.compiler$with_core_cljs invokeStatic "compiler.cljc" 1435] [cljs.compiler$with_core_cljs invoke "compiler.cljc" 1424] [kaocha.cljs.queue_eval_loop$start_BANG_ invokeStatic "queue_eval_loop.clj" 55] [kaocha.cljs.queue_eval_loop$start_BANG_ doInvoke "queue_eval_loop.clj" 40] [clojure.lang.RestFn invoke "RestFn.java" 470] [kaocha.cljs.prepl$prepl$fn__3710$fn__3711 invoke "prepl.clj" 14] [kaocha.cljs.prepl$prepl$fn__3710 invoke "prepl.clj" 13] [clojure.core$binding_conveyor_fn$fn__5754 invoke "core.clj" 2030] [clojure.lang.AFn call "AFn.java" 18] [java.util.concurrent.FutureTask run "FutureTask.java" 264] [java.util.concurrent.ThreadPoolExecutor runWorker "ThreadPoolExecutor.java" 1128] [java.util.concurrent.ThreadPoolExecutor$Worker run "ThreadPoolExecutor.java" 628] [java.lang.Thread run "Thread.java" 830]], :cause "Execution error (Error) at (<cljs repl>:1).\n[object Object] is not ISeqable\n", :data {:type :js-eval-exception, :error {:status :exception, :value "Execution error (Error) at (<cljs repl>:1).\n[object Object] is not ISeqable\n"}, :repl-env #cljs.repl.node.NodeEnv{:host "localhost", :port 54832, :path nil, :socket #object[clojure.lang.Atom 0x37abe814 {:status :ready, :val {:socket #object[java.net.Socket 0xfadd58 "Socket[addr=localhost/127.0.0.1,port=54832,localport=61160]"], :in #object[java.io.BufferedReader 0x6ba88a47 "[email protected]"], :out #object[java.io.BufferedWriter 0x35f18100 "[email protected]"]}}], :proc #object[clojure.lang.Atom 0x335be91 {:status :ready, :val #object[java.lang.ProcessImpl 0x51386308 "Process[pid=45561, exitValue=137]"]}], :state #object[clojure.lang.Atom 0x18f98236 {:status :ready, :val {:listeners 0}}], :debug-port nil}, :form (require (quote com.vadelabs.grid.env-test)), :js "goog.require('com.vadelabs.grid.env_test');\n'nil';\n"}}
I can’t seem to understand what it means, and how can I resolve it?

smith.adriane03:06:32

what does (js->clj (.-env js/process)) show in the repl?

smith.adriane03:06:35

as well as #?(:clj "this is clj" :cljs "this is cljs")

smith.adriane03:06:47

there’s a lot on going in the one statement you posted. the best way to debug is break it down and reduce the surface of area of where the bug could be hiding

noisesmith04:06:31

"not iseqable" means "you can't implicitly use this thing as a collection"

noisesmith04:06:24

you can't just hand the output of

(js->clj (.-env js/process)))
to into like that, because clojure doesn't know what to do with it

noisesmith04:06:33

I think you want to move the into call into System/getenv

pntripathi941704:06:52

ClojureScript 1.10.764
cljs.user=> 
cljs.user=> 
(js->clj (.-env js/process))
#object[Object [object Object]]
cljs.user=> 
I wish to read environment variable in cljs environment. If I remove :cljs block it works fine…

noisesmith04:06:58

but! System/getenv already returns a Map, so you don't need the into unless you plan to conj to it

noisesmith04:06:37

right - you need some method on what .-env returns, js->clj isn't magic and isn't doing anything useful there

noisesmith04:06:51

there are many values in js that should just be data but sadly are not

noisesmith04:06:00

so you need to find the voodoo that turns it into data

pntripathi941704:06:02

Interesting… that explains the issue… thanks @ It really helps.. will try few changes and see if I get it working… Let me try to figure out that “voodoo” 😄 😄

noisesmith04:06:25

I looked it up in the mdn docs, it's just a vanilla js obj, I bet there's something in goog that turns it into a hash

noisesmith04:06:59

you could do it with this, but you would think somebody made something that would just give you a hash map by now https://google.github.io/closure-library/api/goog.object.html#getKeys

noisesmith04:06:44

clearly something else is going on then, based on the error

noisesmith04:06:27

perhaps the thing js->clj returns as a map isn't seqable? that seems super wierd though

pntripathi941704:06:50

If I remove into function… then it works… but need to dig deeper to understand what’s going on…

noisesmith04:06:26

oh, then it's just that js->cljs is weird (the cljs authors warn against using it)

smith.adriane04:06:51

(persistent!
 (reduce (fn [r k] (assoc! r (keyfn k) (thisfn (gobject/get x k))))
         (transient {}) (js-keys x)))

noisesmith04:06:18

if you are OK with a weird almost map that isn't seqable, use js->clj, otherwise you can use goog.object - yeah like that @

smith.adriane04:06:18

this is taken from js->clj and seems like it would work

smith.adriane04:06:28

but I’m guessing js->clj doesn’t work because it can’t tell if it should convert the type that is (.-env js/process)

smith.adriane04:06:29

the above snippet lets you parameterize the key type, but it’s probably just better to use the given key:

(persistent!
 (reduce (fn [r k] (assoc! r k (gobject/get x k)))
         (transient {}) (js-keys x)))

noisesmith04:06:29

also, since this is #beginners we can lose a few ms of perf and use

(into {} (map (fn [k] [k (gobject/get x k)]))
      (js-keys x))

noisesmith04:06:00

(if it's easier to read / understand)

pntripathi941704:06:09

Thanks guys…

(defn- nodejs->clj
  [x]
  (persistent!
   (reduce (fn [r k] (assoc! r k (gobject/get x k)))
           (transient {}) (js-keys x))))

(def ^:dynamic *env*
  (into {} #?(:clj (System/getenv)
              :cljs (nodejs->clj (.-env js/process)))))
This snippet works as expected…

noisesmith04:06:28

if all you are doing is lookups, you might be able to skip both into and nodejs->clj (using the raw map from System/getenv, and the raw map from js-clj )

noisesmith04:06:14

also, a common thing is to end up merging the result of getenv and getProperties (useful because it's easier to set / impose properties with java tooling)

pntripathi941704:06:20

Cool.. thanks… I did end up removing into from the implementation…

pntripathi941704:06:25

Just an FYI, whenever I was running the above snippet in repl it was freezing my repl and editor… I realized that the js/process has some cyclic key references… It works well with following:

(def ^:dynamic *env*
  #?(:clj (System/getenv)
     :cljs (->> (.-env js/process)
                (.stringify js/JSON)
                (.parse js/JSON)
                (js->clj))))
I am just stringifying and parsing js object back again…

smith.adriane05:06:00

interesting. I assumed that all the keys and values for the node process.env would be strings.

pntripathi941705:06:19

Yes… even I hoped so… but it’s JS… lots of weird things keep happening under the hood 😄

contact92104:06:29

My 2018 Macbook Pro battery swelled up and the Apple store isn't open anytime soon, so I've decided to take a look at Clojure coming from a Swift background as I'm now on Windows for the foreseeable future

contact92104:06:55

I'm interested in learning a lisp as well as functional programming

contact92104:06:36

So far, I've installed Cursive in IntelliJ and I've been playing around with the REPL

andy.fingerhut04:06:19

FYI there is a #cursive channel here, too, which might be the best place to ask questions that are specific to Cursive. Granted, it might not be obvious at first which of your questions might be Cursive-specific.

contact92104:06:11

My first task will likely be creating a deck of cards

noisesmith04:06:20

doing that as symbols is a fun one-liner - iirc it's one of the puzzles on http://4clojure.com

noisesmith04:06:45

and the core function shuffle does exactly what one wants in this domain haha

contact92104:06:27

I'm not used to lisp, I feel like it's going to take a few days to get the hang of the syntax

noisesmith04:06:47

the thing to really get the hang of is we have nearly no syntax!

noisesmith04:06:08

we have a far fewer "special cases" than most languages do

noisesmith04:06:26

everything that can act like a function or a collection does, as a good first rule of thumb :D

contact92104:06:45

also immutability, which I used a bit in Swift since structs and let is a thing

contact92104:06:57

in terms of the functional side

noisesmith04:06:04

and then laziness - that's another gotcha

noisesmith04:06:11

but these are not syntax, they are semantics

noisesmith04:06:26

(which can be harder, it's just a different kind of problem)

noisesmith04:06:12

syntax: how do I write the behavior I understand in my new language semantics: how do I construct the result I want from the behaviors my language offers

raspasov04:06:27

When I was first learning Clojure it seemed like a very steep uphill, and it can be; the good part is that the steep uphill is SHORT; you learn the few main concepts that are hard initially and then you’re like “That’s it?“…. and yes, that’s pretty much it 🙂

seancorfield04:06:51

@contact921 Since you're on Windows, you might find #clj-on-windows of interest. Do you use Scoop to install things on Powershell?

contact92104:06:34

I haven't tried yet, but I'm running Cursive inside of IntelliJ

seancorfield04:06:48

Was that your preferred editor before this?

contact92104:06:23

Prior to this I had been using XCode with Swift

raspasov04:06:30

@contact921 do you have the REPL running from within Cursive? If yes, you’re on a good track

contact92104:06:00

@ nREPL server started on port 59471 on host 127.0.0.1 - <nrepl://127.0.0.1:59471> Clojure 1.10.0

raspasov04:06:08

Cool, I’ve been using Cursive for years, I’m a fan

contact92104:06:36

I noticed that sometimes with parentheses you can delete them, is there a reason for this?

contact92104:06:44

Sometimes I want to insert code, or move them

seancorfield04:06:48

Ah, fair enough. I use both macOS and Windows, but I don't like IDEs so I find IntelliJ way too heavy.

raspasov04:06:07

In general, try to not get in the position where you need to delete () one by one

contact92104:06:08

@ are you on emacs?

raspasov04:06:26

Cursive and most all good Clojure editors manage that for you

seancorfield04:06:37

I switched off Emacs about four years ago (after using it on and off for 20-some years).

raspasov04:06:50

@ what do you use?

seancorfield04:06:08

I went from Emacs to Atom, with Chlorine now, and Cognitect's REBL data browser.

seancorfield04:06:20

I've posted a few YT videos about my workflow.

raspasov04:06:05

@contact921 in any case, let me know if you have any specific questions about Cursive or in general Clojure, happy to help;

contact92104:06:25

Thanks! I see now that the parentheses are highlighted in yellow to show the scope

raspasov04:06:37

I’m on Mac, the worflow within is probably similar

contact92104:06:41

Makes keeping track a lot easier

raspasov04:06:44

Yes, exactly

seancorfield04:06:58

I thought Cursive had parinfer which managed parens automatically?

raspasov04:06:09

And backspacing auto-magically deletes matching ()

seancorfield04:06:20

There's a #cursive channel BTW where the author provides support.

raspasov04:06:32

So you never have to “manually” delete them except in very rare cases, in those it’s usually just easier to retype the expression

seancorfield05:06:31

Probably the biggest adjustment when learning a Lisp is getting used to either parinfer or paredit and letting the editor manage parens for us, and then the next level is "structural editing".

raspasov05:06:33

@ yea it does, but you can manually select over some (()) and you can still delete non-matching (); again that’s almost never needed

raspasov05:06:34

@contact921 in terms of structural editing: I assume on Windows it’s also under Edit -> Structural Editing in IntelliJ

raspasov05:06:11

I’d point you to Slurp/Barf combinations; for me those are essential for structural editing

raspasov05:06:50

Also “Move Form Up”, “Move Form Down”

raspasov05:06:46

I probably use no more than 4-6 of the Structural Edition actions… you don’t really need all of them to be productive but I encourage you to try them all out

contact92105:06:04

Thanks! I'm off to bed for now but will be back tomorrow

raspasov05:06:22

Cool! Good night! 🙂

raspasov04:06:25

And to say they are “hard” is wrong, it’s more like they are “unfamiliar”

contact92104:06:16

I installed things a few hours ago, and now have a base project in leiningen setup which I'm using to test syntax

danieltanfh9505:06:38

i think the biggest barrier is parentheses and the lack of code you actually need to write troll

seancorfield05:06:41

Once you get to a certain point, it's very much "what parentheses?" 🙂

danieltanfh9505:06:36

commented lisp code is almost python troll

seancorfield05:06:14

Parinfer uses indentation to organize parens so it is very nearly Python.

phil63406:06:11

When do I use reset! vs swap!

phil63406:06:38

Superficially they seem to do pretty similar things

seancorfield06:06:44

Prefer swap! so that you are applying a (pure) function to an atom's value rather than just resetting it to a new value.

seancorfield06:06:29

(reset! a (inc @a)) has a race condition that (swap! a inc) does not.

phil63406:06:49

Is there a case where reset! is appropriate?

seancorfield06:06:18

If you are resetting an atom to a specific constant and no race condition is possible.

phil63406:06:27

I see the docs say 'without regard to previous state' but the implications of that aren't clear to me.

lsenjov06:06:44

If you have to refer to the previous value, use swap!, basically

seancorfield06:06:52

Do you understand why the expression above is a race condition @phil634?

phil63406:06:09

Because something else can get at 'a' I guess?

seancorfield06:06:35

Right, two threads, both executing (reset! a (inc @a)) could end up adding 1 or 2.

seancorfield06:06:57

But two threads executing (swap! a inc) are guaranteed to add 2.

phil63406:06:19

So in a case where I know nothing else is going on, I can see that I might be safe with reset! but it seems odd that it's there if there's no case where there's a reason to prefer it.

seancorfield06:06:02

If you want to set an atom to a constant, regardless of what else might be trying to update it, reset! is appropriate.

phil63406:06:50

Ah OK. So it's a way to forcibly win the race condition?

seancorfield06:06:16

Yes, that's one way of looking at it.

phil63406:06:35

Gotcha, I think that makes sense in terms of the code I'm trying to understand. Thanks 🙂

seancorfield06:06:53

But bear in mind the expression above -- which depends on the current value of the atom -- and therefore causes a race condition.

seancorfield06:06:29

(reset! a v) is a shorthand for (swap! a (constantly v))

phil63406:06:54

Ohhh OK. That's crystal. "Use the source Luke"

seancorfield06:06:10

(the implementation is different -- it uses primitives in clojure.lang.RT -- but the effect is basically the same)

raspasov07:06:11

@phil634 in practice, reset! is very rarely needed; a situation I can recall using reset! is during development in the REPL; otherwise, not much

phil63407:06:50

The case I came across was in the context of a simple Reagent markdown editor. {:on-change #(reset! markdown (-> % .-target .-value)) :value @markdown}]

raspasov08:06:53

I see; yea that’s a case where you’re simply grabbing the whole value from onChange and setting it to be the value of the atom named ‘markdown’; that’s an acceptable usage for simple cases like that;

raspasov08:06:41

also in ClojureScript as long as everything is on the same thread (which it usually is), the discussion about race conditions doesn’t really apply; that being said, swap! still has value, even on a single thread in many cases; makes it easier to reason about the current state of the data in your program;

ceronman08:06:49

Hi everyone! I learned that I can create self-referencing datastructures (e.g. a ring buffer) in Clojure using atoms. https://stackoverflow.com/questions/3696214/is-it-possible-to-create-circular-references-in-clojure Sadly, I also learned that this breaks the REPL, because of a stackoverflow. I wonde if there is a workaround for this.

ales.najmann08:06:00

naive solution could be to wrap reset! in function which supress printing out recursive result. Example:

(defn reset-silent! [atom] (reset! (first atom) atom) "Done")

ales.najmann08:06:54

But I was striked by something else... When dereferencing the atom... I got this runtime error:

Execution error (ClassCastException) at user/eval143 (REPL:0).
class clojure.lang.PersistentVector cannot be cast to class java.util.concurrent.Future (clojure.lang.PersistentVector is in unnamed module of loader 'app'; java.util.concurrent.Future is in module java.base of loader 'bootstrap')
I'm on OpenJDK 11.0.7 Confirmed: it's really caused by the previous exception 😅

ales.najmann08:06:22

But it also maybe the result of the broken repl... not sure though

delaguardo09:06:36

https://clojuredocs.org/clojure.core/*print-level* by default print-level is nil that cause your data to be printed recursively. Try give it some number )

delaguardo09:06:44

trivial example:

nil
user> (set! *print-level* 3)
3
user> (def a [(atom nil)])
#'user/a
user> (reset! (first a) a)
[#<[email protected]: [#]>]

hindol.adhya10:06:06

A ring buffer is low level stuff. A good fit for deftype I think.

raspasov10:06:54

@ that’s great stuff about print-level, didn’t know about it!

ales.najmann10:06:36

@ I think it doesn't matter, I would suggest controlled access (indirection) because you can somewhat easily do circular things with non-circular data and it's probably a bit more seizable.

hindol.adhya10:06:06

I was thinking you'd want the buffer elements next to each other in memory, as in a Java array of primitives. Purely from performance point of view. If I have to do it, I would probably pick a good ring buffer implementation from Java.

hindol.adhya10:06:20

But I guess the question is about self reference and not specific to ring buffer.

ceronman11:06:34

That's a great tip, thanks a lot!

roguas11:06:59

Ok, moving a more specific question from main clojure here. I have namespace:

(ns config)
(defn load-x [] (System/getProperty "X"))
(def X (load-x))

(ns app)
(System/setProperty "X" "some-val")
(use 'config :reload-all)
config/X
Problem is, it does not reload def X It's just a representation of the folder, I know this code is not "repl" consider files config.clj and app.clj exist in filesystem

delaguardo11:06:15

but X is a string in your example, why you need to call it? and it is reloading after removing () around config/X

roguas12:06:55

fixed the code, it was typed snippet... problem is that it is not reloading... I will try and run isolated example in new project

roguas12:06:56

ok seems to be as you stated... so the problem is somewhere else, thanks 🙂

roguas12:06:02

still the problem is visible from deftest

jeevarajmvsvg11:06:39

Hello Can anyone help me

jeevarajmvsvg11:06:51

How to break the loop in Clojure

jeevarajmvsvg12:06:18

Anyone let me know

mohsinhassaan14:06:30

You usually dont explicitly break loops, just put the recur in one branch of an if condition and the return value in the other

jimstrieter12:06:05

I have a defrecord example like this: (defrecord SomeName [^String field1 ^boolean field2])

jimstrieter12:06:21

I'm having difficulty creating an instance of the above record. I figured I could do something like this: (def f (SomeName "hi" false)) but I get this error: Execution error at nrepl.middleware.interruptible-eval/evaluate (interruptible_eval.clj:79). Expecting var, but SomeName is mapped to class <project_name>.core.SomeName

delaguardo12:06:47

(def f (SomeName. "hi" false)) I added a dot after record name. record defines a class so to create an instance you have to use constructor.

jimstrieter12:06:39

That worked! Thank you! 🙂

alexmiller12:06:32

It’s preferred to use the constructor functions instead of Java interop

alexmiller12:06:08

(->SomeName “hi” false)

alexmiller12:06:20

(map-SomeName {:field1 “hi” :field2 false})

alexmiller12:06:44

These functions are created automatically when you use defrecord

jimstrieter12:06:06

Just tried the arrow style and that worked.

jimstrieter12:06:00

Tried (map-SomeName...) and got this error: Unable to resolve symbol: map-SomeName in this context

alexmiller12:06:40

sorry map->SomeName

joshkh12:06:26

how might i easily determine if some object is capable of having metadata attached to it?

joshkh12:06:25

does (instance? clojure.lang.IObj {:abd 123})cover all bases?

jimstrieter12:06:19

In the following function definition: (defn f [name1->name2]...) What does the name1->name2 syntax mean?

alexmiller12:06:46

there's nothing special about that syntax. name1->name2 is a valid symbol, just like foo

jimstrieter12:06:21

Oh okay. Got it

alexmiller12:06:24

so hard to say what it means w/o context

jimstrieter13:06:44

That's fine. Clojure allows variable names unlike anything I've ever seen before. In C++, for example, name1->name2 means access method name2 in instance name1. But -> is also the threading macro. But if it's just a plain old name I can accept that.

roguas13:06:09

How to do (use 'some-namespace :reload-all) inside deftest macro? Or achieve a similar effect. I noticed this is my main issue with regards to previous case of reloading def

noisesmith13:06:42

if the variables are already refered, you just need require - I often use this at my repl:

(doto 'some.project.some-test (require :reload) (in-ns) (clojure.test/run-tests))

roguas13:06:47

@ problem is im having trouble with this from within deftest macro,

(deftest configuration-test
  (testing "sometest"
    (use 'app.conf :reload-all :verbose)
    (prn app.conf/some-val)))
    
problem is... that it says its reloading
(clojure.core/load "/app/conf")
(clojure.core/in-ns 'user)
(clojure.core/refer 'app.conf)
but app.conf name space has (prn "refreshing") in the global scope to show me if its refreshed -> and its not getting called

roguas13:06:38

and also the value remains unchanged, it seems the its already interanlly binded and wont budge

noisesmith13:06:08

don't use use for loading, use require

noisesmith13:06:33

also reloading should be a dev operation, reflecting the fact you edited a file, it shouldn't be part of test logic

roguas13:06:48

@ hm, the problem is that config is loading data from env/config file, i memoize the loading so that its pure with cached values... for runtime i do not need dynamic reloading - but for test and validating some scenarios i need to switch the config file

roguas13:06:00

for the config namespace i very much desire it behaves as just some static values... but for testing I try to just call side-effecty (internal)mock library, that just switches out some of the stuff

roguas13:06:59

I try to avoid reference passing as much as possible, with dynamic reloading config becomes a stateful dependency - which generally I try to remove from codebase 🙂

roguas13:06:30

also require works the same - as in doesnt work 🙂

noisesmith13:06:19

then check things like whether the file you edited is the same as the one the repl is using - if require didn't work we would have noticed

noisesmith13:06:55

but in a bigger picture, requiring reload of a namespace to test your code is a code smell in clojure, and is a sign you are making things hard for yourself by fighting the language

roguas14:06:10

its either this, reference passing, or going berserk with with-redefs

noisesmith14:06:37

we usually reference pass

noisesmith14:06:20

I'm not saying what you are doing is objectively wrong, or impossible. just that it is going to be a fight

noisesmith14:06:19

there are libraries that do config, (env, doesn't do reloading, aero, does do reloading), you might look at what they do

roguas14:06:19

so I am actually refactoring large codebase, and trying to tackle some of the implicitly stateful things that were clumsy -> ref passing is not nice, because now I can access settings in straightforward way (since they are considered consts to life cycle of the app) I can just config/some-val... which is big improvement over @config/some-val

noisesmith14:06:30

sure, env and aero both offer that as well

noisesmith14:06:24

you can avoid redefs and still be testable without namespace reloading in test code, by having a mechanism of rebinding the definitions without reloading the namespace - eg. alter-var-root or intern can be used to change the value inside a var or create one from scratch

noisesmith14:06:18

that said, reloading should work, and the failure to reload is probably a dev or tooling error

roguas16:06:13

@ thanks for help, i managed to establish the problem was :aot flag

noisesmith16:06:46

facepalm I feel like I should have noticed that

roguas16:06:46

still your intuition on this being crappy solution was very good 🙂

danieltanfh9513:06:01

wouldn’t that normally mean change something to another thing in the context of clojure? like clj->js

skwak2213:06:51

hello! I'm someone who comes strictly from a clojure/java background wanting to do some front end work. I don't really understand how reagent/clojure input forms work. If I want to take a value from a text field and set an atom's value to it, where would I learn to do that? For reference, I don't really understand what's going on in this piece of code:

:on-change #(reset! time-color (-> % .-target .-value))

noisesmith13:06:21

which parts?

skwak2213:06:47

(-> % .-target .-value)
specifically. I understand the thread macro and the %, but I don't understand what's happening with .-target and .-value

noisesmith13:06:42

.- is just like . except it only works on properties and not methods

noisesmith13:06:04

it's needed in cljs because a cljs object can have a property and method with the same name, so it disambiguates

noisesmith13:06:22

it's valid in clj, but never needed as having the same name for a method and a property is impossible in the jvm

skwak2213:06:34

Could you also explain . ?

noisesmith13:06:24

it's used for accessing methods on objects, which are superficially like functions but unlike functions are not first class entities

noisesmith13:06:42

or properties, which are like keys on a map

skwak2213:06:50

I think that makes sense to me. Thank you so much

contact92113:06:09

In Clojure, we define a variable with def and a function with defn? And defn is just a shorter version of (def name (fn [params* ] exprs*))?

noisesmith13:06:37

that's mostly true, yes

noisesmith13:06:59

both def and defn create var objects which are attached to the current namespace

noisesmith14:06:17

inside a function, you should use let to create local bindings, because def can only create namespace level globally visible bindings, and isn't appropriate for local values

contact92114:06:12

Perfect, this answered a question I had regarding let

contact92115:06:53

How do you write an if-else statement?

contact92115:06:05

I see examples of using if and got that to work, but not else

contact92115:06:28

(defn login-system
  []
  (println "Enter password: ")
  (let [password (read-line)
        password-attempt (read-string password)]
    (if (= password "clojure")
      (println "Logged in!"))))

dpsutton15:06:36

(if true :a :b) (if false :a :b)

noisesmith15:06:38

else is just the third (optional) form in an if

noisesmith15:06:30

also, if always returns a value (which is much more civilized if you think about it, and very useful)

contact92115:06:50

So this code is working, except I noticed something odd

(defn login-system
  []
  (println "Enter password: ")
  (let [password (read-line)
        password-attempt (read-string password)]
    (if true (= :password-attempt "clojure")
             (println "Logged in!")))
    (if false (= :password-attempt "clojure")
              (println "Bad password!")))
Entering 5t as a password attempt throws the following error: Execution error (NumberFormatException) at cjlr.core/login-system (core.clj:28).Execution error (NumberFormatException) at cjlr.core/login-system (core.clj:28). Invalid number: 5t But then other entries like r4 work?

andy.fingerhut15:06:43

read-string should be unnecessary there, as read-line should return a string.

andy.fingerhut15:06:07

read-string means "use the Clojure data/code reader given this string, and try to convert the first Clojure data/code expression into a data structure in memory"

noisesmith15:06:23

I find that code very weird - the two ifs will respectively only call their first or second branch

andy.fingerhut15:06:28

The error is because 5t is not a legal Clojure data/code thing. It starts out looking like a number, but has the t tacked on

noisesmith15:06:32

did you misunderstand @dpsutton’s example?

contact92115:06:09

I was getting stuck with :a .b trying to compare a variable to a string

noisesmith15:06:22

:a is not a variable!

contact92115:06:23

(if true :password : "clojure")

noisesmith15:06:40

that's another problem, :password is a keyword, it's a data object that stands for itself

noisesmith15:06:58

and if true is never useful, it's only used in the example to show how branching works

noisesmith15:06:25

see my example at the top level - one call to println, with a branch to return the apropriate string

noisesmith15:06:09

if you just do the "elementary" problems on http://www.4clojure.com/problems they are a good step by step intro to clojure basics btw, (using an old version, but that shouldn't matter for the really basic stuff)

noisesmith15:06:55

also this guide is great, if you haven't looked at it yet https://clojure.org/guides/learn/syntax

contact92115:06:38

Perfect, I'm going to check these out!

oliver.smit06:06:13

@ just wanted to chip in - thanks for the resources 🙌

noisesmith15:06:53

I'd expect (println (if (= password-attempt "clojure") "Logged in!" "Bad password!"))

dpsutton16:06:00

group-by and then concat the two groups

noisesmith16:06:16

that looks like (sort-by (juxt :b :a) maps) to me

noisesmith16:06:05

yeah, it's more than two groups, but group-by would help

dpsutton16:06:30

but i read it there's only two groups: :b is bananas, or its not bananas. put the bananas group first and then the rest

dpsutton16:06:37

order doesn't matter, just all bananas first

noisesmith16:06:45

I saw them as both segregated and sorted

dgonsalves2216:06:11

just all bananas first

noisesmith17:06:18

(ins)user=> (pprint (sort-by (comp not #{"bananas"} :b) maps))
({:a 1, :b "bananas"}
 {:a 1, :b "bananas"}
 {:a 1, :b "apples"}
 {:a 4, :b "apples"}
 {:a 1, :b "apples"}
 {:a 5, :b "oranges"}
 {:a 7, :b "apples"})
nil

andy.fingerhut17:06:57

Perhaps even (sort-by :b maps) if you truly do not care about the relative order of maps with the same value for the :b key

andy.fingerhut17:06:24

Oops, misunderstood question. Never mind.

contact92117:06:23

Things are a lot easier now that I have slurp and barf figured out

dgonsalves2217:06:46

Thanks guys. that exactly what I needed to push through!

manas.marthi19:06:13

I want to remap  M-e to eval last sexp. how to do it? please help. I tried this `(define-key cider-repl-mode-map (kbd "M-e") #'cider-eval-last-sexp )`   but it did not work (edited) This is the error Symbol's value as variable is void: cider-repl-mode-map

vachichng19:06:11

I don't use emacs, maybe in #cider channel you can get an answer.

contact92120:06:48

Do I need to reload the repl each time I make a change to my code in a project file?

contact92120:06:16

I'm using leiningen and nREPL inside intelliJ

noisesmith20:06:06

there's some clumsy workarounds, but the normal thing is to restart your repl if you change your project.clj

noisesmith20:06:34

or if by "project file" you mean some file in your project, there are a few ways to keep your repl up to date without restarting

contact92120:06:07

For example, I have a core.clj file which I'm writing test functions in and then running in the repl

noisesmith20:06:35

cursive should provide some shortcut or option for reloading / auto-reloading

noisesmith20:06:48

the generic answer is (require 'my.ns :reload) - that works everywhere

noisesmith20:06:21

a common thing for me to use while developing: (doto 'my.ns-test (require 'my.ns :reload) in-ns clojure.test/run-tests) - this gives me a single thing to search for in repl history and run again

noisesmith20:06:46

but once again, cursive likely have a shorthand that's more convenient, every environment offers something different

seancorfield21:06:19

And if you remember to eval every top-level form as you write or change code, your REPL should stay up-to-date without needing any of those reload options. That's what I recommend everyone aims for.

doby16221:06:40

is defmulti pretty much the go-to for running a different method/function body depending on a specific property of a map?

yokoyama.km22:06:36

This is how I usually do that. I'm not aware of a better way.