Fork me on GitHub
#beginners
<
2020-04-12
>
OrdoFlammae00:04:17

I'm having trouble with * throwing integer overflow exceptions when the numbers get too large; how do I fix this by forcing it to auto-promote numbers (I think that's the right terminology).

andy.fingerhut00:04:45

You can use *' instead of * if you want.

andy.fingerhut00:04:01

or make sure that at least one of the arguments is a BigInteger

OrdoFlammae00:04:45

OK, that works. Thanks.

Jim Newton10:04:23

In other programming languages python/scala/common-lisp/scheme I often name this function loop or recur.   those names are reserved in clojure 😞

Jim Newton10:04:07

I thought about naming it the misspelled recure

practicalli-johnny10:04:01

@jimka.issy loop and recur are functions in Clojure language, so you can just use them, eg. https://clojuredocs.org/clojure.core/loop

Jim Newton10:04:34

no, you can only use loop/recur for tail recursion, not for normal recursion. right?

Jim Newton10:04:51

or am I mistaken?

practicalli-johnny10:04:47

If you want to write a recursive function, then it simply calls itself until some termination condition is reached (eg. a boolean condition). You dont have to use loop and recur.

andy.fingerhut12:04:03

I just tried doing defn on a function named recur in Clojure. The defn gave no error, but you cannot call it with an expression like (recur 5) , since the Clojure compiler treats that like the built-in recur

Jim Newton10:04:27

indeed. my question is for whether there's some idiomatic name for such a local function. I generally use a local function because that function usually takes a different argument list than the parent function. i.e., and extra accumulator argument, or omits other arguments which are invariant.

Ben Sless11:04:35

you can name them -loop, -recur, iter, etc. any variation on iter would be more idiomatic because it won't overload the meaning of loop/recur (even if you decorate the name, the meaning might still be ambiguous)

manutter5100:04:12

There's also a fairly common idiom where you have a foo* fn that does the main work of the foo fn, i.e. foo just sets up the arguments to pass to foo* which handles the recursion or whatever.

didibus02:04:18

Clojure supports argument overload, that's what you should do

practicalli-johnny10:04:39

I name custom function definitions (`defn` ) based on its purpose, not based on the type of algorithm it uses. What you are describing sounds like how I use loop and recur (should I need to go to that low level of abstraction). A very simple example is here https://github.com/practicalli/four-clojure/blob/master/src/four_clojure/022_count_a_sequence.clj#L40 A more complex example, which as you see further on in the code can be greatly abstracted using clojure.core function https://github.com/practicalli/four-clojure/blob/master/src/four_clojure/053_longest_increasing_sub_seq.clj#L48

Jim Newton10:04:25

Not sure if we are missing each other. I use loop/recur when it is appropriate to do so. i.e., when i need a locally tail-recursive function which is called automatically. however, when I need to do something like loop/recur, but the function is not tail-recursive, I have to define a local function using letfn and then give the function a name. I can't use loop, nor recur for that function name.

Jim Newton10:04:02

i usually just name it f or tbnl to-be-named-later

Jim Newton10:04:42

loop/recur is an excellent set of names for this common pattern. The clojure authors did will in choosing that name.

Jim Newton10:04:02

the names are clear and evocative.

hindol10:04:12

Just name it loop_. f is also a common name. I tend to name such one off recursive functions step.

practicalli-johnny10:04:17

If I cant think of a meaningful name for a function suggests I havent thought about the problem enough. Using vauge names in your own custom code leads to technical debt... Its also a sign that I should try and abstract the code a bit more or refactor out sections into their own function. It may be useful if you share a code example

hindol11:04:29

@jr0cket I beg to differ. For local one off things, I think it is okay to not spend too much time on naming. Otherwise, anonymous functions wouldn't have existed. Except, with anonymous functions, you cannot really recur, so, you name it something. Just to be clear, I agree with what you said for def/defn .

practicalli-johnny11:04:37

I respectfully disagree, all code is important in communicating intent. If you are going to name something, then you should name it on is purpose, why is it in existence. If its not known why its doing what its doing, then why is code being written to do it? If its a generic algorithm, then avoid naming it at all (unless its complexe, then its probably not that generic and some more hammock time is probably in order)

