Fork me on GitHub
#beginners
<
2020-09-27
>
mitesh06:09:56

When working with looping over strings, should I be using first and rest heavily or use a [Buffered]Reader? Does using a reader and passing it around means that we're now in mutable land? I'm trying to parse html something like Selmer / Mustache. Selmer is using a reader and passing it around, but I'm wondering if there are other ways to do something like that?

sova14:09:26

not a pro but, I don't think there is a significant performance benefit for the trouble involved -- not sure how it's implemented under the hood but i reckon it's probably close to optimal. Check out how other projects do it -- hiccup, selmer as you mentioned, maybe enlive by cgrand can point you in the best direction.

noisesmith17:09:24

using strings via the seq api is a bad idea, the slowdown is significant

noisesmith17:09:41

if you are just reading you can use indexing and subs, and just keep track of indexes

mitesh08:09:18

Thanks will try the subs approach, I was mainly concerned if passing around a reader is like an anti pattern or not. I've seen it being used at multiple places so I guess it's fine.

jimka.issy09:09:57

• The fn macro distinguishes between the two syntaxes: (fn name? [params*] exprs*) vs (fn name? ([params*] exprs*) +).

jimka.issy09:09:37

What is the correct way in closure to distinguish between [something ...] and (something ...)

schmee14:09:52

