Fork me on GitHub
#beginners
<
2020-04-02
>
Jim Newton07:04:02

Good morning everyone.. I'm wondering about mapcat. I was a bit surprised that it returns a list rather than respecting the type of its input. For example, I expected that if I mapcat a sequence of sets, the result would be a set, but alas it is a list (mapcat identity #{ #{1} #{2} #{2 1} }) returns (`1 2 2 1)` rather than #{1 2} . Should I construct my own application specific flatmap function, or does such a function already exists. Clearly I can do what I want using reduce and union .

lispyclouds07:04:37

mapcat actually returns a LazySeq, whose printed representation happens to be a list. You can check the type with (type (mapcat reverse [[3 2 1 0] [6 5 4] [9 8 7]]))

lispyclouds07:04:12

its upto you to consume this the way that's correct

Jim Newton07:04:37

Does there already exist a fold variant of reduce , one for which I can give the initial value. I.e., reduce is documented to simply call the given function on 0 arguments if no argument is given. What I'd like to do is give a seed value, then always call the given function with two arguments. if the list has length 0, the return the seed value. if the list has 1 or more values then call the function n times, each time with the accumulated value and the next value.

Jim Newton07:04:28

That's a regular implementation of fold for which the types of the input and output value of the function are not the same. Of course i can implement that myself, just don't want to implement what's already there.

Jim Newton07:04:07

hmmm. trying to implement fold. I realized I don't know how to do it. How can I iterative recursively loop/recure over an arbitrary sequence and detect when the sequence is finished?

Jim Newton07:04:47

should I used first/rest and detect when rest returns false ?

Jim Newton07:04:41

(defn foldl [zero f items]
  (loop [acc zero
         items items]
    (if items
      (recur (f acc (first items))
             (rest items))
      acc)))

andy.fingerhut07:04:25

Built-in reduce has 2 arities, one of which takes an initial value.

Jim Newton07:04:09

yes but I also so a library of fold related functions which seem to be related to parallelism.

andy.fingerhut07:04:56

Earlier you said you wanted this: "What I'd like to do is give a seed value, then always call the given function with two arguments. if the list has length 0, the return the seed value. if the list has 1 or more values then call the function n times, each time with the accumulated value and the next value"

andy.fingerhut07:04:08

reduce can do exactly that, and documents that it can do so.

andy.fingerhut07:04:27

Sorry, I may be behind, catching up from what you said in earlier messages.

Jim Newton07:04:29

ahhh, yes indeed. thanks andy. you're right

andy.fingerhut07:04:05

It would probably be slightly easier to understand if the doc string for reduce described the two arities in separate paragraphs, rather than one longer one.

andy.fingerhut07:04:05

(source reduce) is nice if you want to see how Rich chose to implement it, except that in this case it is calls to Java methods.

andy.fingerhut07:04:24

So not the best if you want to see a good Clojure-y way to implement it.

andy.fingerhut07:04:33

But implemented that way for performance, I am sure.

Jim Newton07:04:59

Yes, when I re-read the paragraph it indeed explains it. But you're right, having two paragraphs to describe different use models would be easier for the first time reader.

didibus08:04:28

I think the reason mapcat and other things tend to return an ISeq is because the collections are immutable, and the functions are lazy, like mapcat is.

didibus08:04:52

So you can't construct a new set with the elements, because sets aren't lazy like that.

didibus08:04:04

And since things are immutable, functions don't modify them in-place, so since you're having to copy into a new structure anyways, it opens the door for that new structure to be of a different type then the input one

didibus08:04:56

So.on fact, all these functions are known as the Sequence functions, because they'll all return a sequence, and that's mostly because they are all lazy, so they need to returns a type that supports lazy semantics

didibus08:04:13

If you do care about the return type, and you don't care about things being lazy, you can use the transducer variant instead. With those, you get to pick the collection into which you want the result to be put in

☝️ 4
didibus08:04:07

