Fork me on GitHub
#beginners
<
2020-03-22
>
Luis C. Arbildo01:03:35

Hello, there a way to re-load automatically an clojure app (not clojurescript) per each change do it like nodemon

didibus02:03:14

Normally you should be working at the REPL

didibus02:03:33

Where you don't really need to reload, as you'd just be loading as you make the changes themselves

didibus02:03:57

But, you can use https://github.com/clojure/tools.namespace to handle a proper reload

didibus02:03:06

Cider has the functionality built in

didibus02:03:39

The problem if you constantly "reload" is that you lose your state

didibus02:03:00

That's why it's better to just get used to the REPL driven style, where you load things as you change them and see fit

👍 4
seancorfield02:03:23

I highly recommend getting used to a workflow where you edit, eval, edit, eval and keep the REPL state up to date all the time.

Luis C. Arbildo02:03:04

it is understood!

seancorfield02:03:10

Watch Stu Halloway's "REPL-Driven Development" talk to the Chicago Clojure meetup (2018 or 2017?)

💯 12
seancorfield02:03:21

and his more recent "Running with Scissors" talk.

seancorfield02:03:50

I never use any "reload" / "refresh" or "watch" tools. I just don't need them.

seancorfield02:03:01

A tight RDD workflow is better in so many ways.

seancorfield02:03:33

Also worth considering is buying Eric Normand's "REPL-Driven Development" course on http://PurelyFunctional.tv -- not cheap but worth every dollar!

seancorfield02:03:59

I tend to work "live" via the REPL for quite a while without even saving files.

didibus02:03:21

I too rarely use reload, except once in a while, when I explicitly am looking to start totally fresh.

seancorfield02:03:41

I never type into a REPL: I edit code in my editor and eval each form/top-level form as I make each change.

didibus02:03:41

What editor are you using?

Luis C. Arbildo02:03:50

Currently Emacs with Spacemacs

seancorfield02:03:27

I also use Rich Comment Forms (again, a Stu Halloway term, based on how Rich Hickey works), to contain code forms that I need for dev/test so I can eval them whenever I want.

didibus02:03:28

Are you familiar with the eval commands of cider?

didibus02:03:58

You should take some time to do so if not

seancorfield02:03:23

(some Clojure core namespaces have RCFs too)

seancorfield02:03:51

Eval form and eval top-level form should be muscle memory in whatever editor you use 🙂

didibus04:03:27

I prefer to use the reader discard form #_ personally over comment forms. But ya, in most of our namespace, we have a section at the bottom called ;;;; REPL only code and it has commented forms (or discarded) forms that are useful when working in that namespace

dpsutton02:03:28

@steiner3044 the coordinates for that lib are [clj-commons/fs "1.5.0"] . In a namespace you can require it as

(ns my.ns
  (:require [me.raynes.fs :as fs]))

Gulli07:03:58

All functions touching a database (Inserting, Selecting etc.) are impure and should end with a "!", right?

hindol08:03:40

Not really. It is just a convention. I read somewhere that we should not use ! for all side effecting functions because there will be too many in a real project. Sometimes ! is used to signal thread safety related stuff like swap! and reset! that operate on an atom.

Gulli08:03:48

Regarding one thing in that guide, I've heard that there are accessability issues when it comes to spaces (instead of tabs). But even before hearing that I've always used tabs

hindol08:03:46

Hmm, never heard that one before. I prefer spaces because they render the same everywhere.

Gulli08:03:55

Good poing though. The accessability issue I saw in some big thread on reddit a while back, don't remember exactly how it was though.

Tzafrir Ben Ami10:03:43

the next.jdbc library does use exclamation mark for insert\update\delete functions (`insert!, update!, delete! execute!` ) but not for query: https://github.com/seancorfield/next-jdbc/blob/master/doc/friendly-sql-functions.md

jsn10:03:05

Traditionally in lisps "!" is used to indicate not just a side effect, but a mutation

Lukas10:03:41

Hey Guys, i wrote a little script which fetches the current weather. I now have the problem that when i don't have an internet connection I get a NPE. But when i test the functions/script inside the repl (without connection) it works flaweless without giving me a NPE. I can't really make sense out of the error message either. I would really appreciate if someone could me a pointer what i missed here:face_with_rolling_eyes:

#!/usr/bin/env bb
(require '[clojure.java.shell :refer [sh]])

(def weather-icon
  '({:id "01" :icon "B"}
    {:id "02" :icon "H"}
    {:id "03" :icon "N"}
    {:id "04" :icon "Y"}
    {:id "09" :icon "R" }
    {:id "10" :icon "Q"}
    {:id "11" :icon "0"}
    {:id "13" :icon "G"}
    {:id "50" :icon "M"}))

