Fork me on GitHub
#beginners
<
2022-03-01
>
seancorfield00:03:40

@michael740 One approach you might consider is first refactoring your ns so the function definitions are all in my.ns.impl and then have the versions in my.ns just call the ones in my.ns.impl -- then you can copy my.ns to my.ns.v2 or whatever and you have the whole API available and you can modify/add whatever is actually different between the two versions and leave the common stuff as-is.

seancorfield00:03:54

(for someone using Polylith, this is the default approach for "components" -- separate interface and implementation namespaces)

Michael Stokley00:03:19

thank you, that’s a good idea

Michael Stokley00:03:38

so for example (defn f [x] (my.impl.namespace/f x)) across all public vars

1
teodorlu09:03:48

> @ Michael Stokley One approach you might consider is first refactoring your ns so the function definitions are all in my.ns.impl and then have the versions in my.ns just call the ones in my.ns.impl -- then you can copy my.ns to my.ns.v2 or whatever and you have the whole API available and you can modify/add whatever is actually different between the two versions and leave the common stuff as-is. @seancorfield Would you then only add docstrings on the public functions? Do you have any public projects where you've followed this structure? I had a look in jdbc.next and couldn't find any impl files.

seancorfield16:03:37

next.jdbc is a set of APIs and most everything is public so it doesn't really lend itself to that structure. In Polylith, where you always have separate interface and implementation files (often <top-ns>.<component-name>.interface and <top-ns>.<component-name>.impl) what I like to do is write the interface docstrings for the users of the component and the implementation docstrings for future maintainers of the component.

seancorfield16:03:00

(`next.jdbc` has 104 public API functions I think, and 37 private functions, nearly all of which are helper functions and many could be inlined into let forms)

teodorlu21:03:46

That makes a lot of sense. Thanks for explaining!

Adir Ohayon09:03:36

Hello everyone! I'm trying to print a map (its' key value pairs) using println and map, I have tried something like so:

(defn map-printing
  [new-map [key val]]
  (map (println "key is: " key "and val is " val) new-map))
But it's not working, what am I missing here?

Ferdinand Beyer09:03:42

You might have fallen victim of map’s lazy nature — it will not evaluate it’s body unless read from

Martin Půda09:03:10

(doseq [[k v] {:a 5 :b 6 :c 3}]
  (println "Key is" k " and val is" v))

🙌 1
Ferdinand Beyer09:03:18

Try using doseq instead:

(doseq [[k v] newmap]
  (println "key is: " k "and val is " v))

Ferdinand Beyer09:03:47

Or try wrapping your map in doall

Adir Ohayon09:03:24

Possible indeed @U031CHTGX1T First thank you both for the answers But regarding the lazy part - (map inc [1 2 3 4 5]) this for example does what's expected despise the lazy part - what is the difference?

Ferdinand Beyer10:03:34

It depends on what you do with the return value. On the repl, both should eagerly evaluate, as the REPL tries to print the lazy sequence. When using with side effects, like println, you will often ignore the return value of map. Then it will not be executed at all. Think of map as a functional tool that tries to “optimise itself away” when the return value is never used.

Ferdinand Beyer10:03:28

E.g. run this on the REPL: (map println (range 10)) — it will print 10 numbers. Now do: (def result (map println (range 10))) — it will not print the numbers. Then look at the result var: result — now the map is evaluated and the values will be printed.

Ferdinand Beyer10:03:05

(map inc [1 2 3 4 5]) behaves the same: It will do nothing unless you use the return value.

Adir Ohayon10:03:06

AHhhhhh, Noice! got it! 😄 Many thanks for the very very detailed explanation, much appreciated

👍 1
Ferdinand Beyer10:03:30

This seemingly odd behavior allows you to use map efficiently for very large or even infinite sequences. E.g. (take 10 (map inc (range))) will return the first 10 numbers after map, even though (range) returns an infinite sequence!

😲 1
Adir Ohayon10:03:57

Haven't thought of that, that's quite a powerful usage

dabrazhe14:03:00

Is there a way to filter for several regex matches in one pass? eg for #"as" #"te" #"be" , instead of one.

