Fork me on GitHub
#beginners
<
2021-05-11
>
indy07:05:45

Hello. Was looking for information on when to use a case form and when to use multimethods (I want to dispatch on value), and also some performance comparisons. And I came across this https://insideclojure.org/2015/04/27/poly-perf/. When Alex says case is "closed", does that mean anything more than "one has to add another case condition"? In contrast, multimethods are "open" because, you have define a multimethod external to the defmulti? Not quite able to understand why case is more closed than multimethods. If I need to make some changes to the value I'm dispatching on, I have to change the mulimethod definition as well isn't it?

phronmophobic07:05:49

One major difference between case and defmulti is that defmulti is more flexible. There's a couple of ways the difference in flexibility manifests. For case, there's really only one way to add an additional clause which is to add another clause within the case statement. For defmulti , there are multiple options for extending a multi method. For programs that are fairly static, the code for using either defmulti or case might look largely the same. but for a dynamic program, defmulti 's flexibilty can really shine. Two types of extra flexibility come to mind. 1. Allowing library users to provide extensions. See https://clojuredocs.org/clojure.core/print-method for an example 2. Extending multi methods at run time. Typically, programs don't take advantage of this in production, but it's pretty useful in development if you're doing repl driven development. The idea is that you create a multi method that has 0 clauses and add/update clauses using your attached repl until everything works.

indy08:05:35

Thanks, the library bit makes sense. Need to try out the REPL friendliness of multimethods. 👍:skin-tone-4:

Alex Miller (Clojure team)13:05:01

re the original question, you had it. multimethod methods can be extended after the fact without changing the defmulti. case will require all cases to be defined at the same place.

3
gon07:05:05

if you are using a third party library where things are defined as case over defmulti in your code you are able to extend the defmulti, but not the case..

3
weihua09:05:20

is there any name-spaced syntax for set similar like map, #{:a.b/foo :a.b/bar :a.b/baz} can i take this a.b out for once?

delaguardo09:05:28

no, but even for maps you can’t take multiple values only by namespace

weihua09:05:31

#:a.b {:foo 123 :bar 123} in map i can do this, and the key would all become :a.b/foo :a.b/bar, i guess there's no such thing for set then 😄

Alex Miller (Clojure team)13:05:11

no, maps only. the syntax would be pretty ugly for sets #:a.b#{:foo 123} - that seemed bad :)

Alex Miller (Clojure team)13:05:31

still sitting on the fence about vectors

weihua05:05:12

tx for confirmation!

Endre Bakken Stovner10:05:01

Does anyone have any idea what this error might be due to? I think it happens in a Luminus project where I am trying to set up Sente. The error happens when Sente tries to connect to the server. I would love some hints on how to debug it or tips if anyone knows what might be wrong.

