Fork me on GitHub
#beginners
<
2019-04-25
>
joel38002:04:22

i want to have clojure files that I load dynamically, I want each to give different implementations of the same interface (forgive the OO terminology). @mail462 pointed me to using multimethods, however, I'm not seeing how I load a given file and start using it's implementation vs. another file w/it's different implementation.

mail46202:04:39

let me see if I can whip up an example

joel38003:04:16

OK, so part of the trick there is knowing what keyword they supported. Not to be difficult, but is there a way to basically ask what they support?

jaihindhreddy08:04:12

Of course there is. run (doc methods) in your REPL.

joel38003:04:57

In my case, i want to know what the implementation is for (what "event") so that i can use it when that even happens.

mail46203:04:46

not sure about that but it seems on the surface like having to know such detail would defeat the purpose...

mail46203:04:37

if you encounter an event in the wild, all you need to do is invoke, say, (handle-my-event <some-distinct-property-of-the-event>)

mail46203:04:52

without having to know anything about the various handle-my-event implementations

mail46203:04:36

multimethods by design handle routing to the correct impl

mail46203:04:52

note that the dispatch fn in my example is (fn [meal-time] meal-time) but you can do all sorts of things there to detect properties of your event

mail46203:04:40

so if you invoked (handle-my-event <the-event>) then your dispatch fn could extract a distinct property of it (say, event name or type) and return that. then you only need to define defmethods for those possible values

joel38003:04:47

i think i can extrapolate from this example... thanks a bunch.

ben60610:04:41

How are these expressions different?

