Fork me on GitHub
#beginners
<
2023-08-28
>
Noyal Jose07:08:12

hey, i want to make my first beginner project based on pedestal(i have never made any executables in any other language before), what are some small projects that will help me understand the basic working of pedestal and the web. I was thinking of making a tic-tac-toe multiplayer game. If that's too big i wanted to start with some smaller projects to get familiar with "real world coding" in clojure, my experience until now have been with clojure texts and exercism. tl;dr - i want to make my first project in web using pedestal, what projects will help me learn pedestal and how projects are made in clojure

Stef Coetzee07:08:40

Have you possibly come across @U05PG8XLSNS’s series which makes use of Pedestal? https://youtube.com/playlist?list=PLRGAFpvDgm2ylbXYIjvu3kI426zAP_Lqc

mrnhrd08:08:20

have you looked at the guide on http://pedestal.io ? It's geared towards people who have never used clojure before, though it's mostly about creating an API. When you say multiplayer, do you mean two people on different machines play against each other using a browser? Do you have web-dev experience? If this is supposed to run in a browser, you will need to create a web-page as the interface using at least html and http (the latter in order to send stuff from the browser to the actual clojure app). If it's supposed to look good, you will also need to learn CSS. I would definitely suggest as an intermediate goal that you get it working on your local machine: Be able to play tic tac toe against yourself by calling localhost:6050 in the browser. If this is the first time you're doing web/browser things, that will take plenty of time (most of it learning html/http and how that hangs together) and to be clear, will not look pretty or fancy (html tables or three rows of buttons would be my approach, which is in no way how this would be done in a "real" game). Multiplayer will require some deployment on the internet and having to start thinking about game sessions and stuff like that. In terms of game logic, tic tac toe is a good candidate.

Noyal Jose09:08:05

@U05N15QDUHX hey yes, ik basic html and http. I'm not aiming for any aesthetically pleasing UI for the game, I'm solely building this game for learning how to make a project in Clojure/pedestal for the first time. Should i learn and use tests and clojure.spec while building every project.

Noyal Jose09:08:33

@U03CPPKDXBL hey i never seen this video series before, is there any pre-requisites i need to know before seeing this video. I just know basic concepts from Brave Clojure book

Stef Coetzee09:08:51

A key ingredient to effective learning is dividing and conquering the material you wish to learn. I wouldn't be too worried about tests and specs when just trying to get something live on your own machine for the first time.

Stef Coetzee09:08:32

Brave Clojure is a good start. You might want to check out the https://clojure.org/guides/deps_and_cli if you're coming from Leiningen and wish to learn how to use deps.edn. Don't be afraid to start over, too. Create a new directory and take another stab at the material.

mrnhrd10:08:06

Well any serious project of certain size that has to be robust and maintainable (including changes in requirements and therefore code) will need some preferably high degree of preferably automated testing. This is not clojure-specific, but just the way apps are built. The robustness notion is also the reason behind spec's existence, so you would do well to use spec if that is important to you. Note that "Serious" here to me means "actual livelihoods of people or organisations depend on this code doing the right thing at acceptable performance". It is not as critical not important at all imho to have it for hobbyist or learning projects or for things that are short-lived, very small or just convenience tooling for devs. That's not to say it can't be useful, but more like it's up to you if you want it. With regards to make-a-project, the biff tutorial (where you build a basic functioning chat application) actually has a section about how to deploy your app on the web which is cool imho: https://biffweb.com/docs/tutorial/deploy/

2
Δημήτριος Φκιαράς08:08:16

Hello, does Test Driven Development fit well in Clojure’s philosophy? Meaning writing first the test, then a function that doesn’t pass the test, and then refactoring?

delaguardo08:08:53

Yes, why wouldn't it) However, you will see a lot of opinions about how repl-driven (or experimental) development is better than test-driven.

❤️ 2
teodorlu08:08:14