java.lang.IllegalArgumentException: No implementation of method: :on-close of protocol: #'org.httpkit.server/Channel found for class: nil
	at clojure.core$_cache_protocol_fn.invokeStatic(core_deftype.clj:583)
	at clojure.core$_cache_protocol_fn.invoke(core_deftype.clj:575)
	at org.httpkit.server$eval19781$fn__19851$G__19772__19858.invoke(server.clj:96)
	at taoensso.sente.server_adapters.http_kit.HttpKitServerChanAdapter.ring_req__GT_server_ch_resp(http_kit.clj:25)
	at taoensso.sente$make_channel_socket_server_BANG_$fn__21615.invoke(sente.cljc:606)
	at muuntaja.middleware$wrap_params$fn__8119.invoke(middleware.clj:52)
	at muuntaja.middleware$wrap_format$fn__8123.invoke(middleware.clj:73)
	at everclear.middleware$wrap_formats$fn__9289.invoke(middleware.clj:41)
	at ring.middleware.anti_forgery$wrap_anti_forgery$fn__6955.invoke(anti_forgery.clj:94)
	at ring.middleware.params$wrap_params$fn__9021.invoke(params.clj:67)
	at ring.middleware.keyword_params$wrap_keyword_params$fn__8807.invoke(keyword_params.clj:53)
	at reitit.ring$ring_handler$fn__24478.invoke(ring.cljc:326)
	at clojure.lang.AFn.applyToHelper(AFn.java:154)
	at clojure.lang.AFn.applyTo(AFn.java:144)
	at clojure.lang.AFunction$1.doInvoke(AFunction.java:31)
	at clojure.lang.RestFn.invoke(RestFn.java:408)
	at clojure.lang.Var.invoke(Var.java:384)
	at ring.middleware.reload$wrap_reload$fn__4448.invoke(reload.clj:39)
	at selmer.middleware$wrap_error_page$fn__4463.invoke(middleware.clj:18)
	at prone.middleware$wrap_exceptions$fn__4704.invoke(middleware.clj:159)
	at ring.middleware.flash$wrap_flash$fn__8160.invoke(flash.clj:39)
	at ring.middleware.session$wrap_session$fn__8605.invoke(session.clj:108)
	at ring.adapter.undertow.middleware.session$wrap_undertow_session$fn__8711.invoke(session.clj:88)
	at ring.middleware.keyword_params$wrap_keyword_params$fn__8807.invoke(keyword_params.clj:53)
	at ring.middleware.nested_params$wrap_nested_params$fn__8865.invoke(nested_params.clj:89)
	at ring.middleware.multipart_params$wrap_multipart_params$fn__8997.invoke(multipart_params.clj:171)
	at ring.middleware.params$wrap_params$fn__9021.invoke(params.clj:67)
	at ring.middleware.cookies$wrap_cookies$fn__8484.invoke(cookies.clj:214)
	at ring.middleware.absolute_redirects$wrap_absolute_redirects$fn__9209.invoke(absolute_redirects.clj:47)
	at ring.middleware.resource$wrap_resource_prefer_resources$fn__9057.invoke(resource.clj:25)
	at ring.middleware.content_type$wrap_content_type$fn__9157.invoke(content_type.clj:34)
	at ring.middleware.default_charset$wrap_default_charset$fn__9181.invoke(default_charset.clj:31)
	at ring.middleware.not_modified$wrap_not_modified$fn__9123.invoke(not_modified.clj:61)
	at ring.middleware.x_headers$wrap_x_header$fn__8747.invoke(x_headers.clj:22)
	at ring.middleware.x_headers$wrap_x_header$fn__8747.invoke(x_headers.clj:22)
	at ring.middleware.x_headers$wrap_x_header$fn__8747.invoke(x_headers.clj:22)
	at everclear.middleware$wrap_internal_error$fn__9283.invoke(middleware.clj:20)
	at ring.adapter.undertow$undertow_handler$fn$reify__42283.handleRequest(undertow.clj:33)
	at io.undertow.server.Connectors.executeRootHandler(Connectors.java:370)
	at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830)
	at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
	at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2019)
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1558)
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1449)
	at java.base/java.lang.Thread.run(Thread.java:831)
I get this error in the developer console by the way:
sente.cljc:1045 WebSocket connection to '' failed: 
eval @ sente.cljc:1045
core.cljs:159 ERROR [taoensso.sente:1058] - WebSocket error: [object Event]
eval @ core.cljs:159

Endre Bakken Stovner10:05:16

These are the relevant lines mentioned in the stack trace (from middleware.clj):

17	(defn wrap-internal-error [handler]
    18	  (fn [req]
    19	    (try
    20	      (handler req)
    21	      (catch Throwable t
    22	        (log/error t (.getMessage t))
    23	        (error-page {:status 500
    24	                     :title "Something very bad has happened!"
    25	                     :message "We've dispatched a team of highly trained gnomes to take care of the problem."})))))
    26
    27	(defn wrap-csrf [handler]
    28	  (wrap-anti-forgery
    29	    handler
    30	    {:error-response
    31	     (error-page
    32	       {:status 403
    33	        :title "Invalid anti-forgery token"})}))
    34
    35
    36	(defn wrap-formats [handler]
    37	  (let [wrapped (-> handler wrap-params (wrap-format formats/instance))]
    38	    (fn [request]
    39	      ;; disable wrap-formats for websockets
    40	      ;; since they're not compatible with this middleware
    41	      ((if (:websocket? request) handler wrapped) request))))

