Fork me on GitHub
#beginners
<
2019-12-16
>
hindol.adhya08:12:34

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?

delaguardo10:12:52

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

andy.fingerhut08:12:04

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.

hindol.adhya08:12:55

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.

hindol.adhya08:12:57

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

andy.fingerhut08:12:57

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

hindol.adhya08:12:39

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

andy.fingerhut08:12:22

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

hindol.adhya08:12:56

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

dromar5608:12:38

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

dromar5608:12:13

(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"

dromar5609:12:48

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"

hindol.adhya11:12:35

Thanks! This example is super helpful.

zor16:12:58

Greetings! I was wondering if there was a way to integrate a nicer pretty printer like fipp into clojure.tools.trace ? https://github.com/clojure/tools.trace/blob/e6bc29e4c1d9d69d6d9d7cce62ee8da02662d284/src/main/clojure/clojure/tools/trace.clj#L71 This mentions "may be rebound" but I'm not too sure what this means. Should I alter-var-root another fn that uses fipp ?

alexmiller16:12:17

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

alexmiller16:12:26

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

zor16:12:29

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.

alexmiller16:12:11

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

zor16:12:06

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

zor16:12:54

Here's my result. It works! https://gist.github.com/mrzor/7aab0b628d73d7d2d87e0dfb02030e99 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 🙂

sova17:12:28

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

seancorfield17:12:49

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

seancorfield17:12:23

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.

seancorfield17:12:36

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).

seancorfield17:12:49

(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 https://stackoverflow.com/questions/50018490/how-to-repl-uberjar-inside-a-docker-container and option to update the project.clj doesn't appear to have any effect.

seancorfield19:12:12

@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

seancorfield19:12:31

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.

seancorfield20:12:11

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}"]

seancorfield20:12:16

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?

seancorfield20:12:07

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

seancorfield20:12:38

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

seancorfield20:12:18

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?

seancorfield20:12:20

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

noisesmith20:12:09

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

noisesmith20:12:30

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

noisesmith20:12:50

it errs on the side of being overcareful

seancorfield20:12:00

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

noisesmith20:12:32

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

seancorfield20:12:55

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 127.0.0.1 3001

kevin.byrne20:12:50

but I get rejected by the Docker image.

noisesmith20:12:48

I assume you opened 3001 outside the image?

kevin.byrne20:12:59

`

EXPOSE 3001

noisesmith20:12:28

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

ghadi20:12:38

:address "0.0.0.0"

ghadi20:12:42

add that to your CMD

ghadi20:12:13

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

ghadi20:12:44

by default it listens on 127.0.0.1 (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 "0.0.0.0"}"', '-jar', 'app-standalone.jar']

noisesmith20:12:00

0.0.0.0 doesn't need quotes

noisesmith20:12:16

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 \"0.0.0.0\"}"', '-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

noisesmith20:12:25

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

seancorfield21:12:56

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

noisesmith21:12:23

oh - I didn't notice that change, yeah

seancorfield21:12:52

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

seancorfield21:12:11

@kevin.byrne ^^

kevin.byrne21:12:52

So here's where I'm at:

CMD ["java", "-Dclojure.server.repl='{:port 3001 :accept clojure.core.server/repl :address \"0.0.0.0\"}'", "-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>(Namespace.java:34) at clojure.lang.Namespace.findOrCreate(Namespace.java:176) at clojure.lang.Var.internPrivate(Var.java:156) 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

seancorfield21:12:49

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

seancorfield21:12:07

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

seancorfield21:12:34

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

noisesmith21:12:34

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?

seancorfield21:12:34

Shell quoting is just monstrous 😞

ghadi21:12:54

what sean typed should work

kevin.byrne21:12:58

It appears to have worked

kevin.byrne21:12:10

Thank you 🙂

dharrigan21:12:19

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

dharrigan21:12:41

ENTRYPOINT ["sh", "./launch.sh", "foo.bar.baz"]

dharrigan21:12:59

then launch.sh has exec java .... etc...

dharrigan21:12:32

foo.bar.baz is the clojure namespace containing main

dharrigan06:12:07

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...

mruzekw22:12:16

Are functional lenses an idomatic pattern in the Clojure world?

zdot10123:12:26

(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
   ComplexType
   (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
        
Then
(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

mruzekw23:12:38

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

mruzekw23:12:05

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

zdot10123:12:21

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

zdot10123:12:34

And there are other pitfalls to watch out for

mruzekw23:12:41

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

mruzekw23:12:54

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

zdot10123:12:10

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

zdot10123:12:50

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

zdot10123:12:41

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

mruzekw23:12:00

Yeah, might as well go back to POJOs

zdot10123:12:02

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

zdot10123:12:53

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

mruzekw23:12:28

Yeah, I’d like to start doing that myself

mruzekw23:12:37

Sounds like you’re further into than I

noisesmith00:12:30

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

noisesmith00:12:56

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

zdot10100:12:37

actually, I think it was defrecord ahahah

noisesmith00:12:10

the code sample above uses defstruct

zdot10100:12:52

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

noisesmith00:12:36

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

zdot10100:12:52

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

noisesmith00:12:46

OK - in that case HasBattleBody can't extend ComplexType

noisesmith00:12:33

OH! I read it backward, never mind

zdot10101:12:29

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

(extend-type Type
   Protocol)
and
(extend-protocol Protocol 
  Type)
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

noisesmith22:12:55

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

noisesmith22:12:11

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

noisesmith22:12:39

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

mruzekw22:12:08

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

mruzekw22:12:21

Do they change the path everywhere?

mruzekw22:12:29

Or are there intermediate fns?

mruzekw22:12:42

Speaking generally of course

noisesmith22:12:51

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

noisesmith22:12:58

or an arg to some other function

noisesmith22:12:07

or constructed in whatever manner

mruzekw22:12:13

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

noisesmith22:12:33

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

noisesmith22:12:46

once again, without strong type enforcement that becomes an option

mruzekw22:12:25

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.

mruzekw22:12:22

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

noisesmith22:12:42

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

mruzekw22:12:56

Hmm, I’ll have to re-watch it

noisesmith22:12:58

they tend to make Haskell fans angry though haha

mruzekw22:12:24

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

alexmiller22:12:21

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

alexmiller22:12:48

either during initial development or as the system evolves

mruzekw22:12:49

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

noisesmith22:12:53

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

alexmiller22:12:08

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

mruzekw22:12:19

haha, and spec is the middle ground

alexmiller22:12:33

hopefully somewhere in there, yes :)

noisesmith22:12:42

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

mruzekw22:12:54

That’s what I loved about Elm

alexmiller22:12:08

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

mruzekw22:12:14

I certainly know the pain

alexmiller22:12:46

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

mruzekw22:12:04

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

alexmiller22:12:46

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

alexmiller22:12:51

spec.alpha2 - no

alexmiller22:12:14

but the end is in sight

mruzekw22:12:08

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

alexmiller22:12:22

there are a couple ports out there

mruzekw22:12:41

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

mruzekw22:12:50

But that’s changed since it’s been written

alexmiller22:12:01

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

alexmiller22:12:20

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

mruzekw22:12:48

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

alexmiller22:12:24

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

alexmiller22:12:38

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

mruzekw22:12:45

Yes, that ^

mruzekw22:12:59

Yes, it was select that was so exciting

mruzekw22:12:19

But there’s more? 👀

mruzekw22:12:53

Is there a public design doc?

alexmiller23:12:56

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

zdot10123:12:41

---- 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

mruzekw23:12:51

😮 Reading… haha