Fork me on GitHub
#beginners
<
2021-08-12
>
Njeri01:08:06

I have a clj backend which the user uses to authenticate to an external server (via OAuth2.0). After the authentication process my backend gets an access token from the external server. How can I pass my access token to the cljs frontend so that I can use it from there?

seancorfield02:08:39

@njerigachoka The same way as you pass any other data back to the frontend? I assume the frontend has initiated some interaction with the backend to trigger this auth in the first place?

seancorfield02:08:58

(or maybe I'm misunderstanding what you're asking?)

olaf03:08:07

Hi, I need to find a way to easily pass context between (optionally) nested functions, so probably using macros. Here is a use case using simple JS. Create a red rectangle in a canvas. The canvas-context needs to be shared to effectively color the rectangle.

const canvas = document.getElementById("canvas")
const context = document.getContext("2d")
context.fillStyle = "red"
context.fillRect(10, 10, 50, 50)
Here with a dsl
(fill "red"
      (rect 10 10 50 50))
Here is my idea of fill and rect. Fill alone does nothing, but rect could be used by itself, or with stroke or something else. Same fill could have other functions inside.
(defn get-ctx []
  (.getContext (.getElementById js/document "canvas") "2d"))

(defmacro fill [style & body]
  (let [ctx (get-ctx)]
    (.-fillStyle ctx style)
    ~@body))

(defn rect [x y width height]
  (if ctx
    (.fillRect ctx x y width height)
    (.fillRect (get-ctx) x y width height)))

(rect 10 10 50 50)
But the compiler doesn't like it Unable to resolve symbol: ctx in this context

adi03:08:49

In the rect function, if ctx will expect ctx to be bound to something. There's no binding inside the scope of the function. Clearly there's no binding a the top level either.

emccue03:08:37

(defn get-ctx []
  (.getContext (.getElementById js/document "canvas") "2d"))

(defn rect [ctx x y width height]
  (.fillRect ctx x y width height))

(let [ctx (get-ctx)]
  (rect ctx 10 10 50 50))

emccue03:08:50

i mean, lets start with the semantically simplest version

emccue03:08:56

just thread the context

emccue03:08:48

now you want to be able to write

emccue03:08:16

(fill "red" (rect 10 10 50 50))

emccue03:08:38

where (rect ...) is in the context of the fill

emccue03:08:02

thats a syntactic goal, so its driven mostly by what you want

emccue03:08:29

so lets start with this

emccue04:08:44

(defn get-ctx []
  (.getContext (.getElementById js/document "canvas") "2d"))

(defn fill [ctx style with-fill]
  (set! (. ctx -fillStyle) style)
  (with-fill))

(defn rect [ctx x y width height]
  (.fillRect ctx x y width height))