valerauko15:05:24

is there anything as simple to use as slurp/spit for binary data?

borkdude15:05:48

FileOutputStream perhaps?

borkdude15:05:51

java.nio.file.Files has readAllBytes

borkdude15:05:50

and does support writing a byte array to a file

valerauko15:05:37

my use case would be reading a binary input stream (ring request body), checking how long the contents are and writing it into a file

valerauko15:05:33

this is really easy with text data with slurp and spit but binary data not so much

valerauko15:05:05

jio/copy might be the way to go hmm

Alex Miller (Clojure team)15:05:27

you could just use the http://java.io inputstreams for this kind of thing

valerauko16:05:29

i know but i hoped there was something as simple as slurp/spit 😞

hiredman16:05:35

slurp/spit is easy, but not simple

🙂 3
3
piyer20:05:16

Logging in java is such a frustration. I imported clj-http that messed with my current logging and logs stopped showing up on my cider. Is there a way to turn off the logging from clj-http?

seancorfield20:05:15

@munichlinux What are you currently using for logging?

piyer20:05:14

@seancorfield here is my setup:

[org.apache.logging.log4j/log4j-api "2.14.1"]

                 [org.apache.logging.log4j/log4j-core "2.14.1"]
                 [org.apache.logging.log4j/log4j-jcl "2.14.1"]
                 [org.apache.logging.log4j/log4j-jul "2.14.1"]
                 [org.apache.logging.log4j/log4j-1.2-api "2.14.1"]

piyer20:05:20

here is my log4j2.xml:

<?xml version= "1.0" encoding= "UTF-8" ?>
<Configuration monitorInterval= "10" >
  <Appenders>
    <!-- console output -->
    <Console name= "Console" target= "SYSTEM_OUT" >
      <PatternLayout pattern= "[%level] - %d %logger %m%n%throwable" />
    </Console>
  </Appenders>
  <Loggers>
    <Logger name= "io.grpc.netty.shaded.io" level= "ERROR" >
      <AppenderRef ref= "Console" />
    </Logger>
    <Logger name="org.apache.http" level="OFF" >
    </Logger>
    <Root level= "all" includeLocation= "false" >
      <AppenderRef ref= "Console" level= "DEBUG" />
      <AppenderRef ref= "Console" level= "ERROR" />
      <AppenderRef ref= "Console" />
    </Root>
  </Loggers>
</Configuration>

seancorfield20:05:58

And you’re using tools.logging to do your actual logging, yes?

seancorfield20:05:08

What I believe is happening is that previously, tools.logging was picking up log4j2 by default but when you introduced clj-http, it depends on commons logging which tools.logging “prefers”, so you need to explicitly tell it to use log4j2 anyway (via the JVM option described in that link).

piyer20:05:28

@seancorfield yes using tools.logging.

piyer21:05:12

@seancorfield I added that to :repl section

:repl {:jvm-opts ["-Dclojure.tools.logging.factory=clojure.tools.logging.impl/log4j2-factory"]}

piyer21:05:21

that did not do the magic either.

seancorfield21:05:48

Are you sure that however you are starting up your Clojure process is using that? (profile/alias)

piyer21:05:34

The only problem is on the cider repl. I do see the logs in nrepl-server

seancorfield21:05:12

Then ask in #cider — I haven’t used that for years so I can’t help you, sorry.

👍 3
Kieran D'Mello20:05:06

Hi, im having a bit of an issue calling a package private function from a base class. After doing a bunch of googling it seems others have had some problems too and was wondering if anyone had a solution?