user=> (#(Boolean/valueOf ^String (or % "true")) true)
true
user=> (#(Boolean/valueOf ^String %) (or true "true"))

Execution error (ClassCastException) at user/eval42858$fn (form-init549726585980450320.clj:1).
java.lang.Boolean cannot be cast to java.lang.String

mail46211:04:31

I'd expect it's because the reader attaches ^String to the next form read, not the next value. so in the first case ^String attaches to the form (or ..), but that evaluates to an untagged value true -- equivalent to (#(Boolean/valueOf %) ...) (no type hint at all). in the second case, ^String attaches to %, i.e. tagging it, which hints the typed dispatch, and yields a class cast exception with true as the value

ben60612:04:00

Ahhh okay. So the part of my mental model that was wrong, was that I expected that the tag would propagate with the form (as it gets evaluated) or that it was attached to the actual value.

ben60612:04:12

Thanks very much, this is really interesting!

ben60612:04:49

So in this case, (#(Boolean/valueOf ^String (or % "true")) true), having the annotation is irrelevant?

mail46212:04:54

I think so. you can make it relevant by attaching it to the identifier %: (#(Boolean/valueOf (or ^String % "true")))

k.i.o12:04:10

indeed

'(#(Boolean/valueOf ^String (or % "true")) true)
'(#(Boolean/valueOf ^String %) (or true "true"))
'(#(Boolean/valueOf (or ^String % "true")) true)

((fn* [p1__14629#] 
   (Boolean/valueOf ^{:tag String} (or p1__14629# "true"))) true)

((fn* [p1__14632#] 
   (Boolean/valueOf ^String p1__14632#)) (or true "true"))

((fn* [p1__14635#] 
   (Boolean/valueOf (or ^String p1__14635# "true"))) true)

ben60612:04:03

Fascinating. Thank you very much to both of you!

k.i.o13:04:08

Two last expressions generate this casting code.

final String s = (String)o;
this = null;
return Boolean.valueOf(s);
While first expression type hint is irrelevant and generates reflective code. Where o is a result object of (or) s-form evaluation.
args[n] = o;
return Reflector.invokeStaticMethod(classForName, methodName, args);

k.i.o13:04:13

(eval ^String (or true "string"))
=> true
(meta (eval ^String (or true "string")))
=> nil

michael.e.loughlin17:04:55

@ how do you see the generated code?

ben60610:04:29

why is this bool acceptable, but a plain true is not?

user=> (#(Boolean/valueOf ^String (or % "true")) true)
true
user=> (type (or true "true"))
java.lang.Boolean

tabidots11:04:11

edit: Any way to speed up reductions in a similar way to how clojure.core.reducers optimizes everything? I know Clojure is not really the ideal language to implement primitive math and numeric algorithms in, but I’ve been having a lot of fun lately trying my hand at translating a bunch of number theory algos into a functional style. Most end up looking good and working well—not as fast as the equivalent C implementation, of course, but a lot easier on the eyes. Anyway, I’ve spent the better part of this week trying to understand and implement the Pollard-Brent algorithm (an extension of the Pollard rho algorithm). After 5 rewrites (including a couple terribly imperative/mutative ones with atoms and @s all over the place), I finally have something that is functional (in both senses of the word). BUT… it’s very very slow. Given the same test values, this takes 4 minutes to factor a ~45-digit number vs. the Python implementation, which takes 3 seconds (!!!). Is there anything I can do to fine-tune this code while keeping the functional style? (Everything is included in the gist, including the test values and Python code) https://gist.github.com/52e3ee425ca5930894f1efa20fb451f7#file-brent-clj

tabidots11:04:11

edit: Any way to speed up reductions in a similar way to how clojure.core.reducers optimizes everything? I know Clojure is not really the ideal language to implement primitive math and numeric algorithms in, but I’ve been having a lot of fun lately trying my hand at translating a bunch of number theory algos into a functional style. Most end up looking good and working well—not as fast as the equivalent C implementation, of course, but a lot easier on the eyes. Anyway, I’ve spent the better part of this week trying to understand and implement the Pollard-Brent algorithm (an extension of the Pollard rho algorithm). After 5 rewrites (including a couple terribly imperative/mutative ones with atoms and @s all over the place), I finally have something that is functional (in both senses of the word). BUT… it’s very very slow. Given the same test values, this takes 4 minutes to factor a ~45-digit number vs. the Python implementation, which takes 3 seconds (!!!). Is there anything I can do to fine-tune this code while keeping the functional style? (Everything is included in the gist, including the test values and Python code) https://gist.github.com/52e3ee425ca5930894f1efa20fb451f7#file-brent-clj

tabidots11:04:28

Ah, good idea. Well, I just put a bunch in but it didn’t make a difference. I think the only functions that could make use of them are tower/abs and tower/gcd. My mod-pow and mod-mul functions don’t have any provisions for them (though I added type hints to those calls anyway)

tabidots11:04:55

So I hacked and chopped up the function a bit to figure out the bottleneck and the calculation of the qs is a huge bottleneck

tabidots11:04:21

And I changed mod-mul to *' just to see if it was my function’s fault, but it seems to be the reductions

tabidots12:04:20

I tried to rewrite reductions using r/reduce but it didn’t help

(defn rdns
  [rf init coll]
  (r/reduce (fn [res b]
              (let [a (peek res)]
                (conj res (rf a b))))
            [init] coll))

tabidots12:04:30

😅 You know what ended up yielding a pretty big speedup? Replacing my own mod-mul function (which uses bit shifting and all) with plain old built-in *' and mod

michael.e.loughlin17:04:51

what speed does it run at now compared to Python?

michael.e.loughlin17:04:05

It might be worth asking on Reddit, optimisation questions there seem to get a lot of responses 😉

twaima13:04:32

should we add ! if side effect is outside of the app? send-to-server! or send-to-server?]

k.i.o13:04:04

all that ! thing is not clear for me either, i've heard Stuart Sierra doesn't like it

twaima13:04:44

I would like if it was a clear convention...

k.i.o13:04:51

i guess we use it do differentiate between mutable \ immutable - blocking \ non-blocking operations if there is ambiguity present

k.i.o13:04:13

but i need confirmation for myself from more experienced developers, probably someone from cognitect?

twaima13:04:04

AFAIK it came from Lisp language...

k.i.o13:04:27

well clojure IS a lisp, there is no such thing as "Lisp language" rather lisp dialects, as i see it

michael.e.loughlin13:04:02

it's definitely documented in the Scheme spec >By convention, the names of procedures that store values into previously allocated locations (see section 5.10) usually end in “!”

michael.e.loughlin13:04:25

idk if other Lisps use it as well

k.i.o13:04:41

i would like if we adopted strict rule for ! in non pure functions names

andy.fingerhut14:04:51

That is not going to happen for functions in core, I am 99.9% sure.

michael.e.loughlin13:04:19

as far as I can tell it's used to denote a mutation in Scheme, not IO

k.i.o13:04:54

which is kinda half measure, just to keep core lib function names more uniform as i see it

john14:04:54

I think I've heard folks say that ! should apply to io too, but I think if everyone conformed to that standard we'd see a lot more bangs everywhere.

john14:04:50

'Impure functions' is probably a more solid standard

john14:04:16

After all, what is or isn't IO depends on where you sit in a given abstraction

dpsutton14:04:32

load-users¡

dpsutton14:04:40

upside down exclamation mark is criminally underused

john14:04:12

lol if you want side effects, you should want it enough to use a non-standard character 🙂

dpsutton14:04:18

but i agree about ! generally meaning impure

alexmiller14:04:06

depending on who you ask, ! means "impure", "unsafe in a transaction", or just "beware"

k.i.o14:04:35

! for beware is a crime

hobosarefriends15:04:48

That’s kinda the default one, considering it encompasses the previous 2. 😅

noisesmith16:04:22

also impure/unsafe in a transaction are mostly the same thing, depending on how you feel about erroneous logging / printing

hobosarefriends16:04:34

! for unavoidable complecting?

alexmiller16:04:23

the number of !'s tells you how many times you should consider alternatives before committing to this design choice

alexmiller16:04:36

think twice about launch!!!

alexmiller16:04:43

actually, think 3 times

dpsutton16:04:33

never even heard of it 🙂

seancorfield16:04:09

I inherited its use in clojure.java.jdbc inside the JDBC transaction code and I've just sort of kept it there (and it's in the equivalent in next.jdbc now). I've never seen it used anywhere else TBH.

dpsutton16:04:33

i like it i've just never used the STM

hobosarefriends16:04:37

oh in that case I need to add a lot of ! everywhere..

seancorfield16:04:27

(the discussion about ! made me think about it -- because clojure.java.jdbc uses ! on functions that are expected to perform database updates -- but next.jdbc has only reducible!, execute!, and execute-one! since they could all perform database updates)

seancorfield16:04:21

io! is specific to STM transactions and seems like a good idea but I've never seen it used (outside that one place).

noisesmith16:04:59

so swap! doesn't check for it?

seancorfield16:04:08

Nope:

user=> (def a (atom 0))
#'user/a
user=> (swap! a #(io! (inc %)))
1

seancorfield16:04:21

(no transaction there -- only with refs)

seancorfield16:04:43

At least, that's my understanding of when io! is checked.

seancorfield16:04:50

I've always been surprised that alter-var-root does not have ! (but alter-meta! does... and alter itself does not).

alexmiller16:04:13

I think it’s safe only to say that ! is used inconsistently

lennart.buit16:04:56

naming things remains an issue and other unsurprising facts of 2019

david.folkner16:04:51

Pretty dumb question. I have sets of suits and card ranks (like in the spec guide). I am trying to make a vector of cards taht looks like [{:suite :spade, :rank 7} {:suite :diamond, :rank :ace}] I have a code that looks like this:

(def suit? #{:club :diamond :heart :spade})
(def rank? (into #{:jack :queen :king :ace} (range 2 11)))
(def cards (reduce (fn [new-cards [rank suit]]
                     (into new-cards [{:suit suit :rank rank}]))
                   []
                   (for [suit suit? rank rank?] [rank suit])))

david.folkner16:04:30

There has to be a better way to do that...

dpsutton16:04:13

you already enumate them into vectors [rank suit] why not just make the map there?

john16:04:20

? is inconsistently used too

david.folkner16:04:34

Yeah...that was taken straight from the spec guide.

david.folkner17:04:05

They use s/valid? suite? :diamond) kinda stuff. I think that's why they have the ?

dpsutton17:04:35

(def cards (for [suit suit? rank rank?] {:suit suit :rank rank}))

david.folkner17:04:32

Well now I feel silly.

david.folkner17:04:02

I've been reading Clojure for Brave and True and they don't mention for at all. So I'm used to using reduce for everything.

dpsutton17:04:45

i'm not a huge fan of for either. but if you already have the combinations then you are basically done 🙂

david.folkner17:04:27

Is there a different way to cross those two seqs like they've done with the for

dpsutton17:04:59

(let [cards (fn [suit]
              (map (fn [facecard] {:suit suit :card facecard}) cards))]
 (mapcat cards suits))

seancorfield17:04:13

(map #(hash-map :suit %1 :rank %2) suit? rank?)

seancorfield17:04:01

A lot of times, I think for reads more clearly than map but there's always that nagging confusion with for-loops in other languages whereas it's a for-comprehension in Clojure...

dpsutton17:04:27

that won't give you the combinations

dpsutton17:04:26

i just always forget how it works with multiple bindings. if its like map over two collections or if it gives permutations

dpsutton17:04:38

map and co are always easier to understand for me

seancorfield17:04:06

Oh, right. Yeah, then for is definitely "better" in this case. IMO.

seancorfield17:04:29

(I was just focused on trying to get rid of your mapcat 🙂 )

lvbarbosa17:04:30

Is there an easy way to write a checker in midje like throws that checks for slingshot throw+ exceptions?

pavlos17:04:02

Hello all! Is there any update on this? https://dev.clojure.org/jira/browse/CLJ-1899 It's Add function transform-keys to clojure.walk

seancorfield18:04:41

There's nothing on that ticket since 2016 so, no updates.

kari.marttila18:04:26

Am I asking for trouble if I install Clojure command line tools somewhere else than the default /usr/local? I was wondering that after Clojure 1.10 if I want to install Clojure cli 1.11 and keep 1.10 around I can't install them to /usr/local. E.g. I usually install all my Java versions to /mnt/local/ (i.e. /mnt/local/jdk8, /mnt/local/jdk9... and add those to my path per project).

ghadi18:04:00

@kari.marttila the clojure tools are able to pull different versions of clojure

alexmiller18:04:17

yeah, any version of the cli can run any version of clojure (despite the version number in the name)

alexmiller18:04:45

the version number is really more an indication of vintage and also what the internal classpath-building code is using

alexmiller18:04:01

so, in short, there is no reason to have anything but the latest installed

kari.marttila18:04:05

Ok. Good to know. So, basically I can keep the Clojure CLI tools then in the default directory /usr/local then.

kari.marttila18:04:25

Ok. Thanks for clarifying that to me.

alexmiller18:04:32

the Clojure version you use is determined by your deps.edn

kari.marttila18:04:55

BTW. I really appreciate this community. Great people. And Clojure really blew my mind when I started to use it. I have tried to promote Clojure in my corporation - a great language.

kari.marttila18:04:55

There seems to be Clojure gurus around - I hope you don't mind me asking one thing that has been in my mind recently.

kari.marttila19:04:00

I have read Stuart Sierra's Clojure workflow, regarding how to make Clojure application so that you are able to start / stop etc the application in repl without needing to start repl, http://thinkrelevance.com/blog/2013/06/04/clojure-workflow-reloaded

kari.marttila19:04:32

I was wondering is this some kind of best practice how to structure Clojure application?

kari.marttila19:04:59

I mean, making the development workflow fluent with repl...

kari.marttila19:04:39

Are there some other "recommendations" or "best practices" how to structure a Clojure application and work with repl?

seancorfield19:04:12

@kari.marttila We use Component very heavily. It's very convenient to be able to build a "system", start it, run stuff, and stop it again in the REPL. We use it in our test suites too, to spin up local test versions of servers and shut them down again after tests have run.

seancorfield19:04:53

We don't generally use the actual "reloaded" part tho' -- we very rarely find the need to completely clean out and restart the REPL.

seancorfield19:04:04

I start up Cognitect's REBL with a Socket REPL and connect to that from my editor (Atom w/Chlorine) and leave it running for days and days just evaluating code into it from my editor (`C-, i` and C-, I are bound to "inspect form" and "inspect top-level form" -- which evaluates and sends the expression to REBL). I use "Rich Comment Forms" (the (comment ..) form containing non-production code to aid development) and those often have things like

(def app (component/start (app/build-system {..})))
(component/stop app)
... other useful code ...
So I can easily spin up my application in the REPL and shut it down whenever I need.

seancorfield19:04:41

@hiredman may have additional thoughts on "reloaded" and our workflow.

kari.marttila19:04:40

Thanks! Great info! I try to use Component also in my next personal Clojure project.

seancorfield19:04:18

There's a #component channel if you get stuck (it's low traffic so it's easy for folks like me to keep an eye on).

kari.marttila19:04:34

And thanks also for mentioning Cognitect REBL - I didn't know that such a tool exists. http://rebl.cognitect.com/ I definitely need to try it in my next personal Clojure project.

kari.marttila19:04:58

I need to open my mouth more often in this #beginners channel - great info.

seancorfield19:04:20

I love REBL! (there's a #rebl channel too, BTW)

seancorfield19:04:47

I'm starting to switch from clojure.java.jdbc to next.jdbc at work and that supports datafy and nav out of the box so it's great for working with REBL since you can easily navigate through your result sets into other tables etc.

hiredman19:04:09

Component works well but is very flexible in it's usage so you can end up with different styles of usage. On the one end the is passing the whole system around and on the other end you pass around just components. The library itself doesn't provide guidance there and people end up at different places.

kari.marttila19:04:28

Ok. Good to know.

kari.marttila19:04:41

BTW. Can you recommend some good project in Github that uses Component (that is not too complicated - i.e. to be used as an example)?

seancorfield19:04:57

I tend to point people at this https://github.com/framework-one/fw1-clj/blob/master/examples/usermanager/main.clj -- not because it's "good" but just because it's a fairly simple, self-contained web app + database example.

danie13:04:46

@ thank you, this is exactly the kind of thing I need to let a new developer play with to get to grips with the bigger picture

seancorfield19:04:14

(it doesn't even have a Component for the database setup, which it really should)

seancorfield19:04:51

I ought to migrate it out of that repo and update it to reflect better practices...

seancorfield19:04:29

What I do like about that example is that it builds the application as a component, separate from the web server component, and it uses middleware to add the application component to the Ring request hash map so it is available at the top-level for all the handler functions to access.

seancorfield19:04:05

If it had a component for the database, as a dependency of the application, then Ring handlers could pass just that down into the model functions etc.

kari.marttila19:04:35

Ok. I need to read the example carefully before my next Clojure project.

seancorfield20:04:37

Feel free to DM me if you have questions.

hobosarefriends22:04:57

Thanks Sean. You’re a treasure.