user=> (vector? '(1 2 3))
false

user=> (list? [1 2 3])
false

schmee14:09:10

but most code should not care about whether something is a list or a vector

jimka.issy09:09:54

especially when writing macros ?

jimka.issy10:09:34

When I look at https://github.com/clojure/clojure/blob/clojure-1.10.1/src/clj/clojure/core.clj#L4513 it seems, on line 4525 that the function vector? is used to distinguish these two cases.

jimka.issy10:09:39

This would seem to mean that I can use vector in my code or any structure that implements IPersistentVector. Is that true?

andy.fingerhut14:09:05

True. There are very few data structures built into Clojure that implement IPersistentVector, and none that I am aware of that can be written as literal expressions in code other than clojure.lang.PersistentVector. The other I know of you can create a vector of Java primitive values using (vector-of :byte 1 2 3), but there is no literal syntax for that, so not useful in macros.

jimka.issy10:09:13

Is there a naming convention for functions which I intend to only be used within a file or from within the implementation of a group of functions? In common lisp it is customary to name such functions starting with a %. other namespaces which import this namespace can use such a function, but the naming convention implies they shouldn't.

schmee14:09:42

yes, prefix the function with -, i.e (defn -foo [] ...). note that this is just a convention and is not recognized by the compiler in any way

andy.fingerhut14:09:00

There is also the notion of a private function, written (defn- private-foo ...), or (defn ^:private private-foo ...). Those can be called from outside of the namespace where they are defined, e.g. useful in namespaces containing test code for such functions, but you must use syntax like #'some.namspace/private-foo to refer to them in namespaces other than the one where they are defined.

jimka.issy10:09:51

If I make such a function a local function, than I cannot write test cases for it, so it is better for development purposes to make it a top level function.

ddouglass12:09:13

When you say “local” do you mean “defn-“? You can still write tests for those fn’s, you just have to call the var instead of the name:

(#’foo/my-private-fn)

jr0cket17:09:33

I would recommend test for public functions, rather than a private function that contains code shared between multiple public functions. Testing of shared private functions is likely to be more brittle and at a lower level of abstraction. Directly testing private functions is redundant, as these private functions are tested through all the public functions that use them and at a less brittle level of abstraction.

jimka.issy18:09:12

Not sure that I agree. I write tests for my private functions, making testing the public functions easier. Especially when I write the private functions first, and write the public functions when I get the private ones all working.

jr0cket18:09:55

It is of course your right to disagree and take a different approach. I've rarely seem to use a distinction of public and private functions, the distinction seems largely irellevant, especially compared to the approach I used to take with Java and Scala. I usually have functions and helper functions, occasionally spinning off the very generic helper functions into their own namespace (which would then require refactoring the tests if they had them). I tend to write tests when I have evolved a sufficient understanding of the constraints around the 'public' functions which form the API for each namespace. The design of this API is usually more important to the overall design and should be supported by valuable tests. I tend not to write tests for everything, especially if it is tested elsewhere. When exploring a design I may write funcitons that become helper functions, used by others. However, at that stage of uncertanty I typically would not write unit tests (or not many) as the design would not be concrete enough to put into unit tests that would be stable enough to feel useful. I have found that exploring design choices in the REPL and codifying data models in spec provide an effective way to evolve my understanding of the challenge at hand. An of course just spending a good detail of time away from the computer thinking about the problem in detail (hamock time). Once specific design descisons are made then they can also be codified in test, using clojure.spec generative testing to provide a more diverse set of test data Naturally I do not have a shared set of experiences with all developers and of course you should take an approach that you find effective.

jimka.issy11:09:13

Is there a word clojure programmers use to describe the type of destructuring which happens with a let or fn like this: ?

(let [[[a b] [c [d]]]] some-data] ...)
In Common Lisp it is referred to as destructuring but that name unfortunately is very long, resulting in very long function names such as destructuring-bind which sometimes gets abbreviated in literature as dsb which is very terse.

sova14:09:57

unaware of an extant term, might I suggest "pickpocketing" ... at least I can sort of grok it right away

schmee14:09:19

it is called “destructuring” in clojure as well: https://clojure.org/guides/destructuring

jr0cket17:09:05

I probably have misunderstood, but why would a function be written just to destructure a value? I would name the function on the logic or purpose I needed destructured values for. If destructure was part of the name, would also the type of destructuring (positional, associative) be used? This seems a very low level and brittle approach. What if the function changed it's arguments and didn't destructure, or changed the type of destructuring, the name would no longer be relevant.

jimka.issy13:09:45

Can someone please remind me how to figure out which dispatch value a defmulti is returning. My program is triggering some error in the dispatch that I cannot figure out. If I have the values from the call site, I'd like to know what the code in defmulti returned, or whether it encountered an error while trying to figure that out?

chuck.cassel13:09:16

You can use get-method with the method and dispatch value to see which dispatch fn would get called

chuck.cassel13:09:32

not sure if that's what you're looking for exactly

jimka.issy13:09:03

yes but how can I get the dispatch value?

jimka.issy13:09:27

There is some function, I seem to recall, for debugging purpose, which will give you back the dispatch value.

sova14:09:56

Is it prefers you're talking about? https://clojuredocs.org/clojure.core/prefers that's the closest something I could locate right now

andy.fingerhut14:09:30

If you defined the multimethod yourself, then you know what the dispatch function is, and can call it on any object you want.

andy.fingerhut14:09:07

If you are asking "for an arbitrary multimethod, is there a way I can call its dispatch function, even if I do not know what that happens to be?", then I don't know of any Clojure function included in core that can do that, but there is a Java field named dispatchFn in the object with class clojure.lang.MultiFn that is public, and so you can use Java interop to get it.

andy.fingerhut14:09:34

user=> (defn foo [x] (inc x))
#'user/foo
user=> (defmulti mymm "blarg" foo)
#'user/mymm
user=> mymm
#object[clojure.lang.MultiFn 0x68ad99fe "[email protected]"]
user=> (.dispatchFn mymm)
#object[user$foo 0x1755e85b "[email protected]"]
user=> foo
#object[user$foo 0x1755e85b "[email protected]"]
user=> (identical? foo (.dispatchFn mymm))
true
user=> ((.dispatchFn mymm) 5)
6

sova14:09:48

!discovery, actually it's just called methods https://clojuredocs.org/clojure.core/methods

(methods multifn)
Given a multimethod, returns a map of dispatch values -> dispatch fns

jimka.issy19:09:08

@ I recall that there's an easier way. What you've done is factor out the dispatch function from the defmulti. There's a way, I just have forgotten what it is, to ask the system to call the dispatch function and give you the value back, without actually dispatching.

chuck.cassel20:09:46

unless we're all talking past each other using dispatch value/function to mean different things, as best I can tell from https://github.com/clojure/clojure/blob/clojure-1.10.1/src/jvm/clojure/lang/MultiFn.java at least, the return value of dispatchFn is always immediately used to lookup the associated fn, it isn't stored anywhere besides the method cache or exposed elsewhere.

jimka.issy20:09:52

Oh, I see. I thought there was a more clojurey way

(defmulti xyzzy (fn [x y] 42))
I see I can evaluate the following ((.dispatchFn xyzzy) 1 2) to get back 42.

jimka.issy20:09:27

It would be nice if that were documented with defmulti

andy.fingerhut20:09:17

That looks correct to me. I wouldn't be surprised if it might be somewhat different for ClojureScript, but http://ClojureDocs.org tends to be focused on Clojure/JVM anyway.

jimka.issy06:09:59

great so I'm now a contributor, not just a user. 🙂

zhuxun219:09:33

Is there a way to escape the spaces in main-opts in deps.edn? They seem to always break the argument, even when they are in the quotes... For example :main-opts ["--param={:a 1}"] is always intepreted as ["--param={:a" "1}"]

jason35820:09:34

"--param={:a,1}" should work

zhuxun221:09:02

@ Thanks! What if the param requires a space?

jason35821:09:23

I don't know. It looks like your problem is with shell, splitting at spaces, not with edn. So I'd suggest trying some standard shell escaping approaches (like \\ or "--param='{:a 1}'" )

zhuxun201:09:42

@ Unfortunately, neither \\ nor "--param='{:a 1}'" worked for main-opts

zhuxun219:09:22

Has there been any fixes? Are they any work-arounds?

jr0cket20:09:36

Commas are whitespace in Clojure, so are used instead of spaces that would otherwise need to be managed on the command line. For example, https://github.com/practicalli/clojure-deps-edn/blob/live/deps.edn#L314

zhuxun221:09:47

@jr0cket What if the param requires a space? Here is an example I ran into: --error-format="::error file={{file}}::{{message}}"

jr0cket23:09:58

I believe strings are surrounded with single quotes. Does that help?

zhuxun201:09:14

@jr0cket Unfortunately, no. :main-opts ["-m" "antq.core" "--error-format='::error file={{file}}::{{message}}'"] is interpreted as ["-m" "antq.core" "--error-format='::error" "file={{file}}::{{message}}'"]

jr0cket09:09:25

I was thinking :main-opts ["-m" "antq.core" '"--error-format=::error file={{file}}::{{message}}"'] I will try find time to look at this option for antq, I’m just using the default and piping it into an org file to give an interactive table

zhuxun215:09:02

@jr0cket Single quotes in edn do not "surround" they are syntactic sugar for (quote ...). If you meant :main-opts ["-m" "antq.core" "'--error-format=::error file={{file}}::{{message}}'"] then no, it does not work...

jr0cket15:09:45

I believe single quotes should work on the command line or in a script, but haven't had chance to test

zhuxun216:09:45

@jr0cket Yes, it works in command line, but I was wondering if I could include it in the :main-opts. I mean for antq I can probably just make a Makefile or bash file and include the param, but I was wondering if :main-opts itself supports spaces or not. It seems not ...

zhuxun221:09:43

In this case, --error-format is an arbitrary string that could have spaces them