(let [ctx (get-ctx)]
  (fill ctx "red" #(rect ctx 10 10 50 50))

emccue04:08:00

again, just starting with the "simplest" semantics

emccue04:08:54

your example of having fill be a macro is a good idea

emccue04:08:35

(defn get-ctx []
  (.getContext (.getElementById js/document "canvas") "2d"))

(defmacro fill [ctx style & with-fill]
  `(do (set! (. ctx -fillStyle) style)
       ~@with-fill))

(defn rect [ctx x y width height]
  (.fillRect ctx x y width height))

(let [ctx (get-ctx)]
  (fill ctx "red" (rect ctx 10 10 50 50))

emccue04:08:49

now we just need to get rid of this context everywhere

emccue04:08:24

if we notice that as a convention the context as a first argument is the pattern

emccue04:08:45

we can make a macro like this

emccue04:08:05

(threading-context (get-ctx)
  (fill "red" (rect 10 10 50 50))

emccue04:08:17

and just slap the context as the first argument of everything

emccue04:08:38

(require '[clojure.walk :as walk])

(defn get-ctx []
  (.getContext (.getElementById js/document "canvas") "2d"))

(defmacro fill [ctx style & with-fill]
  `(do (set! (. ctx -fillStyle) style)
       ~@with-fill))

(defn rect [ctx x y width height]
  (.fillRect ctx x y width height))

(defmacro threading-context [context draw-commands]
  (let [context-symbol (gensym "ctx")]
    `(let [~context-symbol ~context]
       ~(walk/postwalk 
          (fn [v] (if (list? v) (cons (first v) (cons context-symbol (rest v))) v))
          draw-commands))

(threading-context 
  (fill ctx "red" (rect ctx 10 10 50 50))

emccue04:08:19

or something like that

emccue04:08:24

its still kinda brittle

emccue04:08:33

and frankly its fine to just thread the context

emccue04:08:56

or maybe whitelist functions you expect in the draw context for the shorthand

emccue04:08:17

or maybe set a global variable and unset it when you leave if you aren't doing any async

emccue04:08:31

lots of options

olaf04:08:22

@emccue Thanks for the explanation. Is clear now. I kind of like a version without seeing the ctx, so I must opt to use a global variable for it and define simple functions.

Russell Mull16:08:42

You can do versions of this with nothing but functions, as well:

(defn fill [style]
  (fn [ctx]
    (set! (. ctx -fillStyle) style)))

(defn rect [x y w h]
  (fn [ctx]
    (.fillRect ctx x y width height)))

(def sequence [& ops]
  (fn [ctx]
    (doseq [op ops]
      (op ctx))))

(def red-rect
  (sequence (fill "red")
            (rect 10 10 50 50)))


;; And you can build upon it to do a nice scoped version, kind of like
;; what you're looking for:

(defn with-fill [style f]
  (fn [ctx]
    (let [old-fill-style (. ctx fillStyle)]
      ((sequence (fill style)
                 f 
                 (fill old-fill-style))
       ctx))))

(def red-rect 
  (with-fill "red"
    (rect 10 10 50 50)))

(defn get-ctx []
  (.getContext (.getElementById js/document "canvas") "2d"))

(red-rect (get-ctx))

🙌 3
Russell Mull16:08:01

but tbh, I would just thread the context.

emccue00:08:53

^ I like the function returning version a lot more than the one i pseudocoded

Prashant06:08:05

Hi, I was solving a problem where sequence of tuples needs to be generated like [[i,j]….] where 1 <= j < i <= n I had written below code that works great for small values of n

(defn pp? [n [i _]] 
  (>= n i))

(defn bla [[i j]] 
  (let [b (inc j)] 
    (if (>= b i) 
      [(inc i) 1] 
      [i b])))

(take-while (partial pp? 6) (iterate bla [2 1]))
;; ([2 1] [3 1] [3 2] [4 1] [4 2] [4 3] [5 1] [5 2] [5 3] [5 4] [6 1] [6 2] [6 3] [6 4] [6 5])
However for large values like 1e9, I get below error:
(take-while (partial pp? 1e9) (iterate bla [2 1]))
;; Error printing return value (OutOfMemoryError) at java.util.Arrays/copyOf (Arrays.java:3745).
;; Java heap space
Any pointers to make the code work with larger values would be greatly appreciated.

indy09:08:48

An alternative way to do it,

(defn some-tuples [n]
   (for [i (range 1 (inc n))
         j (range 1 (inc n))
         :while (> i j)]
     [i j]))

👍 3
Prashant09:08:04

@UMPJRJU9E Thanks for the alternate way, but I guess this would also suffer with same problem when n is large e.g. 1e9

indy09:08:30

Depends on how you're consuming it, if you're consuming the lazy seq one element at a time without keeping a ref to head, then previous elements will be GC'ed

Prashant09:08:45

@UMPJRJU9E Can you explain me this with the code ? It would be of great help

indy11:08:52

For example, the run! function will not keep a ref to the head of the sequence,

(run! do-something! some-lazy-seq)
and hence the elements that have been consumed by do-something! can be GC'ed.

🙏 3
tschady11:08:27

(for [i (range 1 1e9) j (range (inc i) 1e9)] [i j])

tschady11:08:31

works fine in my repl

Prashant12:08:15

@UMPJRJU9E Thanks a ton for the run! . Honestly hadn’t used it before.

simongray07:08:56

you could increase the memory of the JVM

Prashant09:08:54

That’s one option. I was exploring if it’s possible otherwise.

dgb2308:08:45

@prashantsinha what is the purpose of printing all the values there?

Prashant09:08:52

That’s a good question. This wouldn’t be of much use in real life. However, I am just curious if there’s a way to optimize my solution

dgb2309:08:10

if you can just keep working with the lazy seq you will be fine.

dgb2309:08:22

In my case I don’t get an error, my REPL just stops printing after a certain size and abbreviates the rest with a ...

Prashant09:08:41

I agree working in lazy sequence and not materializing such a huge number of elements would prevent the OutOfMemoryError. I am just curious about any further optimizations that I can make. PS: I was asked to handle values like 1e9 in a Clojure job interview after I was able to solve this question far too quickly 😄

dgb2310:08:32

Maybe you’ll find something interesting if you partition the data structure like so: (take 5 (partition-by first (iterate bla [2 1])))

dgb2310:08:58

n of each partition grows in an interesting way: 1 2 4 7 11 16…

dgb2310:08:44

or maybe something can be gained of thinking in terms of the partitions being lazy themselves

Prashant13:08:58

Thanks for the suggestion, but partitioning isn’t useful in this case

sarna19:08:46

hey, is there any good blog post/tutorial on how to work with Java classes in the REPL? the specific problem I have: some function I use returns sun.nio.fs.UnixPath and I'd like to figure out how to work with it without leaving the REPL and diving into Java docs - I don't know Java

Russell Mull20:08:06

In addition to Cora's advice, I also find https://clojuredocs.org/clojure.core/bean useful when doing interop exploration

sarna20:08:30

thank you, will use this one as well :)

sarna19:08:30

perfect, thank you!

sarna19:08:03

should I use this fork or the clj-commons one? I've always been confused by this library and turned to babashka's fs

sarna19:08:25

yep, this is the one that returns sun.nio stuff haha

R.A. Porter19:08:40

Actually, you probably should look at the Java docs. End users should not be interacting with any classes in sun.* packages. You should be interacting with the interface, java.*nio*.file.Path

💯 3
sarna19:08:17

ah! but how am I supposed to know that, if I'm not a Java programmer? :(

noisesmith16:08:50

I think it's good to mention that clojure.java.javadoc/javadoc (aliased to javadoc in any newly created clojure repl) will usually take you to the right docs for a given object or class

💯 2
borkdude19:08:01

to avoid further confusion, I removed the fork of clj-commons/fs from my github account

Ruslan Glaznyov19:08:08

Hi everyone! Is there any way to pretty format code in cursive? For example I have inline code: (reduce (fn [acc [key value]] (apply str acc (repeat (read-string key) value))) "" (apply assoc {} data)) Is there any way to formatting in cursive in new lines with ⌥⌘L ? Any Advice, guides articles would help. Thx!

borkdude19:08:58

@sarna.dev you can use babashka.fs/file to turn such a java.nio thing into a java.io.File

borkdude19:08:33

the sun classes appear because those java.nio things have OS-specific implementations so you see the implementation class name rather than their abstract class or interface

sarna19:08:53

that's useful, thanks everyone :))

sarna19:08:31

I've been thinking about this! felt too hacky though haha

seancorfield19:08:47

If you're doing a lot of heavy lifting with files, you'll be doing a lot of Java interop, and then you'll find a world that either expects File everywhere or a different (more modern Java) world that expects Path everywhere 🙂

dpsutton19:08:04

Docstrings should tell you. And often a main entry point function will be string and then path or file everywhere downstream of that

sarna19:08:21

@seancorfield good to know! I was surprised by the amount of Java in my Clojure - I'm writing a CLI app, I thought that'd be an easy first project. wasn't expecting to be slapped in the face with Java objects straight away :D

noisesmith16:08:41

clojure doesn't hide the underlying implementation much, even functions are objects that implement clojure.lang.IFn

borkdude19:08:29

babashka.fs functions take strings, io Files or nio Paths so as long as you combines fns from that lib, you should not really notice what type of Java class you're dealing with

👍 2
borkdude19:08:18

unless you're passing it to another API. I think most APIs still expect a java.io.File so in that case you can use fs/file to coerce

seancorfield19:08:36

@sarna.dev Are you mostly copying files and directories around or...?

ghadi20:08:47

@mailmeupinside keep in mind that what the REPL prints out is the class name for an object, which might represent a non-public implementation class. In your example a sun.nio.fs.UnixPath is such a private class, and the public interface intended to be used is java.nio.file.Path

ghadi20:08:49

sometimes it's a bit tricky to determine the public API for an opaque private object. One trick to inspect available API is (supers (class the-object))

ghadi20:08:02

Sometimes I wish the REPL didn't print the private names, and instead the public capabilities of an object

ghadi20:08:16

clojure.reflect also shows a bunch of private, uncallable details

sarna20:08:44

@seancorfield creating new files and directories, writing and reading from them. and also some copying/swapping files

sarna20:08:51

@ghadi that's very useful, thank you! I will write that down haha

sarna20:08:54

stealing this!

sarna20:08:07

gotcha! thank you so much 🙇

2
Alex Miller (Clojure team)20:08:37

or just (clojure.java.javadoc/javadoc java.nio.file.Path) :)

💯 5
Alex Miller (Clojure team)20:08:45

should take you to the same page

ghadi20:08:02

you still gotta know you have a Path in hand

ghadi20:08:15

which isn't obvious from #object[sun.nio.fs.UnixPath 0x106faf11 "/usr/local"]

Alex Miller (Clojure team)20:08:03

supers is pretty useful to get some ideas though

Alex Miller (Clojure team)20:08:34

user=> (supers (class (.toPath (jio/file "deps.edn"))))
#{java.lang.Iterable java.lang.Comparable java.nio.file.Path java.lang.Object java.nio.file.Watchable}

Alex Miller (Clojure team)20:08:34

and for direct extension chains

user=> (parents (class (.toPath (jio/file "deps.edn"))))
#{java.nio.file.Path java.lang.Object}

Alex Miller (Clojure team)20:08:15

(parents works with multimethod tag hierarchies too)

Alex Miller (Clojure team)20:08:25

why should classes have all the fun?

2
José22:08:10

["X == other.X" " &&" "Y == other.Y" ";"] -> ["X == other.X &&" "Y == other.Y;"] how will you do this vector transformation?

dpsutton22:08:28

can you say in words the rules? it appears from this snippet that you want to join the first two elements in a vector of strings?

José22:08:41

i want to join consecutive pairs

Russell Mull22:08:30

Take a look at the partition, str, and into functions

dpsutton22:08:20

(let [in ["a " "b " "c " "d "]]
  (mapv (fn [strs] (apply str strs)) (partition-all 2 in)))

José22:08:14

thanks @dpsutton I wasn't sure what to look for

seancorfield22:08:44

apply str always looks odd to me -- I find clojure.string/join easier to read: (clojure.string/join strs) (although I always (:require [clojure.string :as str]) and then it would be (str/join strs))

dpsutton22:08:18

i used to think that but when i looked at the source of str i put that fear/worry to bed

seancorfield22:08:59

Oh, you mean that str/join without a separator is apply str?

dpsutton22:08:03

ah, i used to think that clojure.string/str would be "optimized" to handle a collection better but str is basically the same. i always assumed that the one that called join would somehow be better

dpsutton22:08:10

yeah exactly.

seancorfield22:08:26

str/join is still more readable than apply str for most folk.

quoll13:08:45

TIL I’m not “most folk” 🤪

😂 6