(into []
  (mapcat #(identity [:result %]))
  [1 2 3 4])

☝️ 4
teodorlu10:04:04

Transducers let you "pick the target type" while limiting memory overhead. https://youtu.be/6mTbuzafcII

skoude08:04:23

Is there an easy way to convert from kebab-case to snake-case in jdbc? My api is using kebab-case and I’m using postgresql with snake-case

skoude08:04:26

Thanks, I found it also, but was thinking are there other solutions, but will try that one.. :thumbsup:

teodorlu11:04:47

If you have further questions, I recommend posting in #sql 🙂

skoude10:04:01

Thanks @U3X7174KS, will check that out later on. Just used clj-commons camel-snake-kebab library

👍 4
chetchan08:04:13

Hi I am a newbie at Clj. I am running my repl in IntelliJ Idea, and I want to load my dev config (dev.edn) and fully start my program (which reads from JMS queues) However after I start my repl and I send (dev) I don’t see that my app starts running. My question is is there a way I can fully start my app using config in my dev.edn than just run in repl? Any links that show me how to use config etc would be helpful too…Thank you

Jim Newton09:04:21

I found the unify function in https://clojuredocs.org/clojure.core.logic/unify, what is it? It's not marked as dont-use-me, it just has blank documention.

Jim Newton09:04:55

Thanks delaguardo

Jim Newton09:04:54

I'm looking for a map-like version of every which takes multiple sequence arguments and an n-ary function: i.e. (every < '(1 2 3 4) '(10 20 30 40)) ? I didn't see one in the every/every? variants. (every f '(1 2 3) '(10 20 30) '(100 200 300) '(1000 2000 3000)) would expect f to be a 4-ary function.

andy.fingerhut09:04:12

How about this? (every? identity (map < '(1 2 3 4) '(10 20 30 40)))

Jim Newton11:04:42

Yes, i'm not used to creating lists to throw away, does map create a lazy list, so that < is only called as many times as needed?

Jim Newton11:04:58

if so, it's a clever solution.

Jim Newton11:04:38

The other idea was to use zip although perhaps that's a question for another time, as I don't see the zip function I was looking for. I.e., a function which takes n many sequences each of length, and returns m many sequences of n items each. And does so lazily.

andy.fingerhut13:04:41

Do you mean (map vector coll1 coll2 ... colln) ?

Jim Newton14:04:15

perhaps that's the same. great, thanks.

Jim Newton14:04:41

One thing i noticed from using Common Lisp is that I never needed zip because so many functions behave like map, taking an n-ary function followed by n equi-length lists. Then when I started looking at Scala, i really missed this. you allways need to zip things and then distinguish between a n-ary function and a unary function taking an n-tuple.

Jim Newton14:04:54

I also noticed that my C++ friends use zip alot.

Jim Newton14:04:31

I still have a fear of zip. :face_with_rolling_eyes:

andy.fingerhut14:04:18

Clojure doesn't have all of the keyword options on whatever large fraction of functions that Common Lisp does. It tends to be a little bit more Scheme-like (perhaps - I haven't looked at Scheme libraries in decades) in the sense that it has fewer options, but a good set of built-ins that often work well together, once you figure out how.

andy.fingerhut14:04:17

The Clojure cheat sheet can be a good index to Clojure built-in functions, in that it emphasizes grouping together related functions and macros: https://jafingerhut.github.io

andy.fingerhut14:04:43

But it won't figure out common combinations of functions for you, or anything like that.

Jim Newton12:04:32

I'm looking at the sort function https://clojuredocs.org/clojure.core/sort. for the 2-ary form, it says I need a comparitor and that

comparator must implement
java.util.Comparator
I don't know Java. Is there a way to explain what this means to a non-java person?

Jim Newton12:04:10

Is it just a function which will always return 1, 0 or -1 on any input encountered during the sort?

Jim Newton12:04:18

or is it more complicated than that?

jaihindhreddy12:04:49

I think the Clojure website does a great job at that

manutter5112:04:02

Yeah, that’s probably your best but, and also if you type (javadoc java.util.Comparator) in the REPL the Java documentation will open up in your browser.

manutter5112:04:16

Might be helpful, might not, but it’s good to know.

🙂 4
teodorlu12:04:44

The compare function is a nice example.

Jim Newton12:04:40

I looked over https://clojure.org/guides/comparators, It seems to me that as long as my function (fn [a b] ...) accepts any value which will be encountered during the sort, and returns 1, 0, or -1 enforcing a total ordering, then that is good enough without having to understand how to implement a java interface. Am I being naive?

hindol13:04:42

Short answer, yes. For simple things, compare is enough. For more complex cases, there is comparator.

hindol13:04:54

compare has knowledge of how to compare common types. comparator let's you implement it however you want.

manutter5112:04:23

Looks like it ought to work

manutter5112:04:23

The “must implement” language sounds like you need to implement the actual interface, but there’s no sign of that in the guide. I’d try it with a simple fn, and if that works, build the full-blown comparator.

Gabriel Saliev12:04:30

Hello, I'm having hard time understanding how does the double evaluation happen in this piece of code from clojure for the brave and true: https://gist.github.com/gabbigum/e033175ed4773a03156b240fddd4fa26 , can somebody explain it?

sova-soars-the-sora13:04:23

@40441590 ~to-try gets replaced with the input

sova-soars-the-sora13:04:32