hindol11:04:01

I mean, in the context of just enabling recursion. Like how loop/recur does not have a name. A for, a doseq do not have names. I mean the same way a function named for the sole purpose of recursion can be named step or f or whatever.

practicalli-johnny11:04:00

I am confused. The functions loop, recur, for, doseq all have names, otherwise you could not call them. They are also well established idioms. If custom code is written to do recursion then the assumption is that is does something different to those functions. If so, why is that the case. Is it an oversight in the design or is it a specific intention that has a purpose. If its a specific intent, then that is something additional that needs to be learnt about this code that should be understood before working with it. Adding a meaningful name, comment and documentation would be appropriate to make working with the code simpler.

hindol11:04:27

for has a name, but what that for is doing in a specific context does not have a name.

practicalli-johnny11:04:29

Sorry, very confused about that statement too. for is the name the function defined in clojure.core which is documented and has many examples of its use available (not all good, but there are some valuable examples too). Names are used with for to add context, otherwise what do you put in the [] ? The same goes for loop , what you put in the [] provides context as to why you are recuring? Only putting single character names that could mean anything make that custom code more complex. There seems no reason to hide any knowledge about why you are recuring when you can express it with meaningful names. If names are not obvious, then its a warning sign that you either need some hammock time or need to go and ask some questions.

Cameron12:04:19

for starters, the body of for is an anonymous inline function is what they're saying and

Jim Newton12:04:39

perhaps naming it recurring isn't so bad

practicalli-johnny12:04:03

what is it recurring?

Jim Newton12:04:59

I guess it is recurring what its arguments are.