(defn req [key]
  (-> (sh "curl" "--request" "GET"
          "--url" ""
          "--header" "x-rapidapi-host: "
          "--header" (str "x-rapidapi-key: " key))
      :out
      (json/parse-string true)))

(defn font [w]
  (str "%{T3}" w "%{T-}"))

(defn temperatur [data]
  (-> (get-in data [:main :temp]) Math/ceil int))

(defn format-tmp [tmp]
  (str tmp "°C"))

(defn parse-weather [data]
  (-> (get-in data [:weather]) first (get-in [:icon]) (subs 0 2)))

(defn get-weather-icon [icon-id]
  (-> (filter #(= (:id %) icon-id) weather-icon)
      first
      :icon))

(let [resp (req (first *input*))]
  (println
   (str (-> resp parse-weather get-weather-icon font)
        "%{O3}"
        (-> resp temperatur format-tmp))))
i call the script like this cat weather.api | bb -i weather.clj

Lukas10:03:03

this is error msg

java.lang.NullPointerException: null
 at clojure.core$subs.invokeStatic (core.clj:4989)
    clojure.core$subs.invoke (core.clj:4983)
    sci.impl.interpreter$fn_call.invokeStatic (interpreter.cljc:425)
    sci.impl.interpreter$eval_call.invokeStatic (interpreter.cljc:474)
    sci.impl.interpreter$interpret.invokeStatic (interpreter.cljc:515)
    sci.impl.interpreter$interpret.invoke (interpreter.cljc:492)
    sci.impl.fns$parse_fn_args_PLUS_body$run_fn__1667.doInvoke (fns.cljc:44)
    clojure.lang.RestFn.invoke (RestFn.java:408)
    sci.impl.vars.SciVar.invoke (vars.cljc:314)
    sci.impl.interpreter$fn_call.invokeStatic (interpreter.cljc:425)
    sci.impl.interpreter$eval_call.invokeStatic (interpreter.cljc:474)
    sci.impl.interpreter$interpret.invokeStatic (interpreter.cljc:515)
    sci.impl.interpreter$interpret.invoke (interpreter.cljc:492)
    sci.impl.interpreter$fn_call.invokeStatic (interpreter.cljc:425)
    sci.impl.interpreter$eval_call.invokeStatic (interpreter.cljc:474)
    sci.impl.interpreter$interpret.invokeStatic (interpreter.cljc:515)
    sci.impl.interpreter$interpret.invoke (interpreter.cljc:492)
    sci.impl.interpreter$fn_call.invokeStatic (interpreter.cljc:425)
    sci.impl.interpreter$eval_call.invokeStatic (interpreter.cljc:474)
    sci.impl.interpreter$interpret.invokeStatic (interpreter.cljc:515)
    sci.impl.interpreter$interpret.invoke (interpreter.cljc:492)
    sci.impl.interpreter$fn_call.invokeStatic (interpreter.cljc:425)
    sci.impl.interpreter$eval_call.invokeStatic (interpreter.cljc:474)
    sci.impl.interpreter$interpret.invokeStatic (interpreter.cljc:515)
    sci.impl.interpreter$interpret.invoke (interpreter.cljc:492)
    sci.impl.interpreter$fn_call.invokeStatic (interpreter.cljc:425)
    sci.impl.interpreter$eval_call.invokeStatic (interpreter.cljc:474)
    sci.impl.interpreter$interpret.invokeStatic (interpreter.cljc:515)
    sci.impl.interpreter$interpret.invoke (interpreter.cljc:492)
    sci.impl.interpreter$eval_let.invokeStatic (interpreter.cljc:83)
    sci.impl.interpreter$eval_let.doInvoke (interpreter.cljc:59)
    clojure.lang.RestFn.applyTo (RestFn.java:142)
    clojure.core$apply.invokeStatic (core.clj:667)
    sci.impl.interpreter$eval_special_call.invokeStatic (interpreter.cljc:439)
    sci.impl.interpreter$eval_call.invokeStatic (interpreter.cljc:465)
    sci.impl.interpreter$interpret.invokeStatic (interpreter.cljc:515)
    sci.impl.interpreter$eval_form.invokeStatic (interpreter.cljc:546)
    sci.impl.interpreter$eval_string_STAR_.invokeStatic (interpreter.cljc:560)
    babashka.main$main$fn__12973.invoke (main.clj:403)
    babashka.main$main.invokeStatic (main.clj:396)
    babashka.main$main.doInvoke (main.clj:282)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.core$apply.invokeStatic (core.clj:665)
    babashka.main$_main.invokeStatic (main.clj:442)
    babashka.main$_main.doInvoke (main.clj:437)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    babashka.main.main (:-1)

jumpnbrownweasel14:03:48

The top line in the stack trace is the method throwing the NPE: clojure.core$subs.invokeStatic and this tells us the method is subs. I see that your parse-weather function has a call to subs . So perhaps you need to ensure that this function is not called when the request fails.

Lukas16:03:10

Hey thank you (especially for the hint how to make sense out of the error message, I probably should have read it more carefully) I also figured that in the repl (subs nil 0 2) evaluates to nil but bb '(subs nil 0 2)' results in the NPE I have

jumpnbrownweasel17:03:17

np, glad you figured it out. @U04V15CAJ may be interested in that ^^ difference.

borkdude17:03:58

$ bb '(subs nil 0 2)'
java.lang.NullPointerException
$ clj -e '(subs nil 0 2)'
Execution error (NullPointerException) at user/eval1 (REPL:1).
null
what's the problem?

borkdude17:03:13

Ah, I see:

user=> (subs nil 0 2)
nil
Hmm, that's weird

borkdude17:03:38

I think only the exception message gets printed in the REPL:

user=> (+ nil)
nil
But a nullpointer doesn't have a message 🙂 I'll make an issue for that

jumpnbrownweasel17:03:38

Makes sense. And great support! :-)