(filterv #(re-find #"te" %) ["test" "rest" "best" "taste"]) 

teodorlu14:03:34

bb -e '(filterv #(re-find #"(as|te|be)" %) ["test" "rest" "best" "taste"])'
["test" "best" "taste"]
Use regex groups?

1
dabrazhe14:03:13

Aha, I though it can be done with regex, instead of some or set or smth. Thanks!

👍 1
sheluchin17:03:04

I am running into some problems getting changes in my test files to be recognized. I make changes, like commenting out a whole test block, but they do not seem to be "seen" on the next test execution. Sometimes I get an error message like this, but not always: > Alias foo already exists in namespace com.example.test-etl, aliasing com.example.foo Can anyone suggest what I might be doing wrong and how to avoid such problems? It's a little bit complicated to figure out where the issue is, between my mount states and the way my tooling (vim-iced) is executing the tests.

practicalli-johnny07:03:18

As mentioned, when using a test tool that runs tests from the REPL state, if a test definition is changed (or renamed) then use the original name to remove the stale test definition (deftest) from the REPL state. *ns* refers to the current namespace, or explicitly define it

(ns-unmap *ns* 'deftestname)
Or use an external test runner, e.g. LambdaIsland/kaocha which will run tests from the saved files. Kaocha also has a watch option that runs tests when changes are saved to file. The fail fast option will stop at the first failing test, saving a run through all tests. https://cljdoc.org/d/lambdaisland/kaocha/1.0.861/doc/1-introduction I often use a REPL test runner for convenience with kaocha running in watch mode in the background

hiredman17:03:49

commenting out a test won't remove the existing definition in the repl

hiredman17:03:28

working with the repl is kind of like image based development, you are manipulating and building up some state in a running system, but commented out code does nothing when compiled, so it doesn't update the state

sheluchin17:03:36

@hiredman Hmm, I was under the impression that the ns-map of the test ns would be overwritten with the old symbols removed.

hiredman17:03:48

commented out code does nothing

sheluchin17:03:02

Comment followed by require?

hiredman17:03:43

require is another set of state, if a namespace is already loaded just calling require does nothing, unless you pass :reload or :reload-all, and in both cases it doesn't remove existing definitions in the namespace

sheluchin17:03:27

Okay, and how about refresh from tools.namespace?

hiredman17:03:10

I forget what refresh does, but tools.namespace will usually remove existing definitions before reloading

sheluchin17:03:38

> But first, it will unload (remove) the namespaces that changed to clear out any old definitions. This sounds like it should do the thing I need. But when I call it, it tells me it couldn't locate the file of the ns on the classpath.

hiredman17:03:08

but there are caveats with that, tools.namespace is very file oriented, and is basically trying to sync the state of namespaces in the repl with the state on disk

sheluchin17:03:27

Except I've run the tests successfully before. It must be some part of the setup/glue that I'm getting wrong on occasion.. or something 😕

sheluchin17:03:00

@hiredman what is your approach to this? Do you just avoid running tests in the REPL and instead run them as a one-off command using an alias?

ghadi17:03:47

many of us do not recommend auto-refreshing tools

3
sheluchin17:03:05

squint emoji

hiredman17:03:47

I avoid running tests in the repl, but that is because my tests are terrible large things with all kinds of fixtures and state requirements

sheluchin17:03:55

My projects are fairly small so far. I can write the code and write the tests, but the state management around the tests is giving me much trouble right now.

hiredman17:03:36

a useful thing with tests is to instead of commenting out the test entirely, comment out the body

👍 1
hiredman17:03:04

or I think some ides have pretty good test runners

sheluchin17:03:31

Interesting... because if you comment out the body, the reference to it in the ns-map is still there, but executes a commented out body. Is that right?

hiredman17:03:00

you could also call ns-unmap youself, or even fiddle with the metadata on the test var (that is how clojure.test recognizes tests to run)

hiredman17:03:22

clojure.test can also just run a selection of tests from a given namespace, but that functionality isn't always completely exposed to higher level tooling

sheluchin17:03:35

What do you recommend as the approach I take for now? Avoid running tests from the REPL except in limited cases?

hiredman17:03:58

dunno, do what you want to do, you might check to see if your editor has a disable test feature

sheluchin17:03:45

Disable test feature?

dorab17:03:23

You might find the following article helpful for managing state in tests https://stuartsierra.com/2016/05/19/fixtures-as-caches

hiredman17:03:05

yeah, I dunno (I use emacs and just switched to inf-clojure vs. running a repl in a shell buffer) but if you are using some ide it might have some feature where you say "disable this test" and it figures out the var and ns-umaps it for then comments it out

parens 1
dpsutton17:03:36

i have a local patch that disables the el-doc stuff so you don’t keep clobbering the *1 and other vars

dpsutton17:03:00

i imagine this drives you nuts

hiredman17:03:58

Ah interesting, I hadn't dug in, that explains a lot of weirdness

dpsutton17:03:32

yeah. eldoc will send forms to get metadata for that and that’s why you will get annoying duplicated prompts

dpsutton18:03:18

i also use the following to make the prompt smaller on each repl:

(require '[clojure.string :as str])
(require '[clojure.core.server :as server])
(require '[fipp.edn :as fipp])
(clojure.main/repl
 :prompt (fn [] (printf "%s=> " (peek (str/split (str *ns*) #"\."))))
 :read server/repl-read
 :print fipp/pprint)
because i can’t quite get inf-clojure to play nice with putting forms on the next line after the prompt

dpsutton19:03:35

simple PR and change if you wanted to implement it locally while waiting on the patch. It’s just adding a short-circuit in an and condition on whether to perform the eldoc: https://github.com/clojure-emacs/inf-clojure/pull/197

sheluchin17:03:21

Ah, I used vim with vim-iced and it doesn't appear to have such a thing. https://liquidz.github.io/vim-iced/#testing

dorab17:03:49

Does this work for you? Seems like you can run just one test individually in vim-iced. https://liquidz.github.io/vim-iced/vim-iced.html#%3AIcedTestUnderCursor

sheluchin17:03:00

Unfortunately - and I suppose this is easy enough to fix - I use some Fulcro test wrappers for my tests right now and vim-iced doesn't pick up on those.

sheluchin17:03:27

> switched to inf-clojure vs. running a repl in a shell buffer What does this mean?

hiredman17:03:52

emacs can act sort of like tmux, you can run a shell inside of it in a buffer (where buffers are sort of like tabs or something)

hiredman17:03:10

so I would do that and running a clojure repl from a shell in emacs

hiredman17:03:48

inf-clojure is a similar thing, but slightly nicer(debatable) and more automated

sheluchin17:03:13

Alright. Well, thanks for alerting me to some potential pitfalls around testing. I think it's going to require a few tussles with the machine before I get comfortable with it and find a workflow that works for me.

Surtroot Surtroot18:03:21

👋 Hello, team!

Martin Půda22:03:38

Hi, ClojureScript (Reagent) question, code below in thread: I used async channels to get result from event listener, but only [object Object] is displayed. How should I change this code to get content of file?

Martin Půda22:03:50

(defn read-file [file]
      (let [channel (chan)
            js-file-reader (doto (js/FileReader.)
                                 (.addEventListener
                                   "loadend"
                                   (fn [event] (go (>! channel (.decode (js/TextDecoder.)
                                                                        (js/Uint8Array. (-> event .-target .-result))))))))]
           (.readAsArrayBuffer js-file-reader file)
           (go (<! channel))))

(defn file-picker []
      [:input
       {:type      "file"
        :on-change (fn [event]
                       (-> js/document
                           (.getElementById "file")
                           (.-innerHTML)
                           (set! (read-file (-> event .-target .-files (aget 0))))))}])

(defn hello []
      [:div
       [file-picker]
       [:div#file]])

hiredman22:03:45

(go (<! channel)) is basically the same thing as channel

hiredman22:03:27

go returns a channel to which the results of the body are written to in the future

hiredman22:03:58

you are trying to turn an asynchronous operation (takes a callback) into a synchronous operation (blocks until complete) which you just cannot do with javascript

1
Martin Půda22:03:46

Ok, thank you :D