seancorfield21:05:37

@kierand Can you provide a bit more context? That doesn’t sound like a Clojure problem…?

Kieran D'Mello21:05:48

so i have the following code

(def pca
  (-> (PublicClientApplication/builder client-id)
      (.authority auth)
      (.build)))

Kieran D'Mello21:05:03

but .authority is inherited from an abstract class

Kieran D'Mello21:05:28

so when i try to call it i get Reflection warning, clojure_ad_test/core.clj:22:7 - call to method authority on com.microsoft.aad.msal4j.PublicClientApplication$Builder can't be resolved (argument types: unknown).

seancorfield21:05:18

That says it can’t figure out the type of auth so it doesn’t know what method to call.

seancorfield21:05:42

You’ll have to type hint auth, either where you declare it, or inline.

seancorfield21:05:14

Try (.authority ^String auth)

Kieran D'Mello21:05:58

i tried that but i get the same errror

Kieran D'Mello21:05:24

except type unknown is now java.lang.String

Kieran D'Mello21:05:58

fyi those docs are wrong the actual signature of builder is public static class Builder extends com.microsoft.aad.msal4j.AbstractClientApplicationBase.Builder<PublicClientApplication.Builder>

Kieran D'Mello21:05:32

Reflection warning, clojure_ad_test/core.clj:23:7 - call to method authority on com.microsoft.aad.msal4j.PublicClientApplication$Builder can't be resolved (argument types: java.lang.String). Reflection warning, clojure_ad_test/core.clj:24:7 - reference to field build can't be resolved. Execution error (IllegalArgumentException) at clojure.main/main (main.java:40). No matching method authority found taking 1 args for class com.microsoft.aad.msal4j.PublicClientApplication$Builder

Kieran D'Mello21:05:38

thats the entire stack trace

seancorfield21:05:36

On Clojure 1.8, we get a different error:

