This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-11-17
Channels
- # aleph (4)
- # announcements (2)
- # babashka (85)
- # beginners (136)
- # calva (72)
- # clj-commons (32)
- # clj-kondo (7)
- # cljs-dev (3)
- # clojure (117)
- # clojure-europe (38)
- # clojure-nl (3)
- # clojure-norway (1)
- # clojure-uk (4)
- # clojurescript (19)
- # conjure (38)
- # core-logic (2)
- # cursive (10)
- # datalevin (1)
- # datalog (1)
- # datomic (6)
- # events (2)
- # fulcro (16)
- # google-cloud (5)
- # graphql (10)
- # gratitude (3)
- # hugsql (3)
- # luminus (5)
- # membrane-term (12)
- # missionary (2)
- # nextjournal (5)
- # off-topic (3)
- # pedestal (2)
- # polylith (7)
- # portal (3)
- # re-frame (6)
- # reagent (26)
- # reclojure (8)
- # releases (3)
- # reveal (5)
- # shadow-cljs (14)
- # spacemacs (20)
- # sql (3)
- # tools-build (3)
- # web-security (9)
Hi. The clojure reference https://clojure.org/reference/protocols that "defprotocol is dynamic, and does not require AOT compilation". I'd like to know what the most important implications of this are. Does this mean that protocols can be generated during runtime or something like that? If so, could you give a real-world example where this might be used? I'm somewhat new to the idea of compile time vs. runtime polymorphism, so a simpler (if possible) explanation would be better. Thanks in advance.
I think this is relevant https://en.wikipedia.org/wiki/Late_binding
Compile time polymorphism - pick the method to dispatch to at compile time Run time polymorphism - pick it at run time Interfaces are also a limited example of run time polymorphism. The difference with protocols is that you can extend them to types which don't implement your interface as well. When protocol methods are invoked, there's a check if the target is an instance of an interface backing that protocol. If so, it dispatches to it. Otherwise, there's a lookup in an additional method table with can be extended
@U02CV2P4J6S It is relevant, yes, but still doesn't really answer my question. This is why I asked for a "simpler explanation", actually. I'm hoping for something a noob could understand, because I'm not too familiar with how compilers work.
@UK0810AQ2 This sheds some light on the issue. Your explanation of compile time polymorphism implies that it's possible only in statically typed languages, as you need to know the argument's type at compile time, am I correct? Also, by "interfaces" do you mean class interfaces? I think I've got a basic understanding about differences between the two types of polymorphism, but the question about what this fact changes still remains.
I think I got it. The functions that are part of a protocol dispatch at runtime, as Ben said, which means that they don't need to know the argument's type at compile time. I'm not sure if this is even possible in clojure, as the types are not declared explicitly, but I think this is what was meant by defprotocol being dynamic. Please correct me if I'm wrong.
The case with them is similar, if you have 10 classes implementing an interface, you don't know at compile time which class instance it is, so you're calling the interface method
then at run time, you go to the class and tell it, "hey, I know you know how to handle this, so if you don't mind"
Protocols are similar but stronger, at that case, the target class doesn't even have to implement the interface. You check for that first, and if it does, you dispatch to it, but if not, there's an extra dispatch table which can be extended dynamically
By dynamically, it means that even at run time, you can extend-protocol
to an existing class you haven't met before
But, in everything there's a price, the price for this sweet feature in protocols is that you have to keep the call-site dynamic
An example of dynamic extension:
(let [extend (fn [protocol this]
;; cljs: class clojure.lang.PersistentList cannot be cast to class clojure.lang.Named
#?(:clj (let [s? (satisfies? Schema this)
is? (satisfies? IntoSchema this)]
(extend-protocol Schemas (class this)
(-schema? [_] s?)
(-into-schema? [_] is?)))))]
(extend-protocol Schemas
nil
(-schema? [_] false)
(-into-schema? [_] false)
#?(:clj Object, :cljs default)
(-schema? [this] #?(:clj (extend Schema this)) (satisfies? Schema this))
(-into-schema? [this] #?(:clj (extend IntoSchema this)) (satisfies? IntoSchema this))))
Taken from an older version of malli(reduce concat (seq {:x 1 :y 2}))
(def m {:a 1 :b 2})
(interleave (keys m) (vals m))
;=> (:a 1 :b 2)
(flatten (seq m))
;=>(:a 1 :b 2)
And then apply
wherever you need that? hard to give a good suggestion without knowing why you'd need it.
Ah, I was looking to use a map for keyword args prior to https://clojure.org/news/2021/03/18/apis-serving-people-and-programs but I now realize I should just destructure in the called function instead of in the caller.
@UGFL22X0Q Yes, that was my question. I wasn’t at computer. Equivalent or not? I checked now, same result. Give back a lazySeq (apply concat m)
but is that correct? First I thought flatten+seq as you wrote.. but my question.. apply concat is correct way to do? or not? (same result)
Dunno! Whatever seems more legible would be my recommendation. I don't think there'd be many cases where the performance would be critical for this kind of thing.
is there a common naming for a xform function for a transducer? And is this something you would do:
(let [xf (comp ...)]
(defn process [...]
(into [] xf ...)))
Is there any official documentation anywhere on the semantics of the #:
dispatch for namespaced keywords? Seeing a lot of blog posts and tutorials and such, but was hoping to find something authoritative
there is
@dorandraco
I did find that document, but it doesn't seem to include the dispatch for namespaced keywords. Unless I'm missing something?
look for Map namespace syntax
aha, thank you!
i, personally, disable it universally
i find it more confusing and generally unnecessary. instead of looking just at the keyword to know what i’m dealing with i now also have to check to see if the whole map is namespaced. i find that extra bit of friction annoying, so i just don’t allow clojure to do it 😛
(the biggest problem with that is disabling it is a bit hacky since, iirc, *print-namespaced-maps*
doesn’t work with pprint’d (and thus cider) data, so i had to drop down to overwriting clojure.pprint/lift-ns
)
I do not know if the Clojure core team is interested in changing the Clojure implementation for this issue, but a question on http://ask.clojure.org might start the ball rolling towards improvements.
i was misremembering my exact reasoning for doing what i did. as @U11BV7MTK mentioned below it was an nrepl problem
i think the arrow on that is backwards. CIDER doesn’t work well with some bound variables. it is CIDER’s bug not Clojure’s. And I believe it is due to nrepl not necessarily CIDER
ah gotcha. i knew is was something in that ballpark. it’s been a bit since i set my stuff up
I'm trying a different approach (different to my previous attempt) to getting sente and reagent in a new project: steps to take: 1. lein new reagent pokedex +cider 2. include sente and http-kit deps in project.clj 3. update server to use http-kit/run-server instead of run-jetty 4. update server and client as per https://github.com/ptaoussanis/sente 5. lein figwheel. the code compiles and the app looks to work mostly fine, but requests over /chsk are all coming back as 403 forbidden. https://github.com/jollyblondgiant/clj-pokedex. what do i need to do to finish sente setup?
you’re not using the anti-forgery wrapper
i’m not sure how reitit-ring works, but this part of the sente readme seems to be missing:
(def my-app
(-> my-app-routes
;; Add necessary Ring middleware:
ring.middleware.keyword-params/wrap-keyword-params
ring.middleware.params/wrap-params
ring.middleware.anti-forgery/wrap-anti-forgery
ring.middleware.session/wrap-session))
i see some of the middleware in your pokedex.middleware
namespace, but you’re missing some or all of these
I added all of those and the behavior persisted
(def app
(let [handler (wrap-defaults #'app-routes site-defaults)]
(if (env :dev)
(-> handler
ring.middleware.keyword-params/wrap-keyword-params
ring.middleware.params/wrap-params
ring.middleware.anti-forgery/wrap-anti-forgery
ring.middleware.session/wrap-session
wrap-exceptions
wrap-reload)
handler)))
there seem to be no guides to using sente in an app created from the reagent template. is this just not done or is it possible?
it’s absolutely possible, i run an app that does it
i don’t know anything about reitit tho so that’s what i don’t know how to help
how do I get started? I've been trying for days. how do I start a reagent app without reitit, or replace it?
just checked, I've removed references to reitit from the handler. still receiving 403's in the browser for /chsk
what was your process to get stood up?
something is wrong with your anti-forgery-token
is this a string I need to invent or is it supplied from the library?
i've updated the repo to reflect my most recent flailing about
okay, i’ve spent too long poking at this but for some reason, :websocket?
isn’t being set on the ring requests, so sente returns nil
that’s the best i’ve got for you, lol
is that something that gets set in the client or the server?
it’s set by http-kit here: https://github.com/http-kit/http-kit/blob/9eac670a71f390a9e988deddfcf3203fa0970de7/src/java/org/httpkit/server/RingHandler.java#L82
i have no idea when/where it would be removed
good news: it's not my fault? bad news: what do I do now
lmao i have no idea
that template is complicated and i don’t understand most of what’s happening
maybe try starting over and not using a template
I am able to build a webserver from scratch or a frontend app from scratch but am at a loss at how to get them to compile together. everything I can find (please do believe I have been looking for days-- perhaps I just don't know where to look) starts with a template somehow, or otherwise only refers to the front- or back- end, but rarely how to roll your own full stack
given Stuart Sierra’s warning against concat
(here: https://stuartsierra.com/2015/04/26/clojure-donts-concat) and that the proposed solution wasn’t ever added to Clojure core, what are the best ways to avoid blowing the stack while still getting the functionality of concat
?
do you have an example of how you’d do that?
(defn -concat [& args] (->Eduction cat args))
Just be sure to not do side effects lazily there
don’t be lazy. Every time i’ve hit this i’ve been able to just use a (into [] ...)
or whatever. Not saying that’s always the fix but that has worked for me
I have an array (5 6 8 9) and I want to calculate the difference of consecutive values like (5 (6-5) (8-6) (9-8)) = (5 1 2 1) How can I calculate that ?
user=> (def l '(5 6 8 9))
#'user/l
user=> (->> l (cons 0) (partition 2 1) (map (fn [[n1 n2]] (- n2 n1))))
(5 1 2 1)
prepend a 0, partition by 2 stepping 1 at a time, map over the pairs and subtract the second from the first
oh map over multiple lists is clever
@U11BV7MTK this is brilliant
I have found a very strange case and I hope it's a bug not a feature. Last sexp fails.
(def t (Integer/parseInt "2"))
(= (type t) (type (Integer/parseInt "2")))
(-> (java.time.Instant/now)
(.minus (Integer/parseInt "2") java.time.temporal.ChronoUnit/DAYS))
(-> (java.time.Instant/now)
(.minus t java.time.temporal.ChronoUnit/DAYS))
; Execution error (IllegalArgumentException) at numbers/eval24676 (REPL:24).
; No matching method minus found taking 2 args for class java.time.Instant
but way it not the same result in both cases? why first case is ok, and not the second?
let bindings and locals being immutable preserve type information when they can, but vars (defs) are mutable and assumed to be Object unless you say otherwise
@U0NCTKEV8 I think I understand the problem of clojure maping vars to method arguments, so I try to type hint var and I failed. This is not the solution. (def t ^Integer (Integer/parseInt "2")) (defn ^Integer ft [] t)
when selecting a method, if the argument is primitive the compiler will convert integers to longs, but not when they are boxed Integer and Long
@U050ECB92 I developed a nice app, it all worked well, but then I added cli. https://github.com/clojure/tools.cli In the example is the evil Integer. Next I spend too many hours to solve this puzzle (without truly understand what is going on), and declared myself and clojure to be non-compatible. This kind of problems are killing my productivity.
or even as @U050ECB92 suggested, use Long/parseLong (to get a Long instead of an Integer, the minus method is documented to take a long anyway)
@U0NCTKEV8 I know the solution, but I didn't understand how compiler works. I was totally shocked that extracting the expression and binding it to var changed the outcome. This is so illogical to me, even now. var is to me a substitution. It seems that var and function results are always boxed, because this type hints are not changing the outcome. (def t ^int (Integer/parseInt "2")) (defn ^int ft [] t
if you used the correct type, a long, parsed using Long/parseLong, boxed as a Long, this would all just work
so no matter what you do with integers, the method is going to be compiled reflectively, and when it gets to being called, the reflector is going to be like "there is no matching method"
and you cannot cast a boxed Integer to a boxed Long, there is no subtype relationship which is required for casting reference types on the jvm
it will be logical to me if this would failed too; (.minus (Integer/parseInt "2") but it is working.
user=> (cast Integer (long 1))
Execution error (ClassCastException) at java.lang.Class/cast (Class.java:3889).
Cannot cast java.lang.Long to java.lang.Integer
user=>
@U0NCTKEV8 thank you
(my cast example is slightly confusing there, because generally you think of long as returning a primitive long, which it does, but cast is a function so its arguments need to be references so the primitive long is boxed)
(set! **warn-on-reflection** true)
is a really useful thing to use, because if you don't get any warnings from it, that means the compiler matched on the java method calls in the code it is compiling
user=> (def ^Integer t 1)
#'user/t
user=> (-> (java.time.Instant/now) (.minus t java.time.temporal.ChronoUnit/DAYS))
Reflection warning, NO_SOURCE_PATH:1:1 - call to method minus on java.time.Instant can't be resolved (argument types: java.lang.Integer, java.time.temporal.ChronoUnit).
#object[java.time.Instant 0x4d4960c8 "2021-11-16T23:46:10.018774496Z"]
user=> (type t)
java.lang.Long
user=>
here's an example where it worked, even though it was typed hinted Integer