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)
    [email protected]))

(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

adityaathalye03: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)
       [email protected]))

(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)
       [email protected]))

(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
tws11:08:27

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

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

Cora (she/her)19:08:21

you could use clojure.reflect to inspect it, I guess? https://clojuredocs.org/clojure.reflect/reflect

sarna19:08:30

perfect, thank you!

Cora (she/her)19:08:41

it might be worth looking at this, too, if you want to see how to do filesystem stuff [[DELETED]]

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

Cora (she/her)19:08:18

@sarna.dev and if you don't want to muck about with a path specifically for some reason you can always just call str with the object and get the string path back

👍 2
Cora (she/her)19:08:18

I kind of wish it always returned strings, honestly

sarna19:08:31

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

Cora (she/her)19:08:51

a lot of things expect string paths ¯\(ツ)

Cora (she/her)19:08:08

and in fact you'll get weird errors if you pass them a Path object

Cora (she/her)19:08:43

nothing impossible to decipher, just kind of yet-another-str-call-on-path error when you're doing some extensive fs stuff

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

Cora (she/her)20:08:24

(require '[clojure.pprint :as pprint]
         '[clojure.reflect :as r]
         '[ :as io])
(pprint (->> "my-file-path"
             io/file
             .toPath
             r/reflect
             :members
             (filter
               #(-> % :flags :public))))

😍 2
Cora (she/her)20:08:30

if you wanted just the public members

sarna20:08:54

stealing this!

Cora (she/her)20:08:28

but it is just soooooo much easier to load this up and look at "Instance Methods" https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/Path.html

Cora (she/her)20:08:49

and then use clojure's java interop to call things on it https://clojure.org/reference/java_interop

sarna20:08:07

gotcha! thank you so much 🙇

2
alexmiller20:08:37

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

💯 5
alexmiller20:08:45

should take you to the same page

Cora (she/her)20:08:48

I didn't know about that!

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"]

Cora (she/her)20:08:19

ahhh right, yes, that

alexmiller20:08:03

supers is pretty useful to get some ideas though

alexmiller20: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}

Cora (she/her)20:08:57

user=> (:bases (r/reflect (.toPath (io/file "/foo/bar/baz/foo"))))
#{sun.nio.fs.AbstractPath}
user=> (:bases (r/reflect sun.nio.fs.AbstractPath))
#{java.lang.Object java.nio.file.Path}

Cora (she/her)20:08:17

that's kind of neat, too

alexmiller20:08:34

and for direct extension chains

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

Cora (she/her)20:08:01

ohhhh I like that

Cora (she/her)20:08:08

I'm learning all kinds of things

alexmiller20:08:15

(parents works with multimethod tag hierarchies too)

alexmiller20: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