Fork me on GitHub
#beginners
<
2023-02-05
>
Naor Yaakov Harel11:02:26

What is the right way to insert a map into a SQL table? For now, I'm breaking it into string ("insert into table-name 'xxx', 'xxx'....") and executing by using jdbc/execute!

Amit Gold11:02:33

looks like they have in their example

(j/insert-multi! mysql-db :fruit
  [{:name "Apple" :appearance "rosy" :cost 24}
   {:name "Orange" :appearance "round" :cost 49}])

Amit Gold11:02:52

in general be careful about putting your variables directly into sql string - that makes you vulnerable to sql injection

seancorfield18:02:42

clojure.java.jdbc has been superseded by next.jdbc and the documentation is much, much better: https://cljdoc.org/d/com.github.seancorfield/next.jdbc/1.3.847/doc/getting-started/friendly-sql-functions

🙌 2
lspector18:02:51

Does anyone know of an online Clojure or Clojurescript REPL that lets you type properly indented definitions? I'm teaching a class and while some students are still trying to get their #CBE668G4R setups working (for which I'm getting help in the #CBE668G4R channel), I'd like them to have an environment to try out basic expressions and definitions. I used to use http://Repl.it (now https://replit.com) for this, but it seems like they've gotten rid of their REPL! The editing environment is okay, but you can only run the whole file, it takes weirdly long to do that, and you can't see the values of expressions without wrapping them in calls to print. Trying https://tryclojure.org, it's close but even if you enter spaces to try to get a multi-line definition formatted correctly, it eliminates all of the leading spaces when you evaluate it. Trying https://clojurescript.io, I can't get it to let me enter newlines in a definition. The others I've found also all have at least one of these issues. Am I missing some other option that's out there? Again, this is for short term use, and I'm not looking for any fancier features, but I want students to be able to type and see multi-line definitions/expressions with the correct indentation -- I don't care for this purpose if the system does any indenting itself, or if the student has to do it themselves by typing spaces -- and to be able to evaluate expressions and see their results. Any pointers would be appreciated!

Alex Miller (Clojure team)18:02:56

it's probably a much different env, but https://www.maria.cloud is a notebook style editor and the code blocks work like you want I think

Alex Miller (Clojure team)18:02:25

if you sign in with github, and go to New in upper left

lspector18:02:35

