This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-02-05
Channels
- # babashka (13)
- # beginners (146)
- # biff (3)
- # calva (18)
- # cider (6)
- # clerk (29)
- # clj-commons (1)
- # clj-kondo (75)
- # clojure (42)
- # clojure-belgium (2)
- # clojure-europe (13)
- # clr (9)
- # conjure (2)
- # datomic (2)
- # gratitude (3)
- # hyperfiddle (3)
- # off-topic (26)
- # reitit (5)
- # releases (1)
- # shadow-cljs (2)
- # timbre (3)
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!
i assume you’re using https://github.com/clojure/java.jdbc ?
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}])
in general be careful about putting your variables directly into sql string - that makes you vulnerable to sql injection
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
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!
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
if you sign in with github, and go to New in upper left
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.
ah, it's ClojureScript so it's going to the browser console
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.
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.
have you tried the calva "in the browser" thing?
Ooo! I had forgotten about that! Haven't looked into it in a while, but that's a great idea... will investigate!
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.
I'm sure @U0ETXRFEW would be helpful in creating that simpler thing too if needed
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!
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
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
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).
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.
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!!
Here's a mini-project ready to use from the browser: https://github.com/PEZ/minimal-clojure
This is really fabulous, and a much better solution than I imagined possible. Thanks!!
while I'm sure there are plenty who would argue this, I'll claim people use them interchangeably
and importantly I would define them as "a thing you can evaluate to get a value"
I think some would distinguish form to be Clojure data and expression to be the text representing the form
as in, the Clojure reader reads an expression to a form, then evaluates it
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
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.
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.
Thanks all for your input. It seems like there is no formal definition and that they can be used interchangeably for all practical purposes.
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?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
(let [ns [4 5 6]]
(apply + 1 2 3 ns))
=> 21
It is a common requirement.Note that only the last argument gets this splicing (spread?) treatment.
For instance:
(def elem [:g {:transform ""}])
(transform elem str "updated")
should return
[:g {:transform "updated"}]
@U01S1T7LQ7Q add “apply” before “update-in”
of course, thanks @U0PUGPSFR and @U017QJZ9M7W
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?I agree that the latter is clearer! The transduce version is as efficient as the first one: (transduce + (map :x) xs)
(The second reduce version is slightly less efficient because the map call creates an intermediate collection)
also, since the question asked about using reduce
only:
(reduce ((map :x) +) 0 [{:x 1} {:x 2}])
Nice!!
Not (apply + (map :x [{:x 1} {:x 2}]))
?
That will work too because + uses reduce internally for its arities higher than 2
@U7RJTCH6J I always forget that you can apply (map f)
to another function to get a reducing function! Nice!
Yeah I had never thought of doing that
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&cid=C053AK3F9
Yeah, he's a big fan of transducers -- I'm slowly absorbing them from his code at work 🙂
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.
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.
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)
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.@U0K064KQV imo that’s more a critique of -
…
(defn minus
([] 0)
([x] x)
([x y] (- x y)))
(transduce (map identity) minus [1 2 3])
;;=> -6
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
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)
identity is the right word, but identity means for given binary operation, the identity element can be on either side of the operation
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
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)))
(reduce - coll)
having no initial value is in my mind just wrong, and I always pass an initial value to transduce as well
user=> (reduce - 0 [-1 2 3])
-4
user=> (transduce identity (completing -) 0 [-1 2 3])
-4
user=>
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.
you only think that is right because you insist on the semantics of reduce without an initial value
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?
a fold is a structural rewrite, which is easier to see with foldr, which clojure doesn't have
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?
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?
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))
(*)
= 1
It depends on what you are trying to do @U0K064KQV 🙂
You can't just magically treat -
and +
as the same -- they are not, mathematically, in terms of basic properties.
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?
IT DEPENDS. Sheesh.
Does the function have an identity value or not? Not all functions are created equal.
Why does it depend though? I'm confused? It didn't depend with reduce, it always worked?
You already answered that above, didn't you.
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?
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.
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?
@U0K064KQV to be fair a single test would have caught my bug :)
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
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.
build=> (reduce - 0 [1])
-1
build=> (reduce - 1 [])
1
build=> (reduce - 0 [1 2])
-3
build=> (reduce - 1 [2])
-1
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.
haha okay I have an answer now that I’m back at my computer
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)))))))
@U0K064KQV see the final arity; (- a b c d) gets transformed to (- a (+ b c d))
so you skip the whole reduce question entirely
for -
(I made it general since I’m doing symbolic calculations )
(group sub add negate 0)
(apply - coll)
is always an option 🙂 and it's essentially (reduce - (first coll) (rest coll))
🙂 (it's not actually defined that way but...)
(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
(a PN counter https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type#PN-Counter_(Positive-Negative_Counter))
@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
then you can apply the diff (which you can build in a distributed way) to an existing set
does that match anything from CRDT-land that you’re familiar with?
https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type#2P-Set_(Two-Phase_Set)
nice!!
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?
haha we’ve gone far afield for sure
I think all the wild tangents would be excellent... if we were in the right channel for digging down in the weeds like this.
We can continue here: https://clojurians.slack.com/archives/C03S1KBA2/p1675802500486649
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