Fork me on GitHub
#beginners
<
2022-07-27
>
Richie00:07:04

(.requestAnimationFrame js/window (fn [ts] (swap! db assoc :ts ts)))
How can I turn this into a fn that calls itself in the callback? I don't want to move it to its own defn.
(defn im-not-happy
  [db]
  (.requestAnimationFrame js/window (fn [ts]
                                      (swap! db assoc :ts ts)
                                      (im-not-happy db))))
Thanks!

phronmophobic01:07:29

You can give an anonymous function a name to call itself recursively (although I guess it's not really anonymous anymore, just "keeps mostly to themself")

(fn happy [db]
  (.requestAnimationFrame js/window (fn [ts]
                                      (swap! db assoc :ts ts)
                                      (happy db))))
Why not move it to its own defn? You have to give it a name either way.

Richie01:07:55

Oh right, thanks!

((fn happy-enough []
       (.requestAnimationFrame js/window (fn [ts]
                                           (swap! db assoc :ts ts)
                                           (happy-enough)))))

👍 1
Richie01:07:35

I'm hacking away and I want something that I can inline where I'm working. It's for ergonomics really. I'm not being too rational.

👍 1
phronmophobic02:07:01

Or if you wanted to get fancy:

(((fn [f]
    (fn []
      ((fn [f']
         (f (partial f' f')))
       (fn [f']
         (f (partial f' f'))))))
  (fn [happy-enough]
    (.requestAnimationFrame js/window (fn [ts]
                                        (swap! db assoc :ts ts)
                                        (happy-enough))))))

ahungry02:07:56

if you aren't using recur, you could blow the stack with unbounded recursion - not sure if that's the case with JS engine, last I knew, nodejs at least had dropped TCO support - not sure if browsers ever supported it

phronmophobic02:07:02

@U96DD8U80, that doesn't apply here because it's not being called directly, but via a callback

ahungry02:07:44

Ah, thanks, I just read "a fn that calls itself in the callback" - so it isn't recursing at all, its just referencing a first class function/callback by a fn label instead of a def or let assignment? Or are you saying that if a function recurses on itself, it is free of stack concerns if it has first been referenced/elevated to a callback?

phronmophobic02:07:50

I would probably consider it mutual recursion, but the A calling B which calls A doesn't infinitely consume the stack because .requestAnimationFrame 's behavior and event loop acts similar to a trampoline

Drew Verlee03:07:55

Is trampolining a concept i missed in CS class? It comes up from time to time and im guessing there not referring to the idea of bouncing up and down.

phronmophobic04:07:02

trampolining is a common technique for implementing recursion without blowing the stack. It's sort of related to bouncing up and down. If you run a recursive algorithm the normal way (without tail call optimizations) the stack will continue to grow as it recurses. If you run the same algorithm via a trampoline, the stack will push and pop (ie. bounce) for each recurse. Not all recursive algorithms can leverage tail call optimization. The example that kicked off this thread doesn't really have anything to do with trampolining, but it is a recursive procedure that won't blow the stack. Tail call optimization doesn't apply either. The reason the recursive calls with .requestAnimationFrame don't consume the stack is that the next task isn't pushed onto the stack, but is enqueued and processed by an event loop. I'm not totally sure I'm explaining this well.

phronmophobic04:07:33

I'm having trouble finding a great writeup on the internet, but this stackoverflow answer isn't too bad: https://stackoverflow.com/a/27704484

phronmophobic04:07:25

Actually, the wikipedia article on tail call is really good: https://en.wikipedia.org/wiki/Tail_call

👀 1
Drew Verlee05:07:43

Thanks for the reply. I'm check this in the morning! :)

Jim Strieter07:07:09

What is the best way to get the git hash inside Clojure? Motivation is that I have a database that records output from my program, and it would be really nice if Clojure could retrieve that hash from OS, and then store in database.

delaguardo07:07:45

https://clojuredocs.org/clojure.java.shell/sh there is a provided namespace to do system call.

delaguardo07:07:26

but it depends how you run your application. from prebuild jar? or you checkout the repository and run something similar to lein run?

jumar08:07:13

One way is to include this information in a text file when you generate the final artifact that's being run (you can simply use git via make for example). Or you can add it to MANIFEST.MF, leveraging clojure.java.shell/sh - example for leiningen's :manifest https://gitlab.com/technomancy/leiningen/blob/master/sample.project.clj#L455-459

:manifest {"git-commit-sha" ~(fn [_project] (:out (clojure.java.shell/sh "git" "rev-list" "head" "-1")))}

rolt12:07:28

for a pure java solution you can use jgit (there is a clojure wrapper too)

emccue15:07:44

you can do the sh call in a macro to have the hash embedded in to build

ahungry15:07:11

or copy .git/HEAD - its just a file containing the head (although if you aren't in detached head state, it might just give you the branch name, which could still be valuable) - slurp .git/HEAD in a macro, no need to shell out or have git on the build system

Jim Strieter05:07:58

I ended up doing this:

(defn get-git-hash [] (subs (:out (sh "git" "log")) 7 47))
Works nicely

Jim Strieter05:07:24

I forgot to mention that the solution needs to work at runtime. Every time lein run is called, I want the program to get the latest hash so that hash can be associated with program output in the database. Works nicely.

Jim Strieter05:07:30

Thanks everybody!

sheluchin14:07:20

Since nil is falsey, (if (:x {}) ...) is sufficient, or is that bad style?

delaguardo14:07:26

depends on what you are check for. are you check if the map has a key or if the value if truthy?

sheluchin14:07:58

If it has a key, where I expect the value to be a string, not nil or false.

ghadi14:07:33

is your {} a stand-in for a real map?

delaguardo14:07:56

in general this is fine. But when there are more constraints it is better to explicitly check using predicate: (if (string? (:x {,,,})) ,,,)

ghadi14:07:30

if so, it is absolutely common/idiomatic to do

(if (:x a-map)
   ....)
or
(if-let [x (:x a-map)] ...)

sheluchin14:07:36

> is your {} a stand-in for a real map? Yes.

sheluchin14:07:58

Okay, thank you 🙏

zakkor16:07:48

I get this error when using my macro in cljs

[Figwheel:SEVERE] clojure.lang.ExceptionInfo: failed compiling constant: rc.core$defc$fn__9159$update__9160@3552a96e; rc.core$defc$fn__9159$update__9160 is not a valid ClojureScript constant.
works ok when I macroexpand it on the clojure side, but not in cljs
clj
(defmacro defc
  [name args body]
  `(defn ~name
     ~args
     ~(prewalk
        (fn [n]
          (if (and (vector? n)
                   (-> n
                       first
                       clojure.core/name
                       lower-first?))
            (if (some #{(first args)} n)
              (let [update (fn [v]
                             (transform (mapv #(if (= % (first args)) v %) n)))]
                (conj n {:update update}))
              n)
            n))
        body)))
clj
(prn (macroexpand '(defc simple [x] [:Simple [:div "hi" x]])))

hiredman16:07:04

you are emitting a function object from the macro, instead of code that evaluates to the function object

hiredman16:07:50

clojure will in some cases allow you to get away with this, but it is bad practice, and clojurescript cannot get away with it because macros are written in clojure and expanded away and then the compiled code is run in a different runtime so they cannot share objects like that

zakkor16:07:37

Unfortunately I am not sure how to fix: what would it look like if the snippet correctly had code that evaluated to the function?

hiredman16:07:41

you can see update is bound to a function, and then that function is being cons'ed into the expression that is returned from the macro

hiredman16:07:45

update should be bound to an expression that when evaluated results in a function

zakkor16:07:57

This is bending my brain a bit 😅 I can't figure out what an expression that evaluates to a function would look like. The only thing that comes to mind is wrapping it inside another function, but that would just be another function object…

zakkor16:07:57

update does not really need to be a closure in this instance - if I make it a separate defn and move it outside of the macro, would that work?

zakkor17:07:56

hmm, it didn't really work, but quoting the function seems to work :thinking_face:

hiredman17:07:22

if you type (fn []) into the repl vs. '(fn []) what is the difference

zakkor17:07:53

Hmm, I see, one is a function and the other is code that will evaluate to a function

zakkor17:07:16

Does that mean I need to (eval) the function before I try to use it on the other side?

hiredman17:07:16

what are writing is a macro

hiredman17:07:29

the code it emits will be evaluated

hiredman17:07:27

like, what is the difference between (defmacro m [] (fn [])) and (defmacro m [] '(fn []))

stantheman21:07:50

Ok old security consultant here, trying to keep my mind supple! Last year I delved into latest Object Oriented design and programming but after much research still feel the Object wrappers causes much difficulty in large productive software systems. Cue hook into Clojure immutability benefits (and security design spin offs).

ahungry21:07:26

I think OOP/OOD and immutability are orthogonal concerns, you could make an immutable system that keeps logical structures using OOP vs internal mutable state, it just happens that idiomatic clojure stresses using plain old maps/data structures, vs a traditional OOP hierarchy

ahungry21:07:20

10 functions that apply to 10 data structures, or 100 functions that apply to 1 data structure (or whatever that quote was) - either way, your complexity space is 100 - biggest design win imo is just keeping systems as small as possible/logically separated (so, microservices in web space, piped CLI utilities vs option heavy in console space, and event driven/separation of GUI front ends/games and the back end processing to handle the events)

seancorfield21:07:26

It's true that you can avoid immutability in OO languages (to varying degrees depending on the language) but it's a lot more work than Clojure's approach -- mostly because Clojure focuses on "plain ol' data" and separating data and functionality. I'll be interested to see what @bdstaniforth is actually going to ask about OO vs FP regarding security... 🙂

😁 1
InvictedPrometheus22:07:21

Ref Alan Perlis Epigram #9 and the rest because most of them are timeless: https://users.monash.edu/~lloyd/tildeMisc/1982/Perlis-Epigrams.html

InvictedPrometheus22:07:46

The elevator pitch for OOP is modeling real world objects in code. However, most of those distinctions are arbitrary and we lose information about them and their purpose over time. Object wrappers create high cognitive overhead over time and space. Cue design patterns that try to name objects after the functions they provide. Add in 20 years of Spring and you find classes like https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/support/AbstractAnnotationConfigDispatcherServletInitializer.html. Then we ask the question, “but how do our objects behave?

stantheman21:07:07

Part 2, however I feel Clojure error returns still leaves much conjecture as to the language cause of the error, which together with the ())())) issues slows understanding. Notwithstanding this there are clearly some fantastic benefits. After a few weeks learning here is a prime number clojure version I created based on the description of Sieve of Eratosthenes by Dustin Campbell and Melissa E. O'Neill. I wonder about my breakout of fn Sumkey and repetition of recursive call back to erato. Perhaps it shows some non functional background - C TAL Java? Any constructive critique is welcome.

(defn sumkey                                                                   │;   returns the result of applying f to the first 2 items in coll, then
   91   [sie sumks vfact cand]                                                       │;   applying f to that result and the 3rd item, etc. If coll contains no
   92   (if (seq sumks);(not (seq sumks)); (18) [3] 15 (rest sumks)))                │;   items, f must accept no arguments as well, and reduce returns the
   93     (recur (assoc sie (first sumks)                                            │;   result of calling f with no arguments.  If coll has only 1 item, it
   94                    (cons (first vfact) (sie (first sumks)) ))                  │;   is returned and f is not called.  If val is supplied, returns the
   95            (next sumks) (next vfact) cand)                                     │;   result of applying f to val and the first item in coll, then
   96     (dissoc sie cand)))                                                        │;   applying f to that result and the 2nd item, etc. If coll contains no
   97                                                                                │;   items, returns val and f is not called.
   98 (defn erato                                                                    │; --------------------------------------------------------------------------------
   99   "is cand key in table: no- add it keyed by cand'sqr, add to primes           │; doc (word): assoc
  100   : yes- add cand to each val(factor) if sum is key add val to key's facts     │; -------------------------
  101     else enter key sum val(factor) for each sum & val                          │; clojure.core/assoc
  102     remove orig foun

seancorfield21:07:16

Hard to read that code with the long lines and so much text. Is that in a GitHub repo you can link us to?

seancorfield21:07:30

(also the code doesn't seem complete)

ahungry21:07:48

I know it's sort of the clojure way (esoteric abbreviations for things that those "in the know" may read without issue), but I would make it "noob friendly" and rename sie to sieve, and cand to candidate (as is, cand looks like cond, and could be mistaken as such, or as a typo) - theres a balance between overly verbose java enterprise bean names, and apl/j-lang terseness - I would extend this same critique to some of the built in functions in clojure though

seancorfield21:07:19

Also, the line numbers and the | characters mean we can't even copy'n'paste this into our own editors to try it out.

seancorfield21:07:58

re: sie -- I read it as German "they"... I didn't even make the connection with sieve -- good point @U96DD8U80!

stantheman21:07:36

Yes sorry that was another issue fixing up neovim and conjure to allow decent cut and paste was awkward, sorry.

seancorfield21:07:57

It's much easier to get code review feedback if you have a GitHub repo that folks can look at -- copy'n'paste into Slack is problematic for many reasons (imagine being on a phone and someone pastes a code listing into a channel you're trying to read).

seancorfield21:07:37

And don't worry -- there are some old 'uns here too, like me for example (turned 60 just a few weeks ago) 🙂

stantheman21:07:47

(defn erato │; f should be a function of 2 arguments. If val is not supplied, 99 "is cand key in table: no- add it keyed by cand'sqr, add to primes │; returns the result of applying f to the first 2 items in coll, then 100 : yes- add cand to each val(factor) if sum is key add val to key's facts │; applying f to that result and the 3rd item, etc. If coll contains no 101 else enter key sum val(factor) for each sum & val │; items, f must accept no arguments as well, and reduce returns the 102 remove orig found cand entry" │; result of calling f with no arguments. If coll has only 1 item, it 103 ([] (erato {} 2'() )) │; is returned and f is not called. If val is supplied, returns the 104 ([sie cand facts ];prime-nums] │; result of applying f to val and the first item in coll, then 105 (if-let [facts ( sie cand)] │; applying f to that result and the 2nd item, etc. If coll contains no 106 (erato │; items, returns val and f is not called. 107 (sumkey sie (map (partial + cand) facts) facts cand) │; -------------------------------------------------------------------------------- 108 (inc cand) facts) │; doc (word): assoc 109 (lazy-seq (cons cand │; ------------------------- 110 (erato │; clojure.core/assoc 111 (assoc sie (* cand cand) (list cand)) │; ([map key val] [map key val & kvs]) 112 (inc cand) │; assoc[iate]. When applied to a map, returns a new map of the 113 facts)))) )) │; same (hashed/sorted) type, that contains the mapping of key(s) to 114 │; val(s). When applied to a vector, returns a new vector that 115 (= (drop 1(take 156 primes)) (take 155 (erato))

seancorfield21:07:43

@bdstaniforth That's really just not readable in Slack. Can you put this up on GitHub or somewhere and provide a link?

seancorfield21:07:00

That will give us a better chance of helping you.

stantheman21:07:47

Yes sorry saw the code option in slack entry and assumed cut and paste with that would be good from neovim clearly not data centric like clojure! I have a github account so Ill look at that put it onGit sorry to be overambitious for Slack thnx for trying.

stantheman22:07:02

Main bit is fn Sumkey and erato rest is simple divisors implementation of primes...

seancorfield22:07:37

The first thing I notice is that you are passing facts into erato but then shadowing it with a local binding here https://github.com/StanTheBear/cloj/blob/main/prime-sieve.clj#L23

stantheman22:07:58

Security wise the immutabilty aspects were attractive after working on OO based systems and SPARC and PROLOG tree state theorem checking. Yes that is related to my concern that erato uses that to fork the recursive call back on erato I was wondering if if I could make the difference a variable into a common recursive call but felt that was where my imperative history was leading my a'way?!

seancorfield22:07:05

In terms of readability and understandability, naming and non-idiomatic layout are probably your two biggest issues right now. I'd have to refresh my old memory on how the sieve algorithm is meant to work in order to see how well the code matches it -- because I can't tell what the code is really trying to do based on what I'm looking at right now.

stantheman22:07:03

Thanks Sean yes sorry legibility probably reflects my hesitancy adapting to functional here is a link to the description of the composite table form of the sieve I was trying to use https://web.archive.org/web/20150710134640/http://diditwith.net/2009/01/20/YAPESProblemSevenPart2.aspx

seancorfield22:07:30

Here's an update with more idiomatic formatting and (I think) clearer names: https://github.com/seancorfield/bernard-staniforth/blob/main/src/prime_sieve.clj

seancorfield22:07:24

(that's also now structured into a project with deps.edn and the code in a src folder, so it can be used with the Clojure CLI and so more editors will know how to start a REPL in it -- I use VS Code / Calva, for example)

stantheman22:07:07

Grand thanks Sean, looks like Clojure now ! I'll have to digest it tomorrow ! But perhaps add-candidates shouldn't be conjoined into erato.

seancorfield22:07:06

Here's how I'd probably implement the algorithm as described in that link:

(defn erato-filter
  ([] (erato-filter (iterate inc 2)))
  ([unfiltered]
   (let [prime (first unfiltered)]
     (cons prime
           (lazy-seq
            (erato-filter (remove #(zero? (mod % prime)) (rest unfiltered))))))))

(comment
  (take 10 (erato-filter))
  )

seancorfield22:07:18

iterate is lazy. remove is lazy. So you start with all natural numbers from 2 on up. And at each step, you pull off the first number (it's prime) and the remove all of its multiples (lazily).

stantheman11:07:33

Wow the (remove anon fn is splendid looks like solution to my my concern that erato used the if-let to fork the recursive call back on erato in two versions; I was wondering how to make a common recursive call.

stantheman11:07:40

Yes and regarding the security and /or safety-critical aspects I wondering if current research had identified if Clojure immutable & namespace points aided validation / verification with regard to issues in generating pre-conditions, loop invariants ...?? Or perhaps it just moves the difficult aspects around!

seancorfield21:07:47

@bdstaniforth Please use threads and don't post huge code listings. See comments in the threaded replies to your previous posts.

ahungry21:07:41

And don't feel shame for seeing this, I got the same heads up a few months back, as threads were not used in my prior slack-usage on different org slacks 😄 - but they really help with readability (main channel and contextual)

1