I’d say yes. I like to do something like this: 1. Explore the behavior I want with examples in the REPL 2. When I’m happy with the design, make a test for it. I run bin/kaocha --watch --fail-fast in a terminal window next to my editor while I’m working, using the #CCY2V0U6A test runner. I like kaocha because it has good messages for passing and failing tests, plus is really fast with --watch — avoiding JVM process restarts when loading new code.

❤️ 2
Δημήτριος Φκιαράς09:08:42

Thanks a lot guys. I think I will try to combine both. I listened to this talk: https://www.youtube.com/watch?v=r5i_OiZw6Sw It seems generative testing is very powerful.

mrnhrd09:08:24

Difficult question in a sense. Arguments in favor: 1. Pure functions lend themselves exceptionally well to unit testing, pure data in, pure data out. 2. It's very easy to turn REPL interactions into a unit test. 3. If you look towards clojure.spec (particularly function specs), you get a way to express "if the arguments of this function are like this, the output must look like this". This is a big part of why people do unit-testing and TDD. Arguments against doing TDD in clojure: 1. you have the same problems with TDD in clojure that you have elsewhere: you have to have identified a clear scope for your code. If you have done TDD in java, you probably have encountered the case where a refactoring actually necessitated changing the test code too, partially defeating the point of the entire exercise. 2. imho a big driver of TDD in java comes from the fact that it's actually impossible to just run a simple bit of code to see what it does in java except in a unit test. if you have a repl and you code in pure functions (which don't require an elaborate test setup), that's that problem solved and makes TDD kinda pointless. 3. imho java's static nature and its IDEs lend themselves more to simple refactorings where you almost don't need to turn on your brain from a is-this-this-runnable-code standpoint, and one interpretation of TDD that I just came up with is that good test coverage is supposed to allow you to not need to turn your brain on from a actual-requirements standpoint as well. Clojure is less amenable to the first kind of simpler refactorings imho (from my also-beginner's pov).

❤️ 8
nikolavojicic09:08:43

I like with-test during TDD style dev so that I don't have to jump to other window all the time... https://clojuredocs.org/clojure.test/with-test

4
❤️ 2
mrnhrd10:08:44

@U011LEAEURE woah that's awesome.

carnundotcom10:08:27

https://github.com/hyperfiddle/rcf is a really nice stepping stone up from a purely REPL-driven, 'Rich comment' workflow (https://betweentwoparens.com/blog/rich-comment-blocks/) imo. To @U05N15QDUHX's point 2 above.

☝️ 6
❤️ 2
seancorfield15:08:14

Another option is https://github.com/matthewdowney/rich-comment-tests/ (we tried RCF but it didn't work well with a REPL-driven approach if you're also trying to use it with test runners - RCT solved that by taking a different approach to how it works with test runners and we like it).

❤️ 4
Δημήτριος Φκιαράς19:08:58

Thank you for all this high-quality suggestions!

shiyi.gu18:09:56

Can repl-driven works with spec smoothly like tests? Writing repl tests are very intuitive and we can accumulate them incrementally. What about spec? I guess writing spec loop may be longer, it comes at the end when finalising the functionality of the code. But usually we did do some tests covering the scope of the function when we start to create another function based on this existing function. Could you please share some experience for this part? (Repl and tests happened so naturally, I believe everyone do them all the time from the beginning. However, I never spent time to learn and practice using spec. Maybe I learn Clojure too casually.)

Alex Miller (Clojure team)18:09:20

specs are designed to work when defined either before or after the code and it is very possible to use them while developing and in instrument mode. I do not typically use them as I develop the very first version of code as things are changing rapidly, they are better as the rate of changes slows down

shiyi.gu18:09:54

Thank you for sharing it! I think I should try it myself to have more concrete reflex. My question comes from the land "Why doesn't specs come naturally like tests" "Can we accumulate rough scopes during the development to help produce specs? I believe we produces the fragments (tests) during the whole process".

Alex Miller (Clojure team)19:09:36

specs are most useful when they are describing information - domain attributes and collections thereof (not higher-order type stuff). when you are developing the code, you are probably also most knowledgeable about the data so it's a good time/way to record that information

Alex Miller (Clojure team)19:09:08

specs can also be vague if you use things like any? to defer fine-grained bounds

quan xing13:08:03

In next.jdbc may I use insert-mulits function insert all cols like this?

(jdbc-sql/insert-multi! (db/ds) :mytable
                          [:*]
                          [["a" "b" "c"]])

mrnhrd14:08:02

Looking at the source code in jdbc.next, I doubt that this query would have the intended effect, but I am not sure (I'd run it through a debugger, it's not too many calls). Just to be clear, if you have a table with col1 col2 col3 after your insert you want one row where all 3 cols have the value "a", one where all cols are "b" and so on?

seancorfield16:08:45

You must name the columns for the next.jdbc.sql helper functions. If you want to insert just column data without names, look at HoneySQL for generating the SQL (and use the result with jdbc/execute-one! and {:return-keys true}).

seancorfield16:08:58

(require '[next.jdbc :as jdbc] '[honey.sql :as hsql])
(jdbc/execute-one! (ds/ds) (hsql/format {:insert-into [:mytable] :values [["a" "b" "c"]]}) {:return-keys true})
The output from that hsql/format call is
["INSERT INTO mytable VALUES (?, ?, ?)" "a" "b" "c"]
(also note: I'm more likely to see HoneySQL questions in #C66EM8D5H and next.jdbc questions in #C1Q164V29)

seancorfield17:08:24

Some people prefer to use the HoneySQL helper functions to build the DSL:

(require '[honey.sql.helpers :as h] '[honey.sql :as hsql])
(-> (h/insert-into :mytable) (h/values [["a" "b" "c"]]) (hsql/format))
That is equivalent to the call above.

quan xing06:08:06

ok. I got it . Thanks @U04V70XH6

fappy18:08:45

hi 🙂 Is there something special about Thread/sleep which with-redefs does not like?

(defn my-fn []
  (java.lang.Thread/sleep 5000)
  :result)

(deftest my-fn-test 
  (with-redefs [java.lang.Thread/sleep
                (constantly :does-not-matter)]
    (is (= :result (my-fn)))))
Unable to resolve var: java.lang.Thread/sleep in this context
Currently both bits of code are in the same namespace/file…

respatialized18:08:50

Java methods are not Clojure vars, so you can't use macros and functions intended to manipulate vars to manipulate Java methods. You could create a small wrapper function around Thread/sleep with defn, which would allow you to dynamically redefine the behavior of your function in the context of your test.

gratitude-thank-you 2
shiyi.gu20:08:58

The clojure-lsp consumes a huge amount memory of my laptop like 50GB. Can this be considered as an issue?

ericdallo22:08:59

that's weird, clojure-lsp has a limit of 2GB per process (project), check if you have multiple clojure-lsp process running somehow

🙏 2
shiyi.gu15:08:01

Thank you for sharing that. I havn't had time to verify it. I will pay more attention to its behaviour when I will use it.

Harry20:08:09

Hi all, trying to setup pdf-lib in clojurescript but my promise interop skills are lacking. This is the js code I am converting:

const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica) 

  const pages = pdfDoc.getPages()
  const firstPage = pages[0]
  const { width, height } = firstPage.getSize()
  firstPage.drawText('This text was added with JavaScript!', {
    x: 5,
    y: height / 2 + 300,
    size: 50,
    font: helveticaFont,
    color: rgb(0.95, 0.1, 0.1),
    rotate: degrees(-45),
  })

  const pdfBytes = await pdfDoc.save()
and this is my attempt, I am having trouble with the helveticaFont:
(defn modifyPDF
  [file]
  (-> (readFileDataArrayBuffer file)
      (.then (fn [data] (.load pdf-lib/PDFDocument data)))
      #_(.then (fn [data] (js/console.log data)))
      (.then (fn [pdf]
               (let [helveticaFont (.embedFont pdf (.-Helvetica pdf-lib/StandardFonts))
                     pages (.getPages pdf)
                     firstPage (aget pages 0)
                     dimensions (.getSize firstPage)
                     width (.-width dimensions)
                     height (.-height dimensions)
                     ]
                 (set! (.-drawText firstPage) (.drawText firstPage
                            "This text was added with JavaScript!"
                            #js
                            {:x 5,
                             :y (+ (/ height 2) 300),
                             :size 50,
                             :font helveticaFont,
                             :color (pdf-lib/rgb 0.95 0.1 0.1),
                             :rotate (pdf-lib/degrees (- 45))})))))))
It is being returned as a promise. I tried following the await code structuring on the promise interop guide but didn't have any luck. Thanks in advance!

p-himik09:08:19

Just to get the terminology straight, there's no such thing as "promise interop". There's only JS interop. Or, more generally, host interop, but in the context of CLJS people usually use the former. The very first line of the JS code block uses await. It means that embedFont actually returns a promise and the whole JS block can be rewritten as:

pdfDoc.embedFont(...).then((helveticaFond) => { ... })
So all you have to do in your CLJS code is to add another .then to that chain where you call .embedFont. Apart from the (set! (.-drawText firstPage) ...) part. No clue what it's about but it doesn't seem right. Given the JS code, you only need the (.drawText ...) form there, without any usages of set!.

😍 2
p-himik09:08:22

Also, this line

#_(.then (fn [data] (js/console.log data)))
can't work properly if you uncomment it because it doesn't return data.

Harry09:08:14

Ok that was what I was trying to do but how would I pass both the helveticaFont and pdf through the .then

(defn modifyPDF
  [file]
  (-> (readFileDataArrayBuffer file)
      (.then (fn [data] (.load pdf-lib/PDFDocument data)))
      (.then (fn [pdf] (.embedFont pdf (.-Helvetica pdf-lib/StandardFonts))))
      (.then (fn [helveticaFont] 
(let [ pages (.getPages pdf)
                     firstPage (aget pages 0)
                     dimensions (.getSize firstPage)
                     width (.-width dimensions)
                     height (.-height dimensions)]
                 (.drawText firstPage
                            "This text was added with JavaScript!"
                            #js
                            {:x 5,
                             :y (+ (/ height 2) 300),
                             :size 50,
                             :font helveticaFont,
                             :color (pdf-lib/rgb 0.95 0.1 0.1),
                             :rotate (pdf-lib/degrees (- 45))}))))))

p-himik09:08:41

(-> ...
    (.then (fn [pdf]
             (-> (.embedFont pdf ...)
                 (.then (fn [helveticaFont] ...))))))

Harry20:08:14

So I have almost got it working but when I try convert the object to a blob all data is lost and I get a pdf that doesn't load. I am assuming it is because it is attempting to convert the promise to a blob which obviously wouldn't work. Not sure if this is actually a library problem or promise issue, as the pdf object appears to be printing the correct object but the (.-save pdf) is returning a promise :

(defn modifyPDF
  [file]
  (-> (readFileDataArrayBuffer file)
      (.then (fn [data] (.load pdf-lib/PDFDocument data)))
      (.then (fn [pdf] (-> (.embedFont pdf (.-Helvetica pdf-lib/StandardFonts))
                           (.then (fn [helveticaFont]
                                    (let [pages (.getPages pdf)
                                          firstPage (aget pages 0)
                                          dimensions (.getSize firstPage)
                                          width (.-width dimensions)
                                          height (.-height dimensions)]
                                      (.drawText firstPage
                                                 "This text was added with JavaScript!"
                                                 #js
                                                  {:x 5,
                                                   :y (+ (/ height 2) 300),
                                                   :size 50,
                                                   :font helveticaFont,
                                                   :color (pdf-lib/rgb 0.95 0.1 0.1),
                                                   :rotate (pdf-lib/degrees (- 45))}))))
                           (.then #(.-save pdf))
                           (.then (fn [save] (new js/Blob #js [save] #js {:type "appliciation/pdf"})))
(.then (fn [save] (download save "Test.pdf"))))))))

p-himik20:08:51

JS unwraps promises automatically, so regardless of whether a .then handler returns a promise or a regular value, the next .then will get the underlying regular value (with a caveat of objects having a then field, IIRC, since JS is a mess). In the function where you create a blob, what does (js/console.log save) print in the JS console?

Harry08:08:20

Gotcha, I get a javascript object (not sure how to get a better summary of the object for you, that is some of it, in the scope it appears to have a pdfDocument but doesn't seem right):

ƒ (options)
arguments: null
caller:null
length: 1
name: ""
prototype: {constructor: ƒ}
[[FunctionLocation]]: <unknown>
[[Prototype]]: ƒ ()
[[Scopes]]: Scopes[3]

p-himik08:08:11

You can try printing (type save).

p-himik08:08:56

You can also right-click it and "Save as global variable". Then you'll be able to experiment with it in the JS console, e.g. by creating a blob yourself. But your code looks alright, so not sure where it goes wrong. Are you sure it's not the download function that's to blame?

Harry08:08:29

Type save gives ƒ Function() { [native code] } It could be download although it is downloading the file with the correct name as a pdf file so that makes me think it is the object. Also if I print before running the (.-save pdf) I get an actual pdf object

Harry08:08:59

Download is from downloadjs

Harry08:08:54

PDFDocument {defaultWordBreaks: Array(1), context: PDFContext, catalog: PDFCatalog, computePages: ƒ, getOrCreateForm: ƒ, …}
catalog: PDFCatalog {dict: Map(6), context: PDFContext}
computePages: []
context: PDFContext {largestObjectNumber: 371, header: PDFHeader, trailerInfo: {…}, indirectObjects: Map(370), rng: SimpleRNG, …}
defaultWordBreaks: [' ']
embeddedFiles: []
embeddedPages: []
fonts: [PDFFont]
formCache: Cache {value: undefined, populate: ƒ}
getOrCreateForm: []
images: []
isEncrypted: false
javaScripts: []
pageCache: Cache {value: Array(2), populate: ƒ}
pageMap: Map(2) {PDFPageLeaf => PDFPage, PDFPageLeaf => PDFPage}
[[Prototype]]: Object
This is printing pdf directly

Harry08:08:41

Opening the blob it does appear to have pdf data so maybe download is causing the issue.

Harry23:08:39

Finally got it working, turns out the extra conversion to blob was not necessary! For anyone in future trying to solve this:

["pdf-lib" :as pdf-lib]
    ["downloadjs" :as download]

....

(defn modifyPDF
  [file]
  (-> (readFileDataArrayBuffer file)
      (.then (fn [data] (.load pdf-lib/PDFDocument data)))
      (.then (fn [pdf] (-> (.embedFont pdf (.-Helvetica pdf-lib/StandardFonts))
                           (.then (fn [helveticaFont]
                                    (let [pages (.getPages pdf)
                                          firstPage (aget pages 0)
                                          dimensions (.getSize firstPage)
                                          width (.-width dimensions)
                                          height (.-height dimensions)]
                                      (.drawText firstPage
                                                 "This text was added with JavaScript!"
                                                 #js
                                                  {:x 5,
                                                   :y (+ (/ height 2) 300),
                                                   :size 50,
                                                   :font helveticaFont,
                                                   :color (pdf-lib/rgb 0.95 0.1 0.1),
                                                   :rotate (pdf-lib/degrees (- 45))}))))
                           (.then #(.save pdf))
                           (.then (fn [save]
                             (download save "Test.pdf" "application/pdf"))))))))

shiyi.gu18:09:56

Can repl-driven works with spec smoothly like tests? Writing repl tests are very intuitive and we can accumulate them incrementally. What about spec? I guess writing spec loop may be longer, it comes at the end when finalising the functionality of the code. But usually we did do some tests covering the scope of the function when we start to create another function based on this existing function. Could you please share some experience for this part? (Repl and tests happened so naturally, I believe everyone do them all the time from the beginning. However, I never spent time to learn and practice using spec. Maybe I learn Clojure too casually.)