Fork me on GitHub

In defmulti/defmethod, can we capture more than one dispatch values in one defmethod? If not, is there a way to call another defmethod from a defmethod?


((get-method somedefmulti some-sispatch-value) & args)
;; Can be used anywhere

👍 1

The value returned by the dispatch function can be a vector of multiple elements. That is still a single return value from the dispatch function, not more than one. Not sure if that would achieve your goal.


Sorry, my question was confusing. I have a multimethod that dispatches on the URL scheme. For example, for HTTP URLs, the value can be :http or :https. I want a single defmethod to handle both :http and :https.


I know I can always emit :http for HTTPS URLs but I want them to be separate.


You could write a function foo that does the common thing, and call it from more than one method defined via defmethod , one for value :http the other for value :https

👍 1

Yeah, that works too. I just wish there was a (defmethod connect #{:http :https} ...).


Why keep them separate if they will always do the same thing?


Same for now. I was planning to split them down the line.


You can use derive, with the caveat that (as far as I know), you need to use namespaced keywords


(derive ::https ::http)
(isa? ::https ::http)
;; => true

(defmulti request-router ( fn [req] (:method req)))
(defmethod request-router ::http [_] "http")
(defmethod request-router ::ssh [_] "ssh")

(request-router {:method ::http})
;; => "http"

(request-router {:method ::ssh})
;; => "ssh"

(request-router {:method ::https})
;; => "http"

👍 1

Actually, it doesn't have to be a namespace keyword if you use a custom hierarchy:

(def h (-> (make-hierarchy)
           (derive :https :http)))

(isa? h :https :http)
;; => true

(defmulti req-router (fn [req] (:method req)) :hierarchy #'h)
(defmethod req-router :http [_] "http")
(defmethod req-router :ssh [_] "ssh")

(req-router {:method :http})
;; => "http"

(req-router {:method :ssh})
;; => "ssh"

(req-router {:method :https})
;; => "http"


Thanks! This example is super helpful.


Greetings! I was wondering if there was a way to integrate a nicer pretty printer like fipp into ? This mentions "may be rebound" but I'm not too sure what this means. Should I alter-var-root another fn that uses fipp ?


that code seems to be expecting you to do something like with-redefs, but that's generally problematic (particularly with multi-threaded code)


the changelog note "replaced *trace-out* with tracer" makes it sound like it was previously using a dynamic var, which is what I would expect for something like this


Hello Alex and thanks for the explanation 🙂 My guess is that *trace-out* used to be a way to redirect from stdout to something else - which is often a concern when having concurrent output. Back to my eye candy issue, using alter-var-root feels promising at this point.


yeah, that will work in most cases (won't work if everything has been aot compiled with direct linking)


Thanks for that remark. I'll make a comment out of it and bear that in mind if I do aot-compile+direct-linking


Here's my result. It works! On a sidenote, it prettifies alright but on second thought, it'd be better if CIDER was doing it. Which will be a distraction for another day 🙂


is it possible to have a running program reload certain files on update?


@sova You could definitely construct code that would do that but when reloading namespaces there are several caveats/gotchas to be wary of.


A running program is no different to a running REPL in Clojure so you can call (require 'my.namespace :reload) inside the program to reload a namespace -- but all the usual caveats about code reloading apply: def (and defn) get reevaluated so any global state you have may be recreated (`defonce` can help you there); any already compiled-in references to defs won't change unless you reload/recompile that code too; changes in records, protocols, types etc can cause problems (because you can get new instances of existing classes, causing class not found exceptions etc; multimethods can also be a pain point unless you handle them correctly.


In the REPL, some folks resort to full namespace reloading through tooling (and often don't even realize what is going on behind the scenes) and you cannot safely do that inside a running program (even the Component Reloaded workflow assumes you stop the program, reload, then restart the program).


(this is all part of why I don't use a "reloaded" workflow even in the REPL -- so I develop better habits about REPL-driven development, constantly re-evaluating code as I change it, from the bottom up -- and that workflow transfers over to applying changes to live, running programs as well)

Kevin Byrne19:12:31

Anyone familiar with running and connecting to a REPL inside a docker image? I was trying to follow the steps here and option to update the project.clj doesn't appear to have any effect.


@kevin.byrne It's unfortunate that no one answered the last question in comments by the OP there: the JVM opts need to be provided as part of the java command that starts the uberjar inside the Docker container


The person who answered did say that but the OP missed it and, instead, focused on putting those options into their project.clj (locally) -- which only has the effect of starting a Socket Server in the local process and very likely blocking the Docker container from attaching to port 5555.


What "steps" from that SO answer have you followed?

Kevin Byrne20:12:54

I've updated my project.clj with `

:jvm-opts ["-Dclojure.server.repl={:port 3001 :accept clojure.core.server/repl}"]


That won't affect the Docker container -- since it's running the uberjar via the java command, right?

Kevin Byrne20:12:35

So I need to add `

-Dclojure.server.repl="{:port 3001 :accept clojure.core.server/repl}"
as well?


Don't put it in project.clj -- unless you want a Socket REPL running locally.


Whatever java command you use inside the container is where the -Dclojure.server.repl=... option needs to go.


If you want a Socket REPL running in your local process as well you need to use a different port number, otherwise it will conflict with the Docker container's exported port.

Kevin Byrne20:12:34

So does `

CMD ["java", "-Dclojure.server.repl=\"{:port 3001 :accept clojure.core.server/repl}\"", "-jar", "app-standalone.jar"]
Look right?


Yeah, modulo the " escaping being correct which I'm not familiar enough with Docker CMD to tell off-hand.


fun trick for tricky string escapes: with gnu shell printf you can use %q to get the escaped / quoted version of the argument - it's just too bad it doesn't work on OSX

✔️ 2

$ printf '%q\n' '"{:foo :bar}"'
\"\{:foo\ :bar\}\"


it errs on the side of being overcareful


Docker will need to export 3001:3001 to expose that outside and then you can use telnet to connect to that Socket REPL.


I'd just throw ' in there instead of nested/escaped "


But you cannot have Leiningen running locally unless you have also removed the :jvm-opts you showed above, or you changed it to a different port (and restarted any existing Leiningen processes).

Kevin Byrne20:12:15

I believe I still have something wrong in the docker command `

CMD ['java', '-Dclojure.server.repl="{:port 3001 :accept clojure.core.server/repl}"', '-jar', 'app-standalone.jar']

Kevin Byrne20:12:35

I ran the uberjar at the command line via java -Dclojure.server.repl="{:port 3001 :accept clojure.core.server/repl}" -jar app-standalone.jar and I was able to attach the repl via telnet 3001

Kevin Byrne20:12:50

but I get rejected by the Docker image.


I assume you opened 3001 outside the image?

Kevin Byrne20:12:59




the other possibility is if the repl makes a localhost only port (which is a sane choice - a repl is effectiviely a root hole)


:address ""

💯 1

add that to your CMD


'-Dclojure.server.repl="{:address "" :port 3001 :accept clojure.core.server/repl}"'


by default it listens on (localhost), so only visible within the docker container

Kevin Byrne20:12:39

So adding the double quotes to the docker command is causing issues.

Kevin Byrne20:12:56


CMD ['java', '-Dclojure.server.repl="{:port 3001 :accept clojure.core.server/repl :address ""}"', '-jar', 'app-standalone.jar']

noisesmith20:12:00 doesn't need quotes


or wait, for clj it does - so you need \ escape

Kevin Byrne20:12:16

CMD ['java', '-Dclojure.server.repl="{:port 3001 :accept clojure.core.server/repl :address \"\"}"', '-jar', 'app-standalone.jar']

Kevin Byrne20:12:44

Is that what you meant? I get error with that saying [java, : not found

Kevin Byrne20:12:53

Which appears to be a Docker parse issue


OK - I don't know what Docker expects here - hopefully someone else does


I would expect Docker to want "..." style strings. When @noisesmith suggested using ' I think he meant just inside the clojure.server.repl=... argument...


oh - I didn't notice that change, yeah


CMD ["java", "-Dclojure.server.repl='{:port 3001 :accept clojure.core.server/repl :address \"\"}'", "-jar", "app-standalone.jar"]

Kevin Byrne21:12:52

So here's where I'm at:

CMD ["java", "-Dclojure.server.repl='{:port 3001 :accept clojure.core.server/repl :address \"\"}'", "-jar", "app-standalone.jar"]

Kevin Byrne21:12:27

This is now getting accepted by Java, but I think the params are not quite right for Clojure, since I'm getting this error: Exception in thread "main" java.lang.ExceptionInInitializerError at clojure.lang.Namespace.<init>( at clojure.lang.Namespace.findOrCreate( at clojure.lang.Var.internPrivate( at com.markmonitor.versionmanager.server.core.<clinit>(Unknown Source) Caused by: java.lang.IllegalArgumentException: Don't know how to create ISeq from: clojure.lang.Symbol


Oh, you know what... you don't need those ' quotes in there I think...


CMD ["java", "-Dclojure.server.repl={:port 3001 :accept clojure.core.server/repl :address \"\"}", "-jar", "app-standalone.jar"]


If that fails to parse, replace the spaces with commas in the {...} part.


I thought they were needed in order to make the {} into a single arg... but I guess the way Docker handles it does that for you already?


Shell quoting is just monstrous 😞


what sean typed should work

Kevin Byrne21:12:58

It appears to have worked

Kevin Byrne21:12:10

Thank you 🙂


As an alternative, what we do is simply invoke a script to launch java, i.e.,


ENTRYPOINT ["sh", "./", ""]


then has exec java .... etc...

dharrigan21:12:32 is the clojure namespace containing main

👍 1

I forgot to mention that the reason why we do it in a script, is that in the script you have far more control over how you initialise the JVM, like choosing the GC, or memory sizes, or adding in agents for metrics. Something that just becomes cumbersome in Dockerfiles to do...


Are functional lenses an idomatic pattern in the Clojure world?


(Summary: no, but I'm gonna ramble about my experiments with lens) When I first introduced myself to Clojure, I purely wanted to use it to experiment, and was valuing it more as a lisp than as Clojure (where I can express perhaps anything), and one of the things I did was implement this unusual .. functional object oriented-ish frankenstein system built on lenses, where possibly all forms of polymorphism were themselves sort of based on lenses. Plus, I was coming from Haskell. (Disclaimer: again, this was for experimentation; don't do this) Meaning, instead of subtyping, if you have the following (excuse me, I used records and protocols instead of, say, maps and multimethods I believe. You don't generally use records in Clojure afaik)

(defrecord Cat [ ... ] )
You would give the cat an 'Animal' lens
;; I forget how I did it, but I think it was all using protocols and structs, instead of maps
;; and multimethods 
(extend-protocol HasAnimal Cat 
   (get-Animal ...) 
   (set-Animal ...))
Once this was defined, Cat would work with all Animal lenses -- and because of the transitive nature of lenses, any lenses Animal extended. So if animals were expected to themselves have a sound lens, which would fetch the sound they'd make, then you could make this function for anything to extend the Animal lens
(defn make-sound [our-animal]
  (println (l-get-in our-animal [animal sound])))
Here animal and sound are lenses being composed, and note you included the animal first to ensure that whatever you're looking at is attempted to be converted into an animal first (and if that succeeds, now it transitively will work with all of animal's lenses; as we know, lens compose). This would work for any sort of behavior --if you wanted to give something 'battle behavior', you'd ask "what data can express the change battle has to cause? Well, how about {:hp :strength :defense} (except I think, again, I used records). Ok, now let's extend this type we have to allow battle
;; Interface for battle
(defrecord BattleBody [hp strength defense])
(make-lenses BattleBody)

(defrecord ComplexType [ health initial-strength magic-strength status-effects ...])

(extend-protocol HasBattleBody
   (get-BattleBody [obj] 
      { :hp (:health obj)  
        :strength (+ (:initial-strength obj) 
                     (magic-strength obj) 
                     (get-status-effect-strength-multiplier obj)})
   (set-BattleBody [obj body] 
     (assoc obj :health (:hp body)))))
       ;; Perhaps this 'BattleBody' only can change health -- not strength or defense, which as it is 
       ;; may have lost information in what individual stats they were derived from
(def battle [attacker defender]
  (l-update-in defender [body health] - (l-get attacker strength)))
Where body and health and strength are lenses. I may be violating some lens laws there, I forget -- this is the first time I had an example where I realize I might not want it to perfectly, symmetrically, get and set I would never do real Clojure this way, but it is useful for looking at things in different ways. However, there has finally been a recent use where I've considered for a personal project (never otherwise) using a watered down map version, where I have registers on a cpu that can be accessed like this AF , which will will view the registers A and F together as one 16 bit integer, if I remember A , which will just view the register A as an 8 bit register F , which will do the same I still like the ability to do things like (l-set cpu :a 0xff) (l-set cpu af 0x11ff) Although now that I'm away from the problem, I forget why I wanted to do that in this case, and not have some other functions for it (perhaps it was for fun again, but I feel like there was something I wanted to actually accomplish) Anyways, unlike Haskell many of the nice features you might be trying to accomplish with lenses are not exactly a problem to begin with, and already have simple implementations


Interesting, thanks for sharing. Using protocols and structs didn’t immediately come to mind. Just fns and paths (vectors)


I like the ability to abstract data location, but it’s fine enough to have a central reference for all needed paths


As long as its clear I am not advocating for this ahahaha and if you are going to mess with this, I would probably use maps and multimethods, but even then I need to think about this for a moment as its been a while since I messed with that


And there are other pitfalls to watch out for


The main advantage I was looking for was abstracting and composing paths, which is possible with Clojure data structures.


Just wasn’t apparent to me until we started talking about it


yea as it is that's enough for 90% of everything

parrot 1

and for the other 10%, I don't think the logical overhead of introducing something new like lenses is worth it


not to mention depending on how its done it can be a long winded way of describing something


Yeah, might as well go back to POJOs


there were other issues -- I think, for instance, you can't just extend something with the protocol to use a lens, and then immediately use the lens, which was the point -- you have to recompile the lens each time, and so then there's another mess to think about. Something like that anyways, as mentioned its been a while


using just maps and functions and specs is simple and seems to go a long way, although like everything else I'm still exploring it and exploring it


Yeah, I’d like to start doing that myself


Sounds like you’re further into than I


I'm surprised the docs don't mention this, but FYI defstruct is no longer recommended. You can use defrecord for things that should act like maps or deftype for immutable data with defined updatable keys


or you know, just use hash-maps and functions like normal code :D


actually, I think it was defrecord ahahah


the code sample above uses defstruct


Yeah but that's what I'm saying, now that you mention it I think the original experiment used defrecord , not defstruct. This wasn't copy and pasted from that, but me trying to recall how it was written, and since I never use those now, I think I got defrecord mixed up with perhaps Common Lisp's defstruct


that's fair - I think ComplexType would be defprotocol in that case


I'm making some edits right now. The ComplexType part is correct, in that it should be a record; the ComplexType represents some type that should inherit, in this case, battle behavior. Perhaps I'll add an edit with two types that can be imagined; Player and Sword, two things that might respond to being attacked, but might not look the same at all internally. One line I just added is there's a (make-lenses BattleBody) that should be below BattleBody; this generates a lens for that BattleBody, and the protocol HasBattleBody as well as lens and protocols for hp and strength and defense I believe. Anything now that extends the HasBattleBody protocol can use the BattleBody lens, and will work with functions defined on top of that interface


OK - in that case HasBattleBody can't extend ComplexType


OH! I read it backward, never mind


Aha yeah I think that can be easy to do since there's both

(extend-type Type
(extend-protocol Protocol 
For me anyways, this threw me for a loop once as I had gotten used to one order, only to one day see the other macro used without realizing the switcheroo


thanks to less type enforcement, a big part of their utility disappears


no, they are not common at all (though there are libraries that provide some of their features)


we have get-in and update-in and assoc-in which provide some overlap but definitely are not the same thing


What do clj devs do when there’s a path change?


Do they change the path everywhere?


Or are there intermediate fns?


Speaking generally of course


those functions all take path as a data structure, that data structure can be a top level shared binding


or an arg to some other function


or constructed in whatever manner


Ah, makes sense. So usually a global ref to the path


also we try to avoid nesting - we merge maps when apropriate


once again, without strong type enforcement that becomes an option


Sure, do you have a general resource for the advantages of Clojure’s lack of strong type checking? I know that’s a huge question, and I’ve seen many of Hickey’s talks, but I wondered if there were a blog post or docs that explain examples like these.


“In the typed functional world we do…but in clojure it’s just <this> because of <this>”


Rich Hickey's talks about clojure.spec might be particularly interesting here


Hmm, I’ll have to re-watch it


they tend to make Haskell fans angry though haha


I wouldn’t say I’m a Haskell purist, more so interested in various solutions and why and how to use them


the advantage is that you don't need to spend any time trying to prove something to the compiler


either during initial development or as the system evolves


I mean, that certainly is nice (die TypeScript!)


that's a good point - there are things that are easy and effective to use but hard to formally prove


the downside is that you didn't prove anything to the compiler

😂 1

haha, and spec is the middle ground


hopefully somewhere in there, yes :)


yeah, I really miss OCaml refactoring sometimes - I could just change the type, and the compiler would lead me to all the relevant code that needs updating until the refactor was done


That’s what I loved about Elm


the assertion here is not that types are bad or don't have benefits, but that types have (often ignored) costs


I certainly know the pain


and Clojure is the hypothesis that you can make robust systems that evolve gracefully over time without types


Is spec.alpha2 prod ready? (Despite the name)


you can certainly make robust systems with types (and things like compilers are probably better that way) but I think the evidence for "graceful evolution" are, at best, mixed


spec.alpha2 - no


but the end is in sight


I’ve actually been wanting to play with bringing spec to JS now that we have Babel macros


there are a couple ports out there


There is js.spec, but it doesn’t do function signatures (cause no macros)


But that’s changed since it’s been written


ah yeah, that's the main one I was thinking of


well, function signature stuff is going to change a lot in spec 2, tbd


Yeah, I remember seeing the talk, I remember being excited, but I’ll have to rewatch it


no talk about this :) it's in design right now


unless you mean the schema/select stuff, which is out there now


Yes, that ^


Yes, it was select that was so exciting


But there’s more? 👀


Is there a public design doc?


that's more of the retroactive part :) the design edge is not in public.


---- Oh oops I didn't think that would expand here ahah I just wanted it to say 'Blah blah commented in ...' so it wouldn't potentially be buried


😮 Reading… haha