both places ~to-try exists

sova-soars-the-sora13:04:26

(defmacro report
  [to-try]
  `(if ~to-try
     (println (quote ~to-try) "was successful:" ~to-try) ;1st instance that gets replaced by input in ~to-try
     (println (quote ~to-try) "was not successful:" ~to-try))) ;2nd instance replaced by input in ~to-try
When we invoke the macro called report, it will use the first argument in two places (both indicated by ~to-try)

sova-soars-the-sora13:04:08

The macro does NOT evaluate the if statement.

sova-soars-the-sora13:04:33

because of the front quote `(if ~to-try

sova-soars-the-sora13:04:34

macro means meta code, or code describing other code, so it's not exactly evaluate-able until it becomes actual clojure (and is not just macro language)

Gabriel Saliev13:04:58

Thank you very much!

sova-soars-the-sora13:04:17

you are very welcome, you chose an excellent question

teodorlu13:04:41

@jimka.issy sort itself uses the compare function I posted the docs for (above), which is a plain function. So a plain function should work 🙂 See (source sort)

❤️ 4
👍 4
Michael J Dorian14:04:39

Does it make sense to store a collection of agents inside an atom? I'm thinking of changing my huge-atom-of-maps to a huge-atom-of-agents (for a simulation game) and I'm wondering if that's a good idea. In particular I'm trying to speed the game up, and while I don't expect agents to be "faster" than an atom, I'm thinking it might allow my code to be more flexible in so far as allowing the game objects to alter each others state or execute operations asynchronously.

Michael J Dorian14:04:40

I glanced at the iconic ant sim to see how it's done there but it actually didn't run on my computer, so I wasn't able to quite tease apart how it worked. For one thing, I don't see a way to change the number of ants after the simulation in in progress (could have missed it though)

Jim Newton14:04:32

Is there a way to tell the pretty printer, within cider, to print quoted lists as '(1 2 3) rather than (quote (1 2 3)) similar for quoted anything?

Alex Miller (Clojure team)14:04:17

there is support in the clojure printer to do this

Jim Newton14:04:03

How does it work, is it a global setting, or do I have to pass a flag every time i call print?

Alex Miller (Clojure team)14:04:30

I don't remember, trying to find an example. It's obscure enough that I have to look it up every time. :)

Alex Miller (Clojure team)14:04:05

what exactly are you doing where you're seeing that?

Alex Miller (Clojure team)14:04:28

pprint's default behavior actually does this already

Alex Miller (Clojure team)14:04:31

user=> (println ''(1 2 3))
(quote (1 2 3))
nil
user=> (pprint ''(1 2 3))
'(1 2 3)
nil

Jim Newton14:04:13

it is the output of clojure.test in the emacs clojure repl. I'm seeing messages like the following:

FAIL in (t-canonicalize-pattern-once) (form-init4386036104585295471.clj:140)
canonicalize-pattern-once
not or 2
expected: (= (quote (:and (:not :clojure-rte.core-test/Cat) (:not :clojure-rte.core-test/Lion))) (canonicalize-pattern-once (quote (:not (:or :clojure-rte.core-test/Lion :clojure-rte.core-test/Cat)))))
  actual: (not (= (:and (:not :clojure-rte.core-test/Cat) (:not :clojure-rte.core-test/Lion)) (:and (:not :clojure-rte.core-test/Lion) (:not :clojure-rte.core-test/Cat))))

Alex Miller (Clojure team)15:04:24

that probably is challenging to change as it's coming from clojure.test

Jim Newton14:04:51

> reading https://clojuredocs.org/clojure.core/contains_q the claim is that there is no basic function to determine whether a given element is in a collection. A poster suggests the following to detmine whether 1 is an element of the sequence. Not sure why the quote is missing in the example, perhaps just a typo > > is this good advise? Seems weird to me that no such function exist. Of course I can write my own using loop/recur ... or some

(some #(= 1 %) (1 2 3))

andy.fingerhut15:04:24

some is a good option for this purpose.

andy.fingerhut15:04:38

The docs on http://ClojureDocs.org are community-provided (and community-editable with a free account!), so you can fix mistakes if you find them and are so inclined. They are not edited by the core team, or at least not as a matter of policy (they might on occasion do so, perhaps, unknown to me).

andy.fingerhut15:04:33

Ah, I see the missing quote in an example you mention is in a comment, not in one of the examples. You can edit the examples to add a correct example, but you cannot edit the comments of others, AFAIK

Alex Miller (Clojure team)15:04:50

there is intentionally no "linear search in a sequential collection" function (`some` is probably the closest) - in general the more Clojurey advice would be to put that data in a hash-indexed collection (map/set) and look it up

Jim Newton15:04:37

I'm reducing a symbolic expressions such as (op a b c d e a b c d e ) and if I find the identity element for the given op, I want to remove it.

OrdoFlammae15:04:58

Run a (filter) first then?

Jim Newton15:04:50

If I use remove, how do I know whether it got removed if I don't know whether it was there in the first place? That's fine. I happy with some.

hindol15:04:27

Do a partition-by. EDIT: That's not perfect. Let me look for something better.

andy.fingerhut15:04:32

remove won't tell you that directly. You could do = on return value vs. input value, but that is linear time.

Jim Newton15:04:15

yes remove followed by = is linear in time and space, and find would be linear in time and constant in space

andy.fingerhut15:04:20

I am not aware of any built-in function that behaves like remove but also returns whether it removed any elements. Not difficult to write, of course.

Jim Newton15:04:23

speaking of partition-by I found group-by for another purpose entirely. Is there a better way to group-by a Boolean predicate, effectively splitting the sequence into a good and bad collection?

hindol15:04:42

Maybe a partition-by and then concatenating the odd groups and the even groups together.

hindol15:04:11

group-by is better actually.

hindol15:04:18

Like Andy mentioned.

Darin Douglass15:04:13

can use destructuring to get you most of the way there:

(let [{matched true missing false} (group-by pred collection)]
  ;; do something)

andy.fingerhut15:04:42

Do you actually need a separate true/false return value indicating whether it removed any elements? Does your calling code actually care for some reason?

andy.fingerhut15:04:18

group-by with a function that returns true or false is a good way to split into good/bad. I am not aware of a built-in that does the job any better.

Jim Newton15:04:09

scala has a function, i forget the name, friend of group-by which takes a collection and a Boolean predicte and returns a pair of collections, for the true and false items.

Jim Newton15:04:15

again, it is easy to write.

andy.fingerhut15:04:52

I guess I am having a hard time thinking of what is "wrong" with group-by for that purpose. You don't want the overhead of creating a 2-element map?

andy.fingerhut15:04:14

I don't see how you could possibly get away from that in the JVM

Jim Newton15:04:16

it would presumably just return a tuple.

andy.fingerhut15:04:34

a 2-element map in Clojure is often an array of 4 elements under the hood.

andy.fingerhut15:04:11

well, a 4-element array, plus another object that points at it.

Alex Miller (Clojure team)15:04:04

would split-with help here?

Alex Miller (Clojure team)15:04:31

user=> (split-with odd? [1 3 5 6 7 9])
[(1 3 5) (6 7 9)]

Alex Miller (Clojure team)15:04:49

I guess not, you want more group-by behavior

Alex Miller (Clojure team)15:04:58

I often have written something like

Jim Newton15:04:26

No, that doesnt split into an odd set and even set

Alex Miller (Clojure team)15:04:30

(let [{odds true, evens false} (group-by odd? [1 3 5 6 7 9])]
  (println odds evens))
;; [1 3 5 7 9] [6]

❤️ 4
Alex Miller (Clojure team)15:04:51

you could probably macro that into something you prefer

hindol15:04:07

So, true and false are functions?

Jim Newton15:04:33

no reason to macro that. a normal function works just fine.

andy.fingerhut15:04:33

well, keys into the map in this particular context. They are literal constant Boolean values, in general.

hindol15:04:44

Okay, so in destructuring the keys are actually looked up by get?

hindol15:04:26

I was thinking (true {true :true false :false})

andy.fingerhut15:04:17

That expression will throw an exception if you try to evaluate it in a REPL

andy.fingerhut15:04:30

Because true is not a function, and not callable in any Clojure-y way.

noisesmith16:04:20

the map is callable though - just swap them

Alex Miller (Clojure team)15:04:48

you can examine what destructuring converts to using the (undocumented) destructure function

hindol15:04:06

That's a nice tip!

Alex Miller (Clojure team)15:04:13

user=> (pprint (destructure '[{odds true, evens false} (group-by odd? [1 3 5 6 7 9])]))
[map__161 (group-by odd? [1 3 5 6 7 9])
 map__161 (if (clojure.core/seq? map__161)
            (clojure.lang.PersistentHashMap/create (clojure.core/seq map__161))
            map__161)
 odds (clojure.core/get map__161 true)
 evens (clojure.core/get map__161 false)]

hindol15:04:55

I actually wanted something like (juxt false true) but that won't work because true/false cannot be invoked.

Alex Miller (Clojure team)15:04:06

(juxt false? true?) maybe?

Alex Miller (Clojure team)15:04:25

I guess that won't work on a map

Alex Miller (Clojure team)15:04:45

you'd really need a get with those

noisesmith16:04:04

or (juxt #(get % false) #(get % true)) ?

noisesmith16:04:40

or #(map % [false true]) where the arg is your map

Darrell17:04:00

Can someone explain to me the purpose of (deref)? For example;

user=> (def a (atom 99))
#'user/a
user=> a
#<Atom@1e9083b5: 99>
user=> @a
99
Why does the first a return #<Atom@1e9083b5: 99>? Is there something about the underlying Java that is making this happen? Is the atom effectively a pointer unless dereferenced?

noisesmith17:04:26

the use case for an atom is for it to be a location

noisesmith17:04:50

so you need two sorts of operations: operations on the location itself, and operation on the data in that location

noisesmith17:04:41

you can have a function like (defn attach-foo [a] (swap! a conj :foo)) - it takes an atom, and replaces the contents using conj

noisesmith17:04:56

you can't do that if using a is the same as using @n3k05yd

noisesmith17:04:38

it's almost like an assignable pointer, but it has rules about how it can be updated

noisesmith17:04:24

in fact, even (swap! a conj :foo) would break if a was the same as @a

Darrell17:04:13

Interesting. Thanks @noisesmith!

noisesmith17:04:54

@darrell vars work the opposite way - if you have (def a 99) a gives you 99, but #'a gives the var itself

noisesmith17:04:29

which is useful if you need the metadata on the var, or you need to manipulate it directly, or you want some function to have a handle to the var and not the value in it

bfabry17:04:56

and (deref #'a) and @#'a give you 99 as well as making you feel like a perl programmer again 😆

Darrell17:04:07

That makes sense. I think I was conflating an atom with a var.

noisesmith17:04:19

ins)user=> (def a (atom 99))
#'user/a
(ins)user=> #'a
#'user/a
(ins)user=> a
#object[clojure.lang.Atom 0x4e61e4c2 {:status :ready, :val 99}]
(ins)user=> @a
99
(ins)user=> @@#'a
99

noisesmith17:04:42

please avoid @@#'foo in real code!

Darrell17:04:55

Yeah, that seems a tad unnecessary. 😄

ghadi17:04:04

reference types ("boxes") vs. values (usually not in a box, but can be)

ghadi17:04:17

reference types include vars, atoms, refs, agents

johnj17:04:25

learn how vars work first @darrell

ghadi17:04:35

vars are by far the most common, followed by atoms

noisesmith17:04:04

refs and vars have some convenient behaviors too:

(ins)user=> (def r (ref {:a 0}))
#'user/r
(ins)user=> (r :a)
0

noisesmith17:04:25

a ref or var, when used as a function, invokes the thing it contains

noisesmith17:04:03

(it would be nice if atoms did this too, but they don't)

Darrell17:04:42

So what is a scenario under which it’s preferential to use an atom over a var?

noisesmith17:04:10

if two threads need to keep track of mutable state without breaking each other

noisesmith17:04:32

vars are only meant to be changed as a part of development process - you are loading new definitions, the var changes

Darrell17:04:55

So does that mean one shouldn’t use swap! on vars?

phronmophobic17:04:56

i’m oversimplifying a lot here, but for getting started: • use atoms for mutable state • use vars for constants and function defintions via def and defn You can probably ignore agent , and ref at the start. Regarding atom, it’s typical in a non-clojure program to have lots of mutable state, but most clojure programs (even large ones) can get away with very little mutable state and very few atoms (like one or two).

noisesmith17:04:59

atoms are meant to handle runtime data modification - things which are inconvenient to do purely (or seemingly impossible)

noisesmith17:04:43

swap! on vars isn't possible (there's a similar function for vars, but it should be avoided and I'm forgetting the name :D )

Darrell17:04:58

That makes sense. Thanks guys!

noisesmith17:04:55

it's called alter-var-root, it's used for wrapper / middleware style extension and is basically a sign of "monkeypatching"

johnj17:04:18

when you want to change a shared(think threads) value

phronmophobic17:04:56

i’m oversimplifying a lot here, but for getting started: • use atoms for mutable state • use vars for constants and function defintions via def and defn You can probably ignore agent , and ref at the start. Regarding atom, it’s typical in a non-clojure program to have lots of mutable state, but most clojure programs (even large ones) can get away with very little mutable state and very few atoms (like one or two).

johnj17:04:54

even zero atoms 🙂

parrot 4
noisesmith18:04:20

ideally zero, yes

Michael Stokley23:04:54

how can i construct a ratio (eg 22/7) with expressions? (+ 20 2)/(+ 5 2) doesn't work