Thanks @U064X3EF3 ! Trying it in the Editor Quickstart page (without a login, since some of my students may not have github accounts, and I'm also not sure I want to give access to writing gists on my account) I see that this is an interesting option, and that it does the formatting reasonably. But it looks like calls to print go nowhere here, right? In a basic REPL one expects to see both return values and print results somewhere.

Alex Miller (Clojure team)18:02:51

ah, it's ClojureScript so it's going to the browser console

lspector18:02:19

Took me a while to even find the javascript console (I don't do that much 🙂 ), and now that I have, I don't see the output I printed... but there's a lot in there that I don't understand.

lspector18:02:28

FWIW I think that many years ago tryclj (or something with a similar name) did what I'm looking for, and for several years Replit definitely did. Now there seem to be many more things but they're all trying to do something fancier and in the process losing the basic "try Clojure without installing anything" functionality, or at least making it harder to find and access.

Alex Miller (Clojure team)19:02:46

have you tried the calva "in the browser" thing?

lspector19:02:50

Ooo! I had forgotten about that! Haven't looked into it in a while, but that's a great idea... will investigate!

lspector19:02:38

Remembering this too requires a github account, but if the rest works well that might be worth it... Now trying it and remembering that it comes up in a pretty complicated existing project, rather than a minimal empty one... lots on the screen so maybe bewildering... But it does work! Thanks so much for this suggestion/reminder! I will play and mull and perhaps send this to my students.

Alex Miller (Clojure team)19:02:49

I'm sure @U0ETXRFEW would be helpful in creating that simpler thing too if needed

lspector19:02:58

I think we talked about it when this web-based thing came out 🙂 . But he is indeed awesomely helpful and I will resume that conversation!

Alex Miller (Clojure team)19:02:23

gitpod is just loading https://github.com/PEZ/get-started-with-clojure I think - presumably you could modify that to your needs and start that with gitpod instead

👍 2
pez19:02:54

Yeah, there is not a lot to it. Here's something close to the minimal project that @U06BV1HCH is basing the onboarding on: https://github.com/PEZ/pirate-lang

pez19:02:07

We can strip it of the VNC stuff if it's not important to build a Java UI.

Bob B19:02:12

I think the experience from the gitpod stuff is probably better for expanding beyond a handful of definitions, but in case it helps, on http://clojurescript.io, if the input mode is changed (the keyboard button to the left of the repl window), things like newlines in definitions might work more 'intuitively' (e.g. paren-mode will allow for adding newlines until the parens are balanced).

lspector19:02:51

Thanks @U013JFLRFS8. I had twiddled with the format button, but not tried the right thing. I think paren-mode does do what I want! I see that here too, output from print goes nowhere (or to someplace I don't see), which I guess is just the Clojurescript way.

lspector22:02:01

FYI I've now recommended to my students that, while we debug their Calva setups, they start practicing Clojure by using http://clojurescript.io (in paren-mode and avoiding print and println) or the Zero install Getting Started REPL with Calva on gitpod. If we get a version of the latter working that puts the user in an empty, minimal project then I'll pass that along too, and I think that will be the best option of all. Thanks all for your help!!

pez23:02:44

Here's a mini-project ready to use from the browser: https://github.com/PEZ/minimal-clojure

❤️ 2
lspector23:02:51

This is really fabulous, and a much better solution than I imagined possible. Thanks!!

pez23:02:56

Gitpod is quite amazing. We have made zero changes to Calva to make it work there.

👍 2
cdeln18:02:05

What is the difference between a "form" and a "expression"?

Alex Miller (Clojure team)19:02:18

while I'm sure there are plenty who would argue this, I'll claim people use them interchangeably

Alex Miller (Clojure team)19:02:08

and importantly I would define them as "a thing you can evaluate to get a value"

Alex Miller (Clojure team)19:02:08

I think some would distinguish form to be Clojure data and expression to be the text representing the form

Alex Miller (Clojure team)19:02:39

as in, the Clojure reader reads an expression to a form, then evaluates it

👍 2
hiredman20:02:40

In theory everything in clojure is an expression (can be evaluated resulting in a value) so it makes sense that it ends up being used interchangeably with form

pez00:02:52

I probably use the terms wrongly, but I think of them as much the same thing. When I evaluate the thing I tend to think of it as an expression. When I manipulate it with structural editing, I think of it as a form.

☝️ 2
Danilo Oliveira07:02:37

I believe they are pretty much the same. Lispers tend talk in terms of f*orms*, that can be anything syntactically valid (atoms, numbers, etc), and s-expressions that are lists with first element being a function or macro name.

cdeln07:02:05

Thanks all for your input. It seems like there is no formal definition and that they can be used interchangeably for all practical purposes.

StevenGL20:02:04

Hello, I've got a function

(defn- transform [g-elem transform-fn & args]
  (update-in
   g-elem
   [1 :transform] 
   transform-fn
   args))
but update-in will not work correctly with args as a list, how can I destructure them into individual args?

hiredman20:02:34

It isn't that update-in "will not work correctly with args as a list" it is you have args as a list, and pass them as a list to update-in and update-in passes them as you gave them to transform-fn and transform-fn can't handle them as a list

StevenGL20:02:19

that's correct, I wanted to find workaround for that

StevenGL20:02:43

something like pass-through the args

kennytilton20:02:08

(let [ns [4 5 6]]
  (apply + 1 2 3 ns))
=> 21
It is a common requirement.

kennytilton20:02:07

Note that only the last argument gets this splicing (spread?) treatment.

StevenGL21:02:11

For instance:

(def elem [:g {:transform ""}])
(transform elem str "updated")
should return
[:g {:transform "updated"}]

Sam Ritchie21:02:04

@U01S1T7LQ7Q add “apply” before “update-in”

vlad_poh22:02:18

Which is better? I think the latter is clearer 1.

(reduce #(+ % (:x %2)) 0 [{:x 1} {:x 2}])
2.
(reduce + (map :x [{:x 1} {:x 2}]))
Is there a clearer solution that uses reduce only?

Sam Ritchie23:02:52

I agree that the latter is clearer! The transduce version is as efficient as the first one: (transduce + (map :x) xs)

👍 4
Sam Ritchie23:02:33

(The second reduce version is slightly less efficient because the map call creates an intermediate collection)

phronmophobic23:02:30

also, since the question asked about using reduce only:

(reduce ((map :x) +) 0 [{:x 1} {:x 2}])

☝️ 4
kennytilton23:02:02

Not (apply + (map :x [{:x 1} {:x 2}]))?

Sam Ritchie23:02:38

That will work too because + uses reduce internally for its arities higher than 2

Sam Ritchie00:02:11

But it will create the intermediate collection too

👍 2
seancorfield00:02:31

@U7RJTCH6J I always forget that you can apply (map f) to another function to get a reducing function! Nice!

Sam Ritchie00:02:47

Yeah I had never thought of doing that

seancorfield00:02:56

(I think transduce is clearer tho')

8
phronmophobic00:02:36

Yea, I agree that the transduce version is the most straightforward. I finally internalized the idea based on a tip from @U0NCTKEV8 https://clojurians.slack.com/archives/C053AK3F9/p1657728161166039?thread_ts=1657568229.677049&amp;cid=C053AK3F9

seancorfield00:02:13

Yeah, he's a big fan of transducers -- I'm slowly absorbing them from his code at work 🙂

😄 2
skylize00:02:38

Minor nitpick. I am having an incredibly difficult time reading your first example because the first arg is not numbered. I think favoring %1 instead of just % when you have multiple arguments can go a long way toward readability, especially when the arg usages are located close together like that.

👍 2
seancorfield00:02:21

I must admit, I almost never use the #(..) form when I have more than one argument these days -- I switch to (fn [..] ..) because I find it more readable.

skylize00:02:29

One step at a time. 😛

ghadi00:02:27

(apply + (map :x …)) is clearest to me. You’re summing up a collection of values

ghadi00:02:48

The reduce variants are all particular strategies for summing

Amit Gold08:02:41

call me old school, but I like (->> collection (map :x) (reduce +))

👍 4
Amit Gold09:02:39

or (apply +) at the end, same readability

ghadi15:02:00

caution against using the reduce arity with no initial value supplied. Rich says it's the worst thing he copied from Common Lisp and regrets it. reduce with no initial value has insane semantics > If val is not supplied, > returns the result of applying f to the first 2 items in coll, then > applying f to that result and the 3rd item, etc. If coll contains no > items, f must accept no arguments as well, and reduce returns the > result of calling f with no arguments. If coll has only 1 item, it > is returned and f is not called. If val is supplied, returns the > result of applying f to val and the first item in coll, then > applying f to that result and the 2nd item, etc. If coll contains no > items, returns val and f is not called. transduce works differently despite being a reduce variant. With no initial value, transduce calls the reducing function with no arguments to get the initial value. Very different (and better)

didibus08:02:21

I saw that, but I don't know, I think both are fine, what annoys me more is that transduce behaves differently to reduce, and that trips me up. Like this is convenient:

(reduce - [1 2 3])
And you can't use transduce for it.

Sam Ritchie17:02:06

@U0K064KQV imo that’s more a critique of -

Sam Ritchie17:02:38

(defn minus
  ([] 0)
  ([x] x)
  ([x y] (- x y)))

(transduce (map identity) minus [1 2 3])
;;=> -6

didibus19:02:34

What if you tried with multiplication or division? Not every function will have an identity result that you can start with no? Or maybe identity is the wrong word, but like a no-op result

hiredman19:02:06

0 isn't even identity for subtraction, it is a left or right identity depending on what you are doing (which you better hope matches your fold)

👍 2
hiredman19:02:15

and that isn't even right, I guess 0 is only a right identity

didibus19:02:40

(transduce (map identity) minus [-1 2 3])
;;> -4
(apply - [-1 2 3])
;;> -6

hiredman19:02:03

identity is the right word, but identity means for given binary operation, the identity element can be on either side of the operation

hiredman19:02:42

but if the identity element can only be on one side of the operator you have a left or right identity depending on which side

hiredman19:02:41

(incidentally identity is the identity transducer, no need to do a nop map)

💯 4
didibus19:02:37

I'm still not sure there's a way to correctly implement (reduce - coll) with transduce. Seems you'd have to do something weird like:

(let [coll [-1 2 3]]
 (transduce
  identity 
  (completing 
   (fn 
    ([] (first coll)) 
    ([x y] (- x y))))
  (rest coll)))

hiredman19:02:12

(reduce - coll) having no initial value is in my mind just wrong, and I always pass an initial value to transduce as well

didibus19:02:33

So you would do?:

(reduce - (first coll) (rest coll))

hiredman19:02:08

user=> (reduce - 0 [-1 2 3])
-4
user=> (transduce identity (completing -) 0 [-1 2 3])
-4
user=>

didibus19:02:43

I guess I do think reduce is overloaded too much, but then we'd need another function that that a binary function and applies it to all elements of a coll starting with the first two.

didibus20:02:14

Those are incorrect though, the right answer is -6

didibus20:02:51

-1 - 2 - 3 = -6

hiredman20:02:59

you only think that is right because you insist on the semantics of reduce without an initial value

hiredman20:02:35

a fold is not that though

didibus20:02:21

Sure, if you want to remove that capability that's ok, but how do I do that now if I can no longer use reduce? If that's what I want to do?

ghadi20:02:44

I just pretend reduce without an initial value doesn't exist.

🙉 2
hiredman20:02:27

a fold is a structural rewrite, which is easier to see with foldr, which clojure doesn't have

hiredman20:02:16

(foldr - 0 (cons 1 (cons 2 (cons 3 nil)))) => (- 1 (- 2 (- 3 0)))

hiredman20:02:29

it replaces cons with - and nil with 0

didibus20:02:41

I agree that reduce is overloaded with two different semantics, and maybe it can be a bit confusing that it is. But I feel that it leaves a gap if you remove the former behavior for when you need that behavior. Am I supposed to use loop/recur for it from now on?

ghadi20:02:36

having feels about tautologies aren't sufficient

2
didibus20:02:06

Basically, performing an "aggregate" function over a collection is quite a common use case. So do we no longer have an answer to how people should implement that?

seancorfield20:02:25

The answer depends on whether your aggregate function has an appropriate identity. So it's either (reduce f id-val coll) or (reduce f (first coll) (rest coll))

2
didibus20:02:06

Yes, I'm saying it doesn't, like for - , *, or /

hiredman20:02:17

* has an identity

hiredman20:02:28

+ has an identity

didibus20:02:16

Ok fair. So (reduce - (first coll) (rest coll)is Rich's preferred idiom?

ghadi20:02:40

also consider sources that only implement IReduceInit

ghadi20:02:52

can't call first/rest on those

seancorfield20:02:59

It depends on what you are trying to do @U0K064KQV 🙂

seancorfield20:02:22

You can't just magically treat - and + as the same -- they are not, mathematically, in terms of basic properties.

didibus20:02:06

What I'm saying is, if we say that reduce was doing too much, that's totally fine, I agree the behavior with no initial value is a bit confusing since it's so different. But now everything you were using reduce without an initial collection for, what's the replacement?

seancorfield20:02:24

IT DEPENDS. Sheesh.

seancorfield20:02:48

Does the function have an identity value or not? Not all functions are created equal.

didibus20:02:50

Why does it depend though? I'm confused? It didn't depend with reduce, it always worked?

ghadi20:02:54

supply an initial value, move on

didibus20:02:32

And when you can't supply an initial value?

seancorfield20:02:52

You already answered that above, didn't you.

ghadi20:02:26

i'm getting off this thread, which has veered OT. OP got their response

2
didibus20:02:17

So for example, I already saw one person introduce a pretty major and hard to find bug in my code base, when Sam Ritchie incorrectly assumed you could use transduce with -, I have never had someone introduce a bug because of using reduce without an initial coll. Now people tell me transduce is "better" than "reduce" . Ok, well maybe not in the correctness dimension?

seancorfield20:02:11

You're just being obtuse at this point -- and you are definitely not helping in the #C053AK3F9 context now. The question has been asked and answered.

didibus20:02:59

I'm not. As a beginner, if I still were, I'd be very confused about what's the advice here. What's my easy to remember best practice? Don't use reduce without an initial value, but be careful if your function doesn't support identity, then it depends and there's no agreed on alternative, you might need to ask here for each special case?

Sam Ritchie20:02:00

@U0K064KQV to be fair a single test would have caught my bug :)

didibus20:02:27

That's fair, my follow up question is how would you have fixed it?

Sam Ritchie20:02:31

I would have considered what I’m actually trying to do since as folks have pointed out subtraction by itself doesn’t have an identity

seancorfield20:02:51

Use reduce with an initial value (always). So then the question is "What should the initial value be when I want to subtract things?" And the answer to that depend on whether you want 0 - e1 - e2 - ... or e1 - e2 - ... -- and there's your initial value.

seancorfield20:02:29

build=> (reduce - 0 [1])
-1
build=> (reduce - 1 [])
1
build=> (reduce - 0 [1 2])
-3
build=> (reduce - 1 [2])
-1

didibus20:02:07

And for the latter, if I still shouldn't use reduce for it, what should we use instead? That's really all my question is. Was not trying to be obtuse, I have ideas, but I wouldn't find any of the alternatives much cleaner than using reduce without an initial value as it stands, and was curious to know if I'm missing another way of doing it. I've asked in #C03S1KBA2 though. Cause I do agree I might have taken this thread too far of the initial question.

Sam Ritchie20:02:35

haha okay I have an answer now that I’m back at my computer

Sam Ritchie20:02:31

here is what I did in #C01ECA9AA74 :

(defn group
  "Similar to [[monoid]] for types with invertible elements. Accepts:

  - binary `minus` and (associative) `plus` functions
  - a unary `negate` function
  - an element `id` that obeys `(plus id other) == (plus other id) == other`
  - optionally, an `annihilate?` function that should return true for any `x`
    such that `(plus x <any>) == x`.

  And returns a function that will SUBTRACT elements. Given `x`, `y`, `z`, for
  example, the returned function will return `(- x y z)`, implemented as `(minus
  x (plus y z))`

  If the `annihilate?` function is supplied, then if the aggregation produces a
  value that returns `(annihilate? true)` at any point, the reduction will
  return immediately."
  ([minus plus invert id]
   (group minus plus invert id nil))
  ([minus plus invert id annihilate?]
   (let [acc (combiner plus annihilate?)]
     (fn
       ([] id)
       ([x] (invert x))
       ([x y] (minus x y))
       ([x y & more]
        (minus x (reduce acc y more)))))))

Sam Ritchie20:02:21

@U0K064KQV see the final arity; (- a b c d) gets transformed to (- a (+ b c d))

Sam Ritchie20:02:26

so you skip the whole reduce question entirely

Sam Ritchie20:02:21

(I made it general since I’m doing symbolic calculations )

(group sub add negate 0)

hiredman20:02:34

you can also transform - into a monotonic function the way crdts do it

seancorfield20:02:56

(apply - coll) is always an option 🙂 and it's essentially (reduce - (first coll) (rest coll)) 🙂 (it's not actually defined that way but...)

hiredman21:02:19

(transduce (map (fn [n] (if (neg? n) [0 (- n)] [n 0]))) (fn ([[a b]] (- (- b) a)) ([[a b] [c d]] [(+ a c) (+ b d)])) [0 0] [-1 2 3]) will produce the same result independent of the fold (left, right, fork join over a tree, etc) and ultimately does something similar to the group above

Sam Ritchie21:02:20

@U0NCTKEV8 that is interesting, I wrote something that this reminds me of years ago for Algebird… instead of tracking positives and negatives it tracks set additions and subtractions https://github.com/twitter/algebird/blob/develop/algebird-core/src/main/scala/com/twitter/algebird/SetDiff.scala#L19

Sam Ritchie21:02:05

then you can apply the diff (which you can build in a distributed way) to an existing set

Sam Ritchie21:02:15

does that match anything from CRDT-land that you’re familiar with?

skylize21:02:17

Wow! Poor @U06GMV0B0 got way more than bargained for when asking such a simple question in the #C053AK3F9 channel. Maybe we can move this to a new topic in #C03S1KBA2?

2
Sam Ritchie21:02:26

haha we’ve gone far afield for sure

skylize21:02:25

I think all the wild tangents would be excellent... if we were in the right channel for digging down in the weeds like this.

didibus21:02:09

At least it was all in a 🧵 😄

Sam Ritchie23:02:19

(transduce + (map :x) xs)

👍 4
2
Martin Půda08:02:29

You have the wrong order of xform and reducing fn:

(transduce + (map :x) [{:x 1} {:x 2}])
Execution error (ArityException)
Wrong number of args (0) passed to: clojure.core/map/fn--5945

(transduce (map :x) + [{:x 1} {:x 2}])
=> 3

☝️ 4