Fork me on GitHub
#beginners
<
2024-05-24
>
Melanie02:05:24

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?

Melanie02:05:40

It does give the right result, and quite quickly.

user=> (time (problem-52))
"Elapsed time: 69.160875 msecs"
142857

Melanie02:05:10

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)))))

Melanie02:05:52

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)))

Melanie02:05:59

slower though.

Bob B03:05:41

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)

Melanie04:05:46

That makes sense thanks Bob!

Troy03:05:29

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.

Fredrik Meyer09:05:01

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 🙂

jpmonettas12:05:46

if I'm doing it as an exercise, for learning, I would try to avoid any libraries, and do everything by hand.

Ludger Solbach14:05:19

on the other hand, defining a grammar and use instaparse is also a valid option.

Jim Newton13:05:49

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.

Jim Newton13:05:14

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> 

dpsutton13:05:19

This doesn’t feel like overzealous error checking

dpsutton13:05:07

it’s seems weird that <current-ns>/var-name works in def.

Bob B14:05:29

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).

Noah Bogart14:05:02

can you def into another namespace?

1
Jim Newton14:05:03

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.

Noah Bogart14:05:58

huh that seems like a bad idea, i would not recommend that

Jim Newton14:05:07

@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.

dpsutton14:05:29

I have not seen that you can def into another namespace

Jim Newton14:05:48

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.

dpsutton14:05:13

❯ 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=>

dpsutton14:05:50

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

Jim Newton14:05:52

hmmm. I don't understand that error.

dpsutton14:05:39

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

Jim Newton14:05:19

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> 

Bob B14:05:20

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

👍 1
Jim Newton14:05:53

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.

Jim Newton14:05:39

so then the syntax check is doubly redundant. it's wrong, and it circumvents another more meaningful error.

dpsutton14:05:26

foo/x - failed: simple-symbol? to me is the most helpful

Jim Newton14:05:56

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.

Jim Newton14:05:17

and you can't check that syntactically, at least I believe you can't

Alex Miller (Clojure team)14:05:32

these should both fail, feel free to file an https://ask.clojure.org, spec can be added/updated to cover

Jim Newton14:05:49

nooooooooo!!!!!!!!!!!

😂 1
1
Jim Newton14:05:00

tightening this check will make working compliant code fail.

Alex Miller (Clojure team)14:05:09

that code should already fail

😡 1
Alex Miller (Clojure team)14:05:21

use intern if you want to make vars in other namespaces

Jim Newton14:05:51

whats the benifit of making (def foo/x 100) fail if already in the namespace foo?

Alex Miller (Clojure team)14:05:40

what's the benefit of it succeeding?

Alex Miller (Clojure team)14:05:12

it's redundant - def always creates vars in the current namespace

Jim Newton14:05:03

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.

Alex Miller (Clojure team)14:05:42

well you brought it up :)

5
Jim Newton14:05:55

also when navigating in the editor ... when I jump to definition, i can see immeditely that it is foo/x rather than bar/x

Jim Newton14:05:39

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 ...)

jpmonettas14:05:53

if it is for grep support you can add a comment on the same line

Jim Newton14:05:29

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.

Jim Newton14:05:37

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.

Jim Newton14:05:08

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.

Jim Newton14:05:29

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)

jpmonettas14:05:01

you can already define symbols in other namespaces with intern

Noah Bogart14:05:08

you want to add that function to another namespace?

Jim Newton14:05:51

oops You're right my reponse is BS. I'll delete it.

Jim Newton14:05:07

how do I replace (in-ns ...) (re-definition ...) (in-ns back to original ns) with intern ?

Noah Bogart14:05:00

but use the other namespace

Jim Newton14:05:12

but defmethod can already take qualified names, right?

jpmonettas14:05:51

defmethod is adding a method for a defmulti that could be on a different namespace. The function defining thing here is defmulti

Jim Newton14:05:50

exactly, defining a method whose defmulti is in a different ns is a pretty common operation.

Jim Newton14:05:12

@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)))))

jpmonettas15:05:29

what I meant is that defmulti is the defining thing, defmethod could be called add-method

Jim Newton15:05:47

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???

Jim Newton15:05:40

This is a different discussion. I'll open another thread.

phill15:05:15

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.

phill15:05:52

Anyway @U010VP3UY9X thanks for putting in the Ask Clojure issue and please report back when it's ready

Jim Newton16:05:03

@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.

Noah Bogart16:05:22

i can't find a single instance of this in the wild using http://grep.app

Jim Newton16:05:36

should I push a copy of my code into the space that http://grep.app searches?

Jim Newton16:05:56

how do I do that?

dpsutton16:05:45

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?

Noah Bogart16:05:42

i think it's all public repos? i've never selected my projects for inclusion but they appear in results

Jim Newton17:05:50

my code is in my school's gitLab.

Jim Newton17:05:05

I probably should mirror it to github

Jim Newton17:05:43

ok, i've mirrored some projects to github

Jim Newton12:05:56

@UEENNMX0T I added several projects to github and made them public, yet the http://grep.app still finds nothing.

Noah Bogart12:05:50

interesting. i don't run http://grep.app and have no special insight

jf14:05:05

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

👍 1
john14:05:57

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.

john15:05:09

Not clojure specific, but definitely familiarize yourself with the latest https://owasp.org/www-project-top-ten/ recommendations.

john15:05:33

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.

john15:05:35

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

jf15:05:20

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

jf15:05:52

Tempel looks interesting. I am specifically interested in authentication and authorization

john15:05:47

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

john15:05:03

One thing we tend to do is minimize unnecessary dependencies (jackson cough)

john16:05:08

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.

john17:05:29

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

john17:05:24

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.

Jim Newton15:05:05

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???

Jim Newton15:05:21

When I look at the documentation of reduce I am confused. Is the documentation wrong, or am I reading it wrong?

Jim Newton15:05:04

the confusing issue is that (reduce + 10 '(1)) fortunately returns 11, but the documentation implies it will return 1

Jim Newton15:05:28

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.

Jim Newton15:05:58

In my opinion that is not clear (worse misleading) when reading the text.

dpsutton15:05:59

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

Jim Newton15:05:15

sorry, I don't understand IReduce and IReduceInit

dpsutton15:05:39

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

Jim Newton15:05:55

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.

Jim Newton15:05:06

I see now that my understanding was wrong.

Jim Newton15:05:42

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?

Jim Newton15:05:04

(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.

Jim Newton15:05:34

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.