This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2024-05-24
Channels
- # announcements (27)
- # beginners (105)
- # calva (10)
- # clojure (2)
- # clojure-europe (10)
- # clojure-nl (1)
- # clojure-norway (11)
- # clojure-sweden (5)
- # clojure-uk (20)
- # clojurescript (7)
- # code-reviews (4)
- # community-development (2)
- # cursive (10)
- # data-science (2)
- # emacs (20)
- # events (1)
- # fulcro (1)
- # gratitude (1)
- # guix (1)
- # hyperfiddle (4)
- # off-topic (4)
- # overtone (24)
- # rdf (2)
- # releases (4)
- # ring (4)
- # shadow-cljs (26)
- # squint (76)
- # yamlscript (29)
Hi!
I'm wondering if that code could be done a little better, outside of potentially extracting the filtering predicate and use a let
in it...
For the context this is https://projecteuler.net/problem=52.
(defn problem-52 []
(->> natural-numbers
(filter (fn [n]
(every? #(= (sort (number-as-digits n))
(sort (number-as-digits (* n %))))
(range 2 7))))
(first)))
I was hoping there was a better way as I'm repeating the same sequence of functions... but I can't come up with a great way for it, I might have looked at my computer for too long... maybe (apply =
and mapping on (range 1 7)
, thoughts?It does give the right result, and quite quickly.
user=> (time (problem-52))
"Elapsed time: 69.160875 msecs"
142857
natural-numbers is defined as:
(def natural-numbers (iterate inc 1))
and number-as-digits
(defn number-as-digits
([n] (number-as-digits n '()))
([n digits]
(if (zero? n)
digits
(recur (quot n 10) (cons (mod n 10) digits)))))
ah. I think I got it.
(defn problem-52 []
(->> natural-numbers
(filter (fn [n]
(apply = (map #(sort (number-as-digits (* n %)))
(range 1 7)))))
(first)))
I assume it's slower because it's always doing all the multiplication, whereas every doesn't need to multiply by 3-6 if 1 and 2 are different (which I imagine is the majority of the cases)
I'm currently falling victim to analysis paralysis while doing Crafting Interpreters by Nystrom in Clojure and am unsure of the following: If I have a set of keywords of a basic programming language, can I just crudely pattern match (using clojure.core.match) one long input string and call it a day on the scanning/lexing? The alternate way I've got seems much more messy by comparison (functions to process characters then form tokens). Any input will be gratefully received.
Not sure if this is relevant, but I read this blog post a while back (https://andreyor.st/posts/2023-12-03-thoughts-on-crafting-interpreters-part-1/). I don't remember how he did it, but he implemented the book in Clojure 🙂
if I'm doing it as an exercise, for learning, I would try to avoid any libraries, and do everything by hand.
on the other hand, defining a grammar and use instaparse is also a valid option.
I would like to beat a dead horse a little bit more and bring up a disagreement I have with the documentation of defn
.
I don't believe these two syntaxes are equivalent. They should be, but because of overzealous error checking they are not.
user> (def user/x (fn [x] (+ x 33)))
#'user/x
user> (defn user/x [x] (+ x 33))
Syntax error macroexpanding clojure.core/defn at (*cider-repl clojurein/clojurein-source-code:localhost:54258(clj)*:174:7).
user/x - failed: simple-symbol? at: [:fn-name] spec: :clojure.core.specs.alpha/defn-args
user>
I think I remember a thread about something similar in the past, essentially saying that the symbol to be defined should be a simple symbol, so arguably def
has the 'issue' (using that term loosely).
In my opinion it is overzealous because the docstring says the syntactical forms are equivalent. However, the error checking (which is supposed to be helpful) on one is different than on the other, so by implementation the syntactical forms are not equivalent.
huh that seems like a bad idea, i would not recommend that
@UEENNMX0T exactly. you can def
into another name space, and you ought be be able to defn
into another name space. and if that syntax check were relaxed you would be able to.
if the syntax check on defn
were relaxed, the change would be backward compatible. If the syntax check on def
were tightened, code would break.
❯ clj
Clojure 1.11.2
user=> (create-ns 'foo)
#object[clojure.lang.Namespace 0x3e134896 "foo"]
user=> (def user/x 1)
#'user/x
user=> (def foo/x 1)
Syntax error compiling def at (REPL:1:1).
Can't refer to qualified var that doesn't exist
user=>
and then
user=> (in-ns 'foo)
#object[clojure.lang.Namespace 0x3e134896 "foo"]
foo=> (def foo/x 1)
#'foo/x
foo=> (in-ns 'user)
#object[clojure.lang.Namespace 0x27068a50 "user"]
user=> (def foo/x 2)
Syntax error compiling def at (REPL:1:1).
Can't create defs outside of current ns
hmmm. I don't understand that error.
i think you’re seeing a bit of undefined behavior. and wanting that undefined behavior in a similar case. and that’s just not an expectation that can work
user> (ns foo1)
nil
foo1> (ns foo2)
nil
foo2> (in-ns 'foo1)
#namespace[foo1]
foo1> (def x 100)
#'foo1/x
foo1> (in-ns 'foo2)
#namespace[foo2]
foo2> (def x 200)
#'foo2/x
foo2> (def foo1/x 300)
Syntax error compiling def at (*cider-repl clojurein/clojurein-source-code:localhost:54258(clj)*:192:7).
Can't create defs outside of current ns
foo2> (def foo2/x 400)
#'foo2/x
foo2>
and from the def
docs:
> Creates and interns or locates a global https://clojure.org/reference/vars with the name of symbol and a namespace of the value of the current namespace (`*ns*`)
So, it seems like, in the larger context, the real departure from the docs is that def
with a qualified symbol (of the current ns) works
indeed interesting, I can't def
into a different namespace. However I can def into the current name space with with or without the qualified name.
so then the syntax check is doubly redundant. it's wrong, and it circumvents another more meaningful error.
hmm. we differ in opinion. the real error is that you can't define into a different name space. that fact that the symbol is not simple is not the real issue. in my opinion. because if foo
names the current namespace, then there's no problem.
and you can't check that syntactically, at least I believe you can't
these should both fail, feel free to file an https://ask.clojure.org, spec can be added/updated to cover
tightening this check will make working compliant code fail.
use intern
if you want to make vars in other namespaces
whats the benifit of making (def foo/x 100)
fail if already in the namespace foo
?
what's the benefit of it succeeding?
it's redundant - def always creates vars in the current namespace
there is a great benifit of it succeeding. It has succeeded since clojure 1.0 (or whatever the beginning of time is). and second if you have an application which defines the same name in multiple namespaces, then it relieves confusion with tools like grep, when you search for definitions.
also when navigating in the editor ... when I jump to definition, i can see immeditely that it is foo/x rather than bar/x
If I have the same symbol defined in multiple places (which is rare but happens) I really like the definitions to be (def foo/x ...)
and (def bar/x ...)
if it is for grep support you can add a comment on the same line
I'd love it if I could also (defn foo/x ...)
and (defn bar/x ...)
, but instead I have to rewrite the defn
as a def
which I find more confusing.
I tried to just overload defn
in my namespace, but I could never make it work, because it is a macro that does really a lot.
BTW what is the reason for prohibiting making definitions in other name spaces? That's not an issue in Common Lisp. common lisp is the only other lisp I know which has namespaces, emacs lisp does not, neither does SKILL.
it seems like it would be useful for debugging, if I could just redefine definitions in the REPL without having to do (in-ns ...) ... (re-definition ...) (in-ns back to original ns)
you can already define symbols in other namespaces with intern
you want to add that function to another namespace?
oops You're right my reponse is BS. I'll delete it.
how do I replace (in-ns ...) (re-definition ...) (in-ns back to original ns)
with intern
?
but use the other namespace
but defmethod
can already take qualified names, right?
defmethod
is adding a method for a defmulti
that could be on a different namespace. The function defining thing here is defmulti
exactly, defining a method whose defmulti
is in a different ns is a pretty common operation.
@U064X3EF3 In my opinion the following code is clearer because the call-site and the definition both use bdd/and
. If either or the other used and
instead, it would risk confusion.
(def bdd/and
"Perform a Boolean AND on 0 or more Bdds."
(fn
([] true)
([bdd] bdd)
([bdd1 bdd2]
(cond
(= false bdd1) false
(= false bdd2) false
(= true bdd1) bdd2
(= true bdd2) bdd1
(identical? bdd1 bdd2) bdd1
:else (binary-op bdd/and bdd1 bdd2)))
([bdd1 bdd2 & bdds]
(reduce bdd/and (apply cons bdd1 bdd2 bdds)))))
what I meant is that defmulti
is the defining thing, defmethod
could be called add-method
BTW looking at this code, I wonder whether I should change (reduce bdd/and (apply cons bdd1 bdd2 bdds))
to (reduce bdd/and (bdd/and bdd1 bdd2) bdds)
... whether that would be equivalent and clearere???
This is a different discussion. I'll open another thread.
I did not quite understand the use cases in defense of defn with a qualified symbol. Not exactly disagreeing, just noting that a stronger argument might help. :-) (1)grep can show filenames! (If you declare all your ns's in a single file, just don't do that.) (2)Editors show the filename in a tab or something! If you rely on every def to remind you of the buffer you're editing, maybe you can do that in an editor extension? (3)defmulti's purpose is to invite open extension, so it isn't comparable.
Anyway @U010VP3UY9X thanks for putting in the Ask Clojure issue and please report back when it's ready
@U0HG4EHMH, not sure whether I want to add this to Ask Clojure, as I certainly do not want an accidental side effect to be a backward-incompatible restriction of def
.... as I don't see the benefit of the simple-symbol?
restriction. Suppose the simple-symbol?
restriction were lifted; the implementation would still prohibit cross-namespace definitions, which seems to be the intent. So in my opinion the simple-symbol?
restriction on defn
adds no value. Yet everyone seems to be defending it. Even Alex Miller.
i can't find a single instance of this in the wild using http://grep.app
should I push a copy of my code into the space that http://grep.app searches?
how do I do that?
the app indexes public github projects that it has selected. it’s a personal project from someone and I don’t believe you can get your project into it. maybe could find some contact info on the app though?
i think it's all public repos? i've never selected my projects for inclusion but they appear in results
my code is in my school's gitLab.
I probably should mirror it to github
ok, i've mirrored some projects to github
@UEENNMX0T I added several projects to github and made them public, yet the http://grep.app still finds nothing.
interesting. i don't run http://grep.app and have no special insight
Does anybody have any recommendations for how to build a secure web app? I've read and heard in multiple places how secure web apps (were? are?) an issue with Clojure, and I'm just wondering what the current state of clojure web security is
I've never heard that. I mean all products have security bugs but I've never heard that clojure was different in that regard than other languages. And entire classes of security issues are fixed by Clojure's focus on immutability and functional programming, so you can make the opposite argument as well. Also, there's no one definition of "security." There's at least confidentiality, integrity and availability as orthogonal concerns, but there's more, depending on the security posture of the individual or organization. For some, lack of availability is a much higher risk of damage than confidentiality, for instance.
Not clojure specific, but definitely familiarize yourself with the latest https://owasp.org/www-project-top-ten/ recommendations.
A few years ago the author of the https://github.com/funcool/buddy library (which you should look into) gave a talk about how some clojure stacks in the wild were not adhering to security best practices, but that's above the level of the language and has to do with the common practices of the community, which has improved over the years as clojure tools for this stuff grows. But if you're not using the existing security tools of the java ecosystem to secure systems that need special security then you're doing it wrong.
At least until more tools show up in the clojure space. Def keep an eye on the 1.0 release of https://github.com/taoensso/tempel too, that looks pretty interesting
re https://clojurians.slack.com/archives/C053AK3F9/p1716563193370229?thread_ts=1716561125.406399&cid=C053AK3F9: yes, that's what I mean. Stuff at the web layer
right now (I did look at buddy) it looks like buddy is basically unmaintained. The readme tells you to refer to individual modules.. I looked at buddy-auth, and that too looks unmaintained
Tempel looks interesting. I am specifically interested in authentication and authorization
Yeah I don't think clojure web app security is going to end up being much different than java web app security in general, so we haven't felt the need to socialize documentation and code around those problems. You can pretty much read some "secure web apps for java" tutorial and the info should be mostly the same. Clojure really doesn't change much of that story
Wrt authentication and authorization, I think folks are generally moving towards jwt. There's https://github.com/sikt-no/clj-jwt for that but if you have some mission critical app where you want your deps updated daily then there might be a java jwt lib out there that is getting more attention.
Or you could just roll your own jwt impl. I haven't done it personally but I've seen it hand rolled at a few companies. It's a pretty basic protocol fwiu
But if you're not already paying someone with enough security experience to hand roll a jwt implementation then your time is probably better spent just using a lib.
Looking at some old code, I wonder whether I should change (reduce bdd/and (apply cons bdd1 bdd2 bdds))
to (reduce bdd/and (bdd/and bdd1 bdd2) bdds)
... whether that would be equivalent and clearer???
When I look at the documentation of reduce
I am confused. Is the documentation wrong, or am I reading it wrong?
the confusing issue is that (reduce + 10 '(1))
fortunately returns 11
, but the documentation implies it will return 1
it seems the the confusion is the punctuation. The part highlighted in blue is inside an implicit indiction headed by If val is not supplied.
In my opinion that is not clear (worse misleading) when reading the text.
I think this confusion is why people consider IReduce
to be a bit of a footgun and always use IReduceInit
and supply an init value
sorry, I don't understand IReduce
and IReduceInit
basically the (reduce f coll)
vs (reduce f val coll)
. The weird rules about applying f
to the two items in coll, or calling f
with no arguments. Or if coll has 1 item.
All that complexity goes away when there’s an initial value
When I look at some old code, I see that I did a really crazy thing to make sure the computation was correct in the case exactly 3 arguments were given.
(def bdd/and
"Perform a Boolean AND on 0 or more Bdds."
(fn
([] true)
([bdd] bdd)
([bdd1 bdd2]
(cond
(= false bdd1) false
(= false bdd2) false
(= true bdd1) bdd2
(= true bdd2) bdd1
(identical? bdd1 bdd2) bdd1
:else (binary-op bdd/and bdd1 bdd2)))
([bdd1 bdd2 & bdds]
(reduce bdd/and (apply cons bdd1 bdd2 bdds)))))
If I had used (reduce bdd/and (bdd/and bdd1 bdd2) bdds))
I was afraid arg-2 would be ignored if bdds contained exactly one element.I see now that my understanding was wrong.
and it also seems to be that (apply cons bdd1 bdd2 bdds)
is completely wrong. strange that that code path was never tested.
foo2> (apply cons 1 2 '(3 4 5))
Execution error (ArityException) at foo2/eval11980 (form-init7718652093210840400.clj:201).
Wrong number of args (5) passed to: clojure.core/cons--5441
foo2>
what's the correct way to cons 2 or more things onto the beginning of a list. I.e., the equivalent of cons*
in other lisps?conj
?
(conj '(3 4 5) 2 1)
produces (1 2 3 4 5)
. yes, in other lisps (cons* 1 2 '(3 4 5))
produces (1 2 3 4 5)
. semantically equivalent I suppose.
I think I see why this error was never caught in my testing. My test cases contain (for ....)
rather than (doseq ...)
so my lazy loops were never executed .... yikes.