Lukas17:03:48

Oh wow, you guys rock 😊 thank you

subsaharancoder20:03:47

Why am I getting a Response map is nil when I access my handlers in the browser but seems to work fine when I curl?

(ns learn-ring.core
  (:require [reitit.ring :as ring]
            [reitit.coercion.spec]
            [ring.adapter.jetty :as jetty]
            [ring.logger :as logger]
            [ring.util.response :refer [content-type response]]
            [ :as io]
            [reitit.dev.pretty :as pretty]
            [reitit.ring.middleware.parameters :as parameters]
            [reitit.ring.middleware.exception :as exception]
            [ring.middleware.reload :refer [wrap-reload]]
            )
  (:gen-class))

(defn ping [request]
  (content-type (response "Ping") "text/plain"))

(defn handler [_]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body (io/input-stream (io/resource "public/index.html"))})


(def router
  (ring/router
    [["/" handler]
     ["/ping" {:name ::ping
               :get ping}]]
    {:exception pretty/exception}))

(def app
  (ring/ring-handler
    router
    (ring/create-resource-handler {:path "/public"})
    (ring/create-default-handler)))

(def reloadable-app
  (wrap-reload #'app))

(defn -main
  [& args]
  (jetty/run-jetty (logger/wrap-with-logger #'app) {:port 4000, :join? false})
  (println "server running in port 4000"))

Tzafrir Ben Ami20:03:34

could it be that your browser is trying to call your page favicon.ico for html page? (I think it happens with Chrome and not with Firefox)

Tzafrir Ben Ami20:03:14

try adding a not found handler, I think that ring should have a default not-found handler

subsaharancoder21:03:10

reitit has a create-default-handler https://cljdoc.org/d/metosin/reitit-ring/0.3.10/api/reitit.ring#create-default-handler which I’ve already:

(def app
  (ring/ring-handler
    router
    (ring/create-resource-handler {:path "/public"})
    (ring/create-default-handler)))

subsaharancoder21:03:55

and you seem to be right, I only see the error when I access using Chrome and not FF

subsaharancoder21:03:05

INFO: {:request-method :get, :uri "/favicon.ico", :server-name "localhost", :ring.logger/type :finish, :status nil, :ring.logger/ms 6}
2020-03-22 14:36:30.032:WARN:oejs.HttpChannel:qtp1226306030-25: /favicon.ico
java.lang.NullPointerException: Response map is nil

subsaharancoder21:03:37

seems like my default handler wasn’t being called

subsaharancoder22:03:06

I do seem to have a problem loading css from my /public directory

👍 4
Gulli21:03:19

Hi, can anyone help me decypher the meaning of the error I'm getting in my browser? Error is shown at bottom

(ns processing-service.handler
 (:require 
      [reitit.ring :as ring]
      [ring.adapter.jetty :as jetty]))
 
(def ^:const OK 200)
 
(def ping-services
 "Pings all relevant services and returns a status map"
 {:aggregator "DOWN"
  :baas "DOWN"
  :ml-service "DOWN"})
 
(def routes
 [
  ["/ping" {:name "Returns status of all relevant services"
          :get {:parameters {:path {:date inst?}}
             :responses {OK {:body ::ping-services}}
             :handler (fn [_]
                  {:status OK
                   :headers {"Content-Type" "application/json"}
                   :body ping-services})}}]
  ])
 
(def router
 (ring/router routes))
 
(def app
 (ring/ring-handler router))
 
(defn start []
 (jetty/run-jetty #'app {:port 3000 :join? false})
 (println "Server running on port 3000"))
 
(comment
 (app {:request-method :get :uri "/ping"})
 (start))
 
;; 1. When I evaluate (app {:request-method :get :uri "/ping"}) I get
;; {:status 200,
;; :headers {"Content-Type" "application/json"},
;; :body {:aggregator "DOWN", :baas "DOWN", :ml-service "DOWN"}}
;;
;; 2. When I actually visit localhost:3000/ping I receive this error:
;;
;; HTTP ERROR 500 java.lang.IllegalArgumentException: No implementation of method: :write-body-to-stream of protocol:
;; #'ring.core.protocols/StreamableResponseBody found for class: clojure.lang.PersistentArrayMap

Gulli23:03:00

I fixed this, just in case anybody else has the same issue, you need middleware for the JSON response:

(def app
  (-> router
      ring/ring-handler
      wrap-json-response
      (wrap-json-body {:keywords? true})))

seancorfield22:03:38

@glfinn83 Does it happen if you curl that URL, or only in a browser?

seancorfield22:03:04

(so perhaps it is related to problem that @johnwesonga was seeing in this channel earlier?)

seancorfield22:03:15

BTW, for both of you, it would be much easier for folks to help you debug these if you put them up on GitHub so all of the deps and other code was available for us to download and help test stuff. Just posting a wall of code in here is... difficult to help with.

Gulli22:03:39

@seancorfield curling yields the same exception, so seems its not the same prob he had above. This part of the project that I'm working on is currently not open source so I'd not be able to share the whole project I'm afraid :(

seancorfield22:03:04

Aye, but the code above -- which you've already posted publicly -- along with, say, a deps.edn file that makes it work could be posted on GitHub...

seancorfield22:03:13

Creating small, self-contained, repro examples is critical when debugging, especially if you want someone else to help, since they need to repro on their machine.

seancorfield22:03:03

If both yours and @johnwesonga’s code were up on GitHub where I could just clone the repo and try it out here on my laptop, while I'm watching Star Trek: Discovery, I would be happy to help both of you debug things... 🙂

subsaharancoder22:03:10

@seancorfield I fixed my issue..my default handler wasn’t capturing the missing /favicon route

seancorfield22:03:30

@johnwesonga I saw in the thread -- just making this point for the future 🙂

seancorfield22:03:07

(and you still have a problem loading CSS, right?)

subsaharancoder22:03:20

(ns learn-ring.core
  (:require [reitit.ring :as ring]
            [reitit.coercion.spec]
            [ring.adapter.jetty :as jetty]
            [ring.logger :as logger]
            [ring.util.response :refer [content-type response resource-response]]
            [reitit.dev.pretty :as pretty]
            [reitit.ring.middleware.exception :as exception]
            [ring.middleware.reload :refer [wrap-reload]]
            [ring.middleware.resource :refer [wrap-resource]]
            [ring.middleware.content-type]
            )
  (:gen-class))

(defn ping [request]
  (content-type (response "Ping") "text/plain"))

(defn handler [_]
  (resource-response "index.html" {:root "public"}))


(def router
  (ring/ring-handler
    (ring/router
      [["/" handler]
       ["/ping" {:name ::ping
                 :get ping}]
       ["/css/*" (ring/create-resource-handler)]]
      {:exception pretty/exception})
    (ring/create-default-handler
      {:not-found (constantly {:status 404, :body "oops"})})))

(def app (-> #'router
             (wrap-reload)
             (wrap-resource "public")))


(defn -main
  [& args]
  (jetty/run-jetty (logger/wrap-with-logger #'app) {:port 4000, :join? false})
  (println "server running in port 4000"))
I keep getting a 404

seancorfield22:03:29

Probably also worth mentioning that there is a #reitit channel where you might be able to get specialized help with that (I've never used that library, but I'm curious enough to be willing to try it out in order to help folks out here 🙂 )

seancorfield22:03:43

Again, posting a wall of code here is... not ideal.

Gulli22:03:20

I'll post mine on GH tomorrow if I've not solved it

4
seancorfield22:03:34

@johnwesonga Not sure how reitit handles web assets but with Ring in general, you normally have to ensure the folder containing those resources is on your classpath...

subsaharancoder22:03:04

my css folder is under public/

seancorfield22:03:17

...and there's normally a configuration to identify the relative path from (somewhere) on your classpath to the "webroot".

seancorfield22:03:39

(I'm used to there being resource/public/css/... as part of the structure, "resources" being on the classpath in addition to "src", and configuration to identify public is the relative path from the classpath to the webroot)

seancorfield22:03:03

@johnwesonga Here's a simple Ring/Compojure example of a small web app that loads CSS https://github.com/seancorfield/usermanager-example

seancorfield22:03:38

(having a version that uses reitit instead of Compojure` might make an interesting branch of that repo)

subsaharancoder22:03:53

@seancorfield that worked!! changed the path from resources/css to resources/public/css thanks!!

👍 4