(defn rte-trace
  "Given a compiled rte, find a sequence of types which satisfy the corresponding pattern."
  [rte]
  (letfn [(recurring [state path lineage]
            (cond
              (:accepting (rte state)) path
              (some #{state} lineage) false
              :else (some (fn [[type dst-state]]
                            (recurring dst-state (conj path type) (conj lineage state)))
                          (:transitions (rte state))))
            )]
    (recurring 0 [] ())))
as an example.

hindol12:04:13

One other area where I am often unable to name things is transducers. How do you even name a combination of maps and filters?

Cameron12:04:17

(I know the convo is starting to move on from this but) the idea that every function needs a name is to say there should never be a truly anonymous function; that we can create functions on the fly, but that they should be tied to an identifier somewhere to state intention (and, in its absence, surely a comment). I'd think hindol was using for and loop as a counter example where we all flat out use truly anonymous functions all the time; the body of,say, a for is an anonymous function, defined inline (by virtue of for being a macro, which for will then use to do its particular flavor of looping). Then, they were saying in cases where its ok to have a truly anonymous function, but where you still technically need a name not for identifying, but for the looping itself, then its ok to name it step or something like that since in that case, the name only serves for the looping itself, not for stating an intention (by definition). I personally agree with the idea that in plenty of cases, its ok for a function to truly be anonymous. When they should and shouldn't be is another conversation of course -- I just believe there are many cases where its ok. And note, you guys might agree more often than not as to when that is -- because in many cases where something is 'anonymous', it might be not what someone is thinking of anonymous, because its really a case where its overall intention / naming is inherited from a close by, 'surrounding' name -- such as the example

(defn grades [school-db] 
 (for [student (get school-db :students)]
   (get student :grade))) 
We didn't name our looping-step in for, but we can sort of bubble up to the overall function definition (`grades`) to know that the body of the for is surely, basically, grade. One might also argue that in a case where you're naming a looping step for recursion, you might as well name it something good since you're already naming it. I personally won't; I like step in many of those aforementioned cases where we've agreed we know the intention, but I could at least see the argument

👍 4
hindol13:04:16

Thank you for explaining it so clearly. I was not finding the right words. Indeed my argument was name when it makes sense, and sometimes it just doesn't.

Cameron12:04:15

Of course, another example off the top of my head I suppose; this is an example where the looping step is right there with the looping construct. I can think of an example as well where the looping step is for some reason is much further from the loop, or there are many different looping steps -- without an example, its hard to say whether this would practically happen a lot, or whether or not it would already inherently be a code smell, but regardless in a case like that I could see a better name becoming relevant. But I suppose again our point was not that you always would name a looping step or the like, but that you wouldn't always not do so

Aron12:04:24

Hi, I could search for this somehow but it's really hard. I understand the keyword and the lambda but I don't understand the .. and the % I know that -target - value is to get ev.target.value and I think % the argument for the lambda, but what is .. ? 12 :onChange #(set-asset-type (.. % -target -value))

andy.fingerhut12:04:46

There is some documentation on .. on this page documenting Java interop: https://clojure.org/reference/java_interop

andy.fingerhut12:04:13

Any expression with #( ) surrounding it in Clojure is a shorthand for an anonymous function. Inside of there, occurrences of % or %1 refer to the first argument of this anonymous function, %2 to the second argument, etc.

👍 4
andy.fingerhut12:04:52

For example, #(inc %) is equivalent in the anonymous function created to (fn [x] (inc x))

andy.fingerhut12:04:30

The #(inc %) syntax is just a bit shorter. #(inc %) is not a great example, because it is equivalent to inc

andy.fingerhut12:04:51

Slightly better example is #(+ % 5) , which is equivalent to (fn [x] (+ x 5))

hindol13:04:50

.. is like the thread first macro -> but only for JS and Java (interop).

hindol13:04:08

There is also the doto macro, which does function chaining common in OOP languages. It injects the same object into the subsequent step, but possibly mutated.

hindol13:04:30

@U0CMVHBL2 I think you missed a # in the last example.

andy.fingerhut13:04:12

sorry, yes, I did. Will try editing it after your message, in case anyone reads your pointing that out after I make it correct.

Aron13:04:08

what is "thread first"?

Aron13:04:01

I am reading the docs. I probably read it before, but unless I use the knowledge for something, it's like I never read it. : (

hindol13:04:39

Thread first means the result of the previous step becomes the first argument of the function that makes the next step.

hindol13:04:19

I am on phone, so forgive any typo but here is an example, (-> x f (g y)) Here, x is injected as the first argument to f, and the result of that is injected as the first argument to g. Equivalent to, (g (f x) y). Does that make sense?

hindol13:04:48

Note that g has another argument y, but y becomes the second argument.

Aron14:04:12

Yes, I think I understand, thanks

Cameron13:04:14

So indeed % is the argument for the lambda. As for what .. is, its easier to show why it exists First, for my example I'm going to rename % to ev, because that's what it really is in this particular case. If you want ev.target, then in Clojurescript that's going to be

(.-target ev)  
(you wouldn't write (.target ev) because that would translate to a ev.target() in javascript) And if you wanted ev.target.value, first you're going to need to get ev.target, and once you have that call .value on the result of that, so
(.- value (.- target ev)
.. is just a nice shorthand for this sort of nested javascript interop, that also looks similar to what you might be expecting in a vanilla language (.. ev -target -value) (which is sort of like saying 'get -target from ev, and then get value from that result'). You'll notice it also lets you drop the . from target and value, since it can infer that by knowing everything inside of it is interop, and is either .-field or (.method-call blah) If you wanted date.month.first_letter() you might have
(.. date -month (first-letter))   
;; or 
(.. date -month first-letter) 

Aron13:04:55

Thanks, very comprehensive!

kenj18:04:29

I wish .. would have been added to the CLJS cheat sheet. Here I’ve been putzing around with oget and oset!.

hiredman18:04:54

Wait till you see ->

hiredman18:04:27

.. predates the -> macro, and is basically never used in favor of it

kenj19:04:43

So the prefered way now is (-> ev .-target .-value) over (.. ev -target -value)?

kenj19:04:03

This is so much nicer (-> js/document (.getElementById "root") .-innerHTML (set! "yay"))

hindol19:04:12

.. is still convenient as we can avoid the extra dots while doing interop. I thought that was the purpose.

didibus20:04:45

There's no real purpose of one over the other. -> is more general, works in more situations, and happens to cover the use case that .. also handles. Which is why I and some others prefer to use it over .. mostly because you can be consistent since it can apply to interop as well as non interop code.

Cameron20:04:35

yeah I often just end up with -> , I don't mind an extra ., and I get to use whatever with it

didibus20:04:36

But .. is still there, and if you enjoy not needing to put a .feel free to use it if you prefer it's ergonomics

didibus20:04:50

Oh, well I mean in Clojure, on ClojureScript they chose not to have ..in favour of -> which makes sense I guess, but that's also just a choice that the ClojureScript authors made

Vincent Cantin20:04:15

Why doesn’t contains? support lists?

Timur Latypoff20:04:37

I've looked up in the docs, it says that contains? checks if a key is present. For the list, keys are numeric indices, so you can check for their presence (but probably (< idx (count lst)) is better)

👍 8
Vincent Cantin20:04:36

Thank you. Yeah, I should have checked clojuredocs before asking.

phronmophobic20:04:39

I don’t think contains? works on lists, although it does work on vectors. I believe the reason that contains? doesn’t work on all sequences is that contains? wants to be able to promise constant time and that’s not possible with lists

👍 8
Timur Latypoff20:04:14

@U7RJTCH6J just checked in repl — it actually works, but checks for index keys:

(contains? [:a :b :c] :a)
=> false
(contains? [:a :b :c] 0)
=> true
(contains? [:a :b :c] 3)
=> false

phronmophobic20:04:26

those are vectors

👍 4
phronmophobic20:04:34

lists/vectors/arrays are often used interchangeably in conversation, but it has a subtle distinction here ¯\(ツ)

Vincent Cantin20:04:32

is there another function which I can use to look for an element in a sequence or list ?

Vincent Cantin20:04:21

ok, I found some which works in my use case.

Cameron20:04:42

yea, I think contains always trips everyone up because you're expecting it to be a function checking that a collection ..er, contains a value, but its checking that it contains a key. And for that reason, it doesn't work on lists because they are not considered keyed sequences

👍 4
Vincent Cantin20:04:27

I fell in the trap after 2 years of Clojure 🙂

phronmophobic20:04:01

I thought one of the main reasons contains? doesn’t work on lists is to guarantee: > ‘contains?’ operates in constant or logarithmic time;

phronmophobic20:04:49

it also sort of explains the behavior with vectors and maps

phronmophobic20:04:10

and also explains why contains? doesn’t support some other sequence types

hindol20:04:50

@vincent.cantin some checks for logical truthiness and is a little quirky. For an arbitrary element, (some #{<element>} coll) works... except nil and false. You need (some false? [1 false 3]) and (some nil? [1 nil 3]).

Vincent Cantin20:04:29

I solved my problem, thank you.

Cameron20:04:11

Well, I'm pulling the 'not considered keyed sequences' from one pair of docs (I almost added an additional message of 'I don't know what that itself inevitably means; lists, for instance, are clearly ordered, and can clearly then be mentally assigned indexes, and from that keys. I don't know if it has to do with their lower level implementation as (cons x (cons y ..)) [or rather more specifically, head = value, tail = another sequence if I remember] over something that itself is indexed' but then I stopped there, as I don't want to say anything inaccurate about their implementation without looking into it again further as I'm a bit fuzzy here) so if there's something written implying something else, I would not be able to argue one as being more true over the other; we'd be at a document he-said-she-said, unless one pair of docs has some tie breaker. I think I got the 'lists are not considered keyed sequences' from clojuredocs, if that helps

Alex Miller (Clojure team)21:04:41

it has to with indexed lookup and performance

Alex Miller (Clojure team)21:04:57

keyed/indexed lookup == better than linear time

Cameron21:04:30

ah so it really is a 'both' answer 👀

Cameron21:04:56

always 😄

Adrian Smith23:04:04

A question to long time clojurians what kinds of structural editing (or editing tips in general) are you using?

Cameron23:04:49

emacs' lispy 👀 hands down, although we were just talking on #emacs on how it has several sharp edges

seancorfield23:04:52

@sfyire I use both Parinfer and Paredit (with Atom), but the most common stuff I use is slurp and barf to grow and shrink expressions (by moving parens around).

seancorfield23:04:34

Not everyone likes Parinfer -- it does take a bit of getting used to but I really like it for simple code editing: it moves parens around as you indent/undent code so that the structure follows the layout.

Adrian Smith23:04:22

The replies are a lot more varied than I thought :o anyone else got a different approach?