(! 1452)-> clj -Sdeps '{:deps {com.microsoft.azure/msal4j {:mvn/version "1.0.0"}}}' -A:1.8
Clojure 1.8.0
user=> (import 'com.microsoft.aad.msal4j.PublicClientApplication)
com.microsoft.aad.msal4j.PublicClientApplication
user=> (let [b (PublicClientApplication/builder "foo")] (.authority b "bar"))
IllegalArgumentException Can't call public method of non-public class: public com.microsoft.aad.msal4j.ClientApplicationBase$Builder com.microsoft.aad.msal4j.ClientApplicationBase$Builder.authority(java.lang.String) throws java.net.MalformedURLException  clojure.lang.Reflector.invokeMatchingMethod (Reflector.java:88)
user=> ^D
(! 1453)-> clj -Sdeps '{:deps {com.microsoft.azure/msal4j {:mvn/version "1.0.0"}}}' -A:1.10
Clojure 1.10.3
user=> (import 'com.microsoft.aad.msal4j.PublicClientApplication)
com.microsoft.aad.msal4j.PublicClientApplication
user=> (let [b (PublicClientApplication/builder "foo")] (.authority b "bar"))
Execution error (IllegalArgumentException) at user/eval138 (REPL:1).
No matching method authority found taking 1 args for class com.microsoft.aad.msal4j.PublicClientApplication$Builder
user=> 
I wonder if @ghadi can shed some light on this?

seancorfield21:05:00

(ISTR this is an area where Clojure has made some changes over the years but I’m cloudy on what they were)

ghadi21:05:02

can someone link to the javadocs for this target class?

seancorfield21:05:09

Linked above.

3
hiredman21:05:56

The is an ask.clojure for this issue

seancorfield21:05:52

I thought it sounded a bit familiar. I’ve upvoted that ask issue.

hiredman22:05:02

Yech, what a mess

Kieran D'Mello22:05:57

yeah i went through all of those issues and they all seem to end at a dead end

hiredman22:05:51

if you are just looking for a work around to make it work something like

(import 'com.microsoft.aad.msal4j.PublicClientApplication)

(def authority
  (.findVirtual
   (java.lang.invoke.MethodHandles/lookup)
   com.microsoft.aad.msal4j.PublicClientApplication$Builder
   "authority"
   (java.lang.invoke.MethodType/methodType
    com.microsoft.aad.msal4j.ClientApplicationBase$Builder
    [String])))

(let [b (PublicClientApplication/builder "foo")]
  (.invokeWithArguments authority [b "foo"]))
should work

Kieran D'Mello22:05:56

yep that works i had to make small change from com.microsoft.aad.msal4j.ClientApplicationBase$Builder to com.microsoft.aad.msal4j.AbstractClientApplicationBase$Builder but i can call the method.

Kieran D'Mello22:05:59

Thanks so much!!

dpsutton22:05:14

wow. this one was kinda sounding impossible to get the Clojure compiler to output the correct bytecode

ghadi22:05:28

there’s a method selection bug

ghadi22:05:46

will try to understand the behavior and documentat

Kieran D'Mello23:05:01

so just out of curiousity - is this something that clojure can fix or something that has to be fixed at a java level?

hiredman23:05:13

it is kind of complicated

hiredman23:05:49

there are kind of two ways method invoking like (.authority b "foo") can get compiled

hiredman23:05:29

one way is without reflection, which the compiler does if it knows all the types, but there appears to be a clojure bug somewhere so it is failing to do that

hiredman23:05:35

if a method call cannot be compiled without reflection, the compiler generates code that will do a reflective call at runtime, which would hit the underlying java issue

Kieran D'Mello23:05:20

Ah so it's trying reflection because it should know the types but then the bug stops it from getting the abstract class it inherits from?

Rob Haisfield23:05:05

Would it be possible to point a function at a directory on my computer, have it find all of the image files, and then create a map according to this schema?

Stuart00:05:46

What about imagez? https://github.com/mikera/imagez It might be a bit heavy for this though, but it lets you get image width and height. Here's a quick example:

(defn get-image-properties [path file]
  (let [img (mimg/load-image (str path "//" file))]
    {:measuremnt {:width (mimg/width img)
                  :height (mimg/height img)}}))

(let [path "//home/stuart//Pictures"
      files (->> (.list (io/file path))
                 (filter #(str/ends-with? % ".png")))]
  {:images (zipmap (map keyword files)
                   (map (partial get-image-properties path) files))})
This outputs:
{:images
 {:Selection_023.png {:measuremnt {:width 842, :height 412}},
  :Selection_025.png {:measuremnt {:width 600, :height 426}},
  :Selection_020.png {:measuremnt {:width 850, :height 340}},
  :Selection_024.png {:measuremnt {:width 602, :height 426}},
  :Selection_021.png {:measuremnt {:width 1292, :height 965}},
  :Screenshot from 2021-04-12 00-22-05.png {:measuremnt {:width 857, :height 335}},
  :Selection_026.png {:measuremnt {:width 1000, :height 426}},
  :puzzle-storm-33.png {:measuremnt {:width 850, :height 340}}}}

Rob Haisfield00:05:33

Looks like you have a couple of dependencies set up here. What’s in your namespace?

Stuart00:05:57

(ns reference.core
  (:gen-class)
  (:require [clojure.string :as str]
            [ :as io]
            [mikera.image.core :as mimg]))

Stuart00:05:23

With this as a dependency in my project.clj

[net.mikera/imagez "0.12.0"]

Rob Haisfield00:05:31

Thanks! I’m a little confused what’s going on with (str path "//" file) and the (let [path "//home/stuart//Pictures" would you mind explaining?

Rob Haisfield01:05:12

Yeah I guess also just what are you putting in the path and file arguments?

Stuart01:05:10

File is just something like "foo.png" as that io file path call only returns the filename, not the path and file name. Imagez needs full path to file so that str is just taking folder where images is and appending the filename on. Needs the // to escape a /.