Fork me on GitHub
#beginners
<
2020-05-08
>
Chris K00:05:03

How hard do you think is the shift from FP to OOP? I've heard many people say that OOP to FP is hard because to "unlearn" the things you already know, but I wasn't sure for the opposite

noisesmith00:05:52

that's kind of a subjective question, I think sometimes I have thought that OO is more intuitive than FP, even if it's less resilient

noisesmith00:05:54

there's also OCaml and Erlang which seem like good models of FP OO (and Clojure shares some design features with both)

potetm01:05:31

idk, but I do think I’m much better at OO after having done clojure for years

phronmophobic01:05:28

i also feel like i learned a lot about OO after learning clojure. for example, i had never heard the phrase “single dispatch” before.

Scott Starkey02:05:26

OK - I’m thinking game ideas and need random numbers. I tried this and didn’t get what I was expecting:

(rand-int 5)
=> 1
(rand-int 5)
=> 0
(repeat 10 (rand-int 5))
=> (4 4 4 4 4 4 4 4 4 4)
Do I need to seed the randomization somehow? Why am I always getting the same number from a rand-int?

potetm02:05:42

(repeat 10 4) is what was run

potetm02:05:12

I think repeatedly might be what you want.

Cameron02:05:20

(and to pull the first example off google:)

(take 10 (repeatedly #(rand-int 5)))

potetm02:05:21

(repeatedly 10 #(rand-int 5))

Cameron02:05:08

(where the former will give you an infinite lazy list with (repeatedly #(rant-int 5), useful if you're generalizing that and reusing it, such as with

(let [ powerball-numbers       (repeatedly #(rand-int 100))
       my-powerball-numbers    (take 10 powerball-numbers) ;; Clearly I don't play powerball
       their-powerball-numbers (take 10 powerball-numbers)]
  {:mine my-powerball-numbers 
   :theirs their-powerball-numbers})
and the latter will more directly, and concisely, give you the 10 numbers you need right away)

👍 4
noisesmith02:05:45

my-powerball-numbers and their-powerball-numbers are the same

Cameron02:05:47

They probably cheated

😄 4
Cameron02:05:06

That being said, I did not know it would not produce fresh values each time

noisesmith02:05:39

lazy-seqs are immutable and cached, reading the nth item of a given lazy-seq always returns the same value

Cameron02:05:27

I'm playing with it now, I see they work / produce fresh values when produced from separate function calls, but not when created in the same let

noisesmith02:05:43

if you call repeatedly again, you get a different lazy-seq, but if you reuse the same lazy-seq (the return of one call to repeatedly) you'll get the same values in the same order

Cameron03:05:25

ah yep I see it now

noisesmith03:05:31

so clearly what you need is (repeatedly (fn [] (repeatedly #(rand-int 5))))

noisesmith03:05:41

or maybe not :D

noisesmith03:05:17

a fun way to do your pass/fail above: #(rand-nth (into [] cat [(repeat 7 "Passed!") (repeat 3 "Failed!")])

noisesmith03:05:26

better to construct that vector outside the function or hard code it actually

Cameron03:05:45

personally, as an optimist, I'd like to go with (repeat 10 "Passed!")

noisesmith03:05:11

in that case you can just use (constantly "Passed!")

noisesmith03:05:40

oh never mind, if you use repeat you don't need constantly

Cameron02:05:04

I supposed also useful if you have more transformations to add there, to eventually create a stream generating something else entirely

(let [ ;; This stream produces random 'pass' 'fail' scenarios  
        grade-simulator (->> (repeatedly #(rand-int 100)) ;; Stream of random numbers 
                                          (map (fn [grade] 
                                            (if (> grade 70) :pass :fail))  ;; Oop, now a stream of 'pass' / 'fail' symbols
        ;; This stream produces random messages reflecting that scenario you might print out, like 'Passed!' 
        grade-message   (map (fn [pass-or-fail] 
                               (if (= pass-or-fail :pass) "Passed!" "Failed!"))
                                   grade-simulator)]
   (take 34 grade-message)) ;; "Passed!" "Failed!" "Passed!" "Passed"... 
but let me stop ahahah

David Pham06:05:54

I really like protocols, but the community seems to advise against using defrecord. Is there a way to conciliate the two tools?

didibus06:05:52

Well, it's not that you shouldn't use defrecord or protocols. Its more that you should only use them when they are really needed

didibus06:05:20

What people try to advise against is using defrecord and protocol as a means to replicate classes

didibus06:05:30

It's too easy with them to think of your protocol as methods that operate over the records, which is basically what a class/object is.

didibus06:05:31

So what people say is, try not to use them for that. Only use protocols when you need polymorphism, that means that you have two records that both share functionality and you want the type of the record to be used to choose which implementation of the protocol to use.

didibus06:05:56

Then you can use protocols and records.

didibus07:05:33

I'd suggest for now if you want to model some entity like say a person. Just do this:

(defn make-person
  [{:keys [name age height]}]
  {:name name
   :age age
   :height height})
 
(defn legal-age?
  [person]
  (> (:age person) 21))

hindol11:05:23

Protocols are also useful when you want users of your library to plug into a feature you expose. For example, a WebSocket library may expose a Sendable protocol and you can implement that for your own types to be able to send your custom type across.

David Pham17:05:19

Thanks a lot!

David Pham17:05:36

For me it was more that protocol allowed me to define operations on some kind collection that I know will be generic. For example: how can you implement the datafy protocol without record?

hindol17:05:57

Protocols need not be mixed with records though. You can even extend Java types with protocols using proxy, reify etc.

didibus18:05:34

Datafy should be implemented by things that are not already data, that means Java objects mostly. Maps are already data, and don't need to implement datafy

didibus18:05:13

But if you just used datafy as an example. When you need type polymorphism, you can use either multi-methods or protocols.

didibus18:05:28

Maps can now implement protocols using metadata. But it is also trivial to upgrade a map to a record when you need too, since they share the map interface, it won't break anything to convert your map to a record later when needed

didibus18:05:13

Most of your generic operations over domain entities can often be generic over maps. And if they can't, you can put metadata on the maps and have multi-methods or protocols over that. Or you can have a :type key on the map itself, and a multi-method over that. The advantage of the latter is that they are then trivial to serialize. But multi-method dispatch is a bit slower, so when you need generic dispatch inside a hot loop, you can switch to a record, and like I said, upgrading a map to a record is easy, so there's an easy route from simple map to more complex records.

didibus18:05:28

Using metadata protocol dispatch:

(defprotocol Walker
  :extend-via-metadata true
  (walk [this]))

(defn make-person
  [{:keys [name age height]}]
  (letfn [(walk [person]
            (update person :distance-walked inc))]
    ^{`walk walk}
    {:name name
     :age age
     :height height
     :distance-walked 0}))

(defn make-fast-person
  [{:keys [name age height]}]
  (letfn [(walk [person]
            (update person :distance-walked #(+ 10 %)))]
    ^{`walk walk}
    {:name name
     :age age
     :height height
     :distance-walked 0}))

(walk (make-person {:name "Bob" :age 23 :height "6.4"}))
;;=> {:name "Bob", :age 23, :height "6.4", :distance-walked 1}

(walk (make-fast-person {:name "Bob" :age 23 :height "6.4"}))
;;=> {:name "Bob", :age 23, :height "6.4", :distance-walked 10}

didibus18:05:27

Using multi-method:

(defmulti walk :type)

(defmethod walk :person
  [person]
  (update person :distance-walked inc))

(defmethod walk :fast-person
  [person]
  (update person :distance-walked #(+ 10 %)))

(defn make-person
  [{:keys [name age height]}]
  {:type :person
   :name name
   :age age
   :height height
   :distance-walked 0})

(defn make-fast-person
  [{:keys [name age height]}]
  {:type :fast-person
   :name name
   :age age
   :height height
   :distance-walked 0})

(walk (make-person {:name "Bob" :age 23 :height "6.4"}))
;;=> {:type :person, :name "Bob", :age 23, :height "6.4", :distance-walked 1}

(walk (make-fast-person {:name "Bob" :age 23 :height "6.4"}))
;;=> {:type :fast-person, :name "Bob", :age 23, :height "6.4", :distance-walked 10}

didibus18:05:59

Upgrading to a record for faster dispatch:

(defprotocol Walker
  (walk [this]))

(defrecord Person [name age height]
  Walker
  (walk [person]
    (update person :distance-walked inc)))

(defrecord FastPerson [name age height]
  Walker
  (walk [person]
    (update person :distance-walked #(+ 10 %))))

(defn make-person
  [{:keys [name age height]}]
  (map->Person
   {:name name
    :age age
    :height height
    :distance-walked 0}))

(defn make-fast-person
  [{:keys [name age height]}]
  (map->FastPerson
   {:name name
    :age age
    :height height
    :distance-walked 0}))

(walk (make-person {:name "Bob" :age 23 :height "6.4"}))
;;=> #user.Person{:name "Bob", :age 23, :height "6.4", :distance-walked 1}

(walk (make-fast-person {:name "Bob" :age 23 :height "6.4"}))
;;=> #user.FastPerson{:name "Bob", :age 23, :height "6.4", :distance-walked 10}

didibus06:05:52

Well, it's not that you shouldn't use defrecord or protocols. Its more that you should only use them when they are really needed

didibus06:05:20

What people try to advise against is using defrecord and protocol as a means to replicate classes

didibus06:05:30

It's too easy with them to think of your protocol as methods that operate over the records, which is basically what a class/object is.

didibus06:05:31

So what people say is, try not to use them for that. Only use protocols when you need polymorphism, that means that you have two records that both share functionality and you want the type of the record to be used to choose which implementation of the protocol to use.

didibus07:05:33

I'd suggest for now if you want to model some entity like say a person. Just do this:

(defn make-person
  [{:keys [name age height]}]
  {:name name
   :age age
   :height height})
 
(defn legal-age?
  [person]
  (> (:age person) 21))

Amir Eldor14:05:37

Hello all, I have strange problem with records and tests (I can be considered a beginner). I have a namespace that defines a record (ags4.game.ship) and I have the test ns (ags4.game.ship-test). When I first run my tests in the test ns, stuff pass nicely. As soon as I added another test which failed, I suddenly get on subsequent test runs a:

Amir Eldor14:05:55

|| :cause "class ags4.game.ship.Ship cannot be cast to class clojure.lang.IFn (ags4.game.ship.Ship is in unnamed module of loader clojure.lang.DynamicClassLoader @54464330; clojure.lang.IFn is in unnamed module of loader 'app')"

Amir Eldor14:05:07

Does it ring any bells? Do you want more code?

Amir Eldor14:05:27

If I reload the regular ship namespace then I can run tests again without this weird error, but it gets back as soon as the new test code is run and fails.

noisesmith14:05:21

did you create a test, then delete the test and reload the file?

Amir Eldor14:05:28

As in deleted the deftest? I might have, let me restart the repl and try again

Amir Eldor14:05:42

If I delete a problematic test I might get this issue?

noisesmith14:05:04

the issue is that deleting the code from the ns doesn't delete the test

noisesmith14:05:37

if it was (deftest bad-test ...) that you needed to delete, you can fix it by running (def bad-test nil) in the same ns - that ensures the test is unregistered

noisesmith14:05:15

the error itself is saying that you had some instance of ship in parens, but it isn't a function

Amir Eldor14:05:56

ohhhhhhhhhhhh

Amir Eldor14:05:01

I know, Silly me.

Amir Eldor14:05:31

I had a (def ship ship) for debugging, probably inside the namespce that defined the (ship) creation function

noisesmith14:05:53

loosely translated "foo cannot be cast to IFn" is "why did you put a foo in parens, I dont' know how to call it?"

Amir Eldor14:05:56

thanks, and also thanks for the bad-test tip

noisesmith14:05:05

that wasn't talking about a function called ship, but rather an instance of a class called Ship

Amir Eldor15:05:00

In a test,

(:require [clojure.test :refer :all]
With ☝️ I get an "unable to resolve thrown?" at:
(is (not (thrown? java.lang.IllegalArgumentException
Ideas?

Amir Eldor15:05:17

I think it doesn't like the 'not'

noisesmith15:05:55

thrown? is not a function or macro, it's a special syntax of is

noisesmith15:05:54

all calls to is are implicitly assertions that no error is thrown - the is will fail and tell you about it if any throw reaches it

noisesmith15:05:10

thrown? is needed for the case where you need to ensure a specific throw happens

Amir Eldor15:05:09

hmmm ok, thanks again

Aleed16:05:41

Do you all use tools.deps for a monorepo? If so, how? I tried setting it up using :local/root dep field, so I could require nested modules within my repo, but I think there’s a limitation (or bug) with tools.deps that doesn’t allow local deps to be within same git repo. (ref: https://github.com/bhauman/rebel-readline/issues/176#issuecomment-421860184)

Alex Miller (Clojure team)16:05:52

that bug was fixed I believe

Aleed16:05:10

oh maybe it’s a shadow-cljs issue then? I was using it with tools.deps for front-end code

seancorfield16:05:02

@alidcastano We have a monorepo at work with about 30 subprojects. The key, for us, was having all the subprojects on the same "level" in the repo:

<repo>
  |--- build
  |       <various build scripts and utilities, configurations, etc>
  |--- clojure
  |       subproject-1
  |       subproject-2
  |       subproject-4
  +--- documentation
          <yes, we have a whole tree of documentation!>
We run clojure in one of the subprojects and each has a deps.edn with :local/root "../subproject-N" for dependencies on other subprojects.

👍 8
seancorfield16:05:32

(and we have a small wrapper script in build that automatically cd's to a subproject folder and then runs CLJ_CONFIG=../versions clojure -A:whatever ... -- where our versions subproject has a "master" deps.edn containing repo-wide aliases and pinned versions of dependencies etc, via :override-deps)

Aleed16:05:52

@seancorfield thanks, that helps. Do you keep all deps in master deps.edn, or selectively put ones that are used across modules there?

seancorfield16:05:33

Each subproject lists the deps it actually needs, but mostly as group-id/artifact-id {} since the version comes from the master deps via :override-deps { ... group-id/artifact-id {:mvn/version "1.2.3"} ...}

seancorfield16:05:12

If a dep is only used by one subproject (rare but does happen) we will put the actual dep in the subproject deps.edn file instead of in the master overrides.

Aleed16:05:12

ah interesting. so group-id/artifact-id {} is to explicitly state this dep is necessary for local module. I was wondering how to declare that, thanks

seancorfield16:05:43

We have an alias :defaults in the master deps file that brings in all the :override-deps -- and our wrapper script turns build alias subproject into cd /path/to/repo/clojure/subproject; CLJ_CONFIG=../versions clojure -A:defaults:alias

seancorfield16:05:18

Yes, each subproject still declares its own explicit dependencies, just with that empty version map.

seancorfield16:05:03

An example from our api subproject

{:deps
 {worldsingles {:local/root "../worldsingles"}
  worldsingles/datamapper {:local/root "../datamapper"}
  worldsingles/environment {:local/root "../environment"}
  worldsingles/lowlevel {:local/root "../lowlevel"}
  worldsingles/newrelic {:local/root "../newrelic"}
  worldsingles/redis {:local/root "../redis"}
  worldsingles/web {:local/root "../web"}
  worldsingles/worldsingles-web {:local/root "../worldsingles-web"}
  worldsingles/wsbilling-sdk {:local/root "../wsbilling-sdk"}
  worldsingles/presence {:local/root "../presence"}
  compojure {}
  date-clj {}}
 :paths ["src" "resources"]}

Aleed16:05:45

this is really helpful, thank you

seancorfield16:05:30

And here's the relevant alias etc from our master deps file:

;; "pinned" versions for all cross-project dependencies
  :defaults
  {:override-deps
   {bidi {:mvn/version "2.1.6"}
    camel-snake-kebab {:mvn/version "0.4.1"}
    cfml-interop {:mvn/version "0.2.9"}
    cheshire {:mvn/version "5.8.1"}
    ...
    compojure {:mvn/version "1.6.1"}
    date-clj {:mvn/version "1.0.1"}
    hiccup {:mvn/version "1.0.5"}
    ...}

👍 8
Kamuela16:05:39

With CLJS, how can I map over the keys of an #object?

David Pham17:05:03

Can you transform the object into a Clojure hash map?

practicalli-johnny17:05:37

https://github.com/mfikes/cljs-bean is very efficient for converting object to Clojure

Kamuela11:05:14

Thank you!

Spaceman17:05:19

How do I unroll a map as parameters? Suppose I have the following: (let [foo {:bar1 1 :bar2 2}] (assoc {} ...) ) What can I replace the ... with so that I get (assoc {} :bar1 1 :bar2 2)?

bfabry17:05:35

apply will unroll the last argument for you, so you just need to come up with a list like [:bar1 1 :bar2 2]

noisesmith17:05:54

apply doesn't work like that with maps though does it?

bfabry17:05:59

so you can do (apply assoc {} (flatten foo))

Alex Miller (Clojure team)17:05:08

god don't do that

12
😁 4
seancorfield17:05:19

I'm trying to encourage @U010Z4Y1J4Q to ask these questions in #beginners

noisesmith17:05:30

scratch=> (apply list {:a 0 :b 1})
([:a 0] [:b 1])

ghadi17:05:53

merge and be done with it

seancorfield17:05:32

See what a great range of helpful answers you get here @U010Z4Y1J4Q? 🙂

noisesmith17:05:43

if you need alternating keys and vals, this would be my preferred way to do it

=> (into [] cat {:a 0 :b 1})
[:a 0 :b 1]

bfabry17:05:47

not saying it's a good idea, it's not, just addressing the specific "unroll" part of the question first. merge or bunch of other ways are much better ways of building up a map

noisesmith17:05:52

but yeah, in this case, use merge

seancorfield17:05:09

Are you trying to do this in a macro, BTW?

seancorfield17:05:30

Which part of the above is coming in as macro arguments, and which part is the body of the defmacro itself? The answers above are good for the specific case you asked above but that wasn't part of a macro, so I wanted to see more context to what you're actually trying to do.

Spaceman17:05:03

Here's what I want:

(re-frame.core/reg-event-db
    :name 
    (fn [db [foo bar]]
        ;; some body
        (assoc db :foo foo :bar bar)))
Here's the macro that seems to work:
(defmacro db-event [name params result & body
  `(re-frame.core/reg-event-db
     ~name
     (fn [db# ~params]
       ~@body
       (apply (assoc db# ~result)))))

Spaceman17:05:44

so I use it like: (db-event :foo [foo bar] [:foo foo :bar bar] ..some body...)

noisesmith17:05:28

FYI trailing close parens like that just makes the code harder to read and wastes vertical space

👍 4
Spaceman17:05:29

You guys can probably help to make it more elegant

seancorfield17:05:45

That (apply (assoc db# ~result)) isn't going to work. Perhaps you mean (apply assoc db# ~result) ?

noisesmith17:05:06

yeah, in this case you can probably use merge, but note that by using result as the second arg, it will replace anything from the db# arg that had the same key

Spaceman17:05:28

@U051SS2EU that's the intent if you observe what I want

noisesmith17:05:04

as long as result is a hash map, mrege will do what you want

seancorfield17:05:43

Just to note: in your call example you are passing [:foo foo :bar bar] as the result which is not a hash map so merge won't work in that case but (apply assoc db# ~result) will work.

seancorfield17:05:30

if you pass a hash map instead, @U051SS2EU’s suggestion of using (merge db# ~result) would work.

Spaceman17:05:16

I'm not done yet. Ultimately what I want is this:

(re-frame.core/reg-event-db
    :event-name 
    (fn [db [foo bar]]
        ;; some body
        (assoc db :foo foo :bar bar)))

(defn event-name [foo bar] (dispatch [:event-name])
And so far I have this:
(defmacro db-event [event-key params result & body]
  `(re-frame.core/reg-event-db ~event-key
     (fn [db# ~params]
       ~@body
       (merge db# ~result)))
  (defn (name event-key) params (dispatch [event-key])))
to be used like (db-event [foo bar] {:foo foo :bar bar})

Spaceman17:05:28

but the defn part doesn't work

bfabry17:05:06

seems like you didn't quote the defn form

Spaceman17:05:45

okay even then:

Syntax error macroexpanding clojure.core/defn at (*cider-repl luminus/vendo:localhost:49281(clj)*:2968:3).
-- Spec failed --------------------

  ((symbol event-key) ... ...)
   ^^^^^^^^^^^^^^^^^^

should satisfy

  simple-symbol?

-- Relevant specs -------

:clojure.core.specs.alpha/defn-args:
  (clojure.spec.alpha/cat
   :name
   clojure.core/simple-symbol?
   :docstring
   (clojure.spec.alpha/? clojure.core/string?)
   :meta
   (clojure.spec.alpha/? clojure.core/map?)
   :bs
   (clojure.spec.alpha/alt
    :arity-1
    :clojure.core.specs.alpha/args+body
    :arity-n
    (clojure.spec.alpha/cat
     :bodies
     (clojure.spec.alpha/+
      (clojure.spec.alpha/spec :clojure.core.specs.alpha/args+body))
     :attr
     (clojure.spec.alpha/? clojure.core/map?))))

-------------------------
Detected 1 error
and also I tried both (name ....) and (symbol ...)

bfabry17:05:04

what does your macro look like now?

Spaceman17:05:51

(defmacro db-event [event-key params result & body] `(do ~event-key (fn [db# ~params] ~@body (merge db# ~result))) `(defn (name event-key) params (dispatch [event-key])) )

noisesmith17:05:30

(name event-key) without escaping is not a valid defn

bfabry17:05:39

your reg-event-db seems to have disappeared. anyway the things inside the now-quote defn form need to be unquoted where appropriate

noisesmith17:05:46

you probably want ~(symbol event-key)

Spaceman17:05:46

@U050MP39D I was using do to test the macro expansions because the re-frame library is cljs and I macroexpand doesn't work in the cljs repl.

bfabry17:05:53

macros also don't include an implicit do block, so if you want the thing they return to do two things, you need to wrap the whole result in a do block

user=> (defmacro foo []
'foo
'foo)
user=> (macroexpand-1 '(foo))
foo

bfabry17:05:55

yeah that ^ is because the macro is only returning the last of your forms. you need to wrap the two forms in a do form if you want to return something that does both

bfabry17:05:31

user=> (defmacro foo2 []
`(do ~'foo ~'foo))
#'user/foo2
user=> (macroexpand-1 '(foo2))
(do foo foo)

noisesmith17:05:02

@U010Z4Y1J4Q maybe macroexpand is not available or inconvenient in cljs, but macros don't need to have valid symbols if you are just expanding - you can literally load your macro definition then expand it in regular clj

noisesmith17:05:49

in fact sometimes I just start with a function that creates a list of symbols - eventually my macro can call that function

Spaceman17:05:52

@U051SS2EU that doesn't work though

noisesmith17:05:28

what doesn't work? making a macro with symbols that don't exist is fine, as long as you only expand it and don't try to use it

Spaceman17:05:11

yeah that's right

noisesmith17:05:01

(ins)scratch=> (defmacro speculative [x y & body] `(do-reagent-thing ~@body))
#'scratch/speculative
(ins)scratch=> (macroexpand '(speculative 1 2 3 4 5))
(user/do-reagent-thing 3 4 5)

noisesmith17:05:25

of course there's no such thing as "do-reagent-thing" but for debugging a macro that doesn't matter if I know the syntax

bfabry17:05:48

2 repls might be helpful. iterate on the definition using macroexpand-1 in a clojure repl, copy+paste the results into a cljs repl once they look right

bfabry17:05:23

but imo work exclusively with macroexpand-1 until the output at least looks right

4
Spaceman17:05:39

Actually I'm not done yet. What I really want is this:

(re-frame.core/reg-event-db
    :name 
    (fn [db [_ foo bar]] ;; notice that _
        ;; some body
        (assoc db :foo foo :bar bar)))
I tried this:
(defmacro db-event [event-key params result & body]
  `(do
    ~`(re-frame.core/reg-event-db ~event-key
                                  (fn [db# (into [_#] ~params)]
           ~@body
           (merge db# ~result)))
    ~`(defn ~(symbol event-key) ~params (dispatch [~event-key]))
    )
  )
which expands to
(do
 (re-frame.core/reg-event-db
  :foo
  (clojure.core/fn
   [db__50277__auto__ (clojure.core/into [___50278__auto__] [foo bar])]
   (clojure.core/merge db__50277__auto__ {:foo foo, :bar bar})))
 (clojure.core/defn foo [foo bar] (vendo.product/dispatch [:foo])))
I don't know if this is correct or not

seancorfield17:05:46

Since you have the syntax quote around the do form, you can remove the tilde/syntax quote from the forms inside it.

bfabry17:05:42

output of macroexpand-1 is usually easier to read than the output of macroexpand

seancorfield17:05:58

(defmacro db-event [event-key params result & body]
  `(do
      (re-frame.core/reg-event-db ~event-key
          (fn [db# (into [_#] ~params)]
            ~@body
            (merge db# ~result)))
      (defn ~(symbol event-key) ~params (dispatch [~event-key]))))

Spaceman17:05:00

@U050MP39D in this case both were the same

bfabry17:05:47

oh right you are, I was thinking of something else

bfabry17:05:56

(this is the difference)

user=> (macroexpand '(foo3))
(def a (clojure.core/fn ([] nil)))
user=> (macroexpand-1 '(foo3))
(clojure.core/defn a [] nil)

seancorfield17:05:47

Looking at your expanded code, I think that instead of (into [_#] ~params) you want ~(into [_] params) (possibly with '_ instead of _)

seancorfield18:05:04

Your goal is (fn [db_123_auto [_ foo bar]] ...) there, right?

noisesmith18:05:06

oh - is the attempt here to create a param list in the function args?

noisesmith18:05:03

I think [_# ~@params] would be the readable way to do that

Spaceman18:05:05

@U051SS2EU what did you think?

seancorfield18:05:11

(I'm not familiar with the signature of the function that reg-event-db requires in re-frame)

seancorfield18:05:43

Ah, good suggestion there @U051SS2EU

Patrick Truong17:05:04

Hello all, I’m new to Clojure/ClojureScript and trying to render a list in Reagent. All the examples I’ve seen use (for [item items]) syntax. I was wondering if there is an idiomatic way to extract indices in a for loop since I want to conditionally render a CSS class after the first item. Thanks for all the help 🙂

noisesmith17:05:18

as an aside - for is a comprehension that makes a list out of a list, it isn't a loop per se

👍 4
Patrick Truong17:05:56

@U051SS2EU thanks for the clarification!

Alex Miller (Clojure team)17:05:13

you might want to look at map-indexed

👍 4
hindol17:05:31

for and map-indexed together worked really well for me in the past.

(for [[i v] (map-indexed vector [:0 :1 :2 :3])]
  [i v])

noisesmith17:05:23

this is identical to (map-indexed vector ...)

noisesmith17:05:55

but if you needed nested clauses, this form can help

hindol17:05:12

Yeah, that is for the example's sake, 🙂

noisesmith17:05:27

and I think in reagent code especially, for is idiomatic so having it there can help communicate intent

noisesmith17:05:41

so I don't need to read closely looking for forms that might result in lists inside the dom

hindol17:05:43

for is useful if OP wants a condition on i , like keep only the odd-indexed elements. I love the flexibility of for.

Jim Newton18:05:00

what is the correct way inside a function to assert that a given argument is of a certain type? I've changed the sematics of one of my functions and I want to run the tests assuring that the write values are being passed to certain functions. I think I've seen this done using meta data on the function declaration, but I didn't understand it exactly.

Babis Zinoviadis18:05:55

Would this work? Is there a more idiomatic way of writing this?

andy.fingerhut18:05:56

There are multiple ways. You could simply insert a call like (assert (map? arg1)) into the body of such a function.

andy.fingerhut18:05:50

You could use preconditions. Look for :pre on this page for some documentation on that approach: https://clojure.org/reference/special_forms#_fn_name_param_condition_map_expr_2

👍 4
Jim Newton18:05:57

Yes, but I've seen this done something like (defn [f^String y^Long] ....) .... That's not right, but it's vaguely what I remember.

andy.fingerhut18:05:59

You could use spec and instrument the function during testing.

andy.fingerhut18:05:37

That is a type hint, and does not check that the value is of that type. It gives a hint to the compiler that if there is a Java interop call involving one or more of those values, when the Clojure compiler is looking up what Java method to use, it can assume that those values are of those types, to narrow down the possibilities. Without that narrowing down, sometimes multiple Java methods are contenders, and run-time searching for the correct method is what the Clojure compiler produces (i.e. run-time reflection), which is hideously slow compared to not using run-time. reflection

✔️ 4
noisesmith18:05:32

there's also the potentially confusingly named cast, which takes an object and a class, and throws an error if the object isn't an instance of that class

noisesmith18:05:40

(ins)scratch=> (cast java.util.Collection [1 2 3])
[1 2 3]
(ins)scratch=> (cast java.util.Map {:a 0 :b 1})
{:a 0, :b 1}
(ins)scratch=> (cast java.util.Map [])
Execution error (ClassCastException) at java.lang.Class/cast (Class.java:3605).
Cannot cast clojure.lang.PersistentVector to java.util.Map

noisesmith18:05:11

it returns the object itself if the cast succeeds

ssushant20:05:35

what are some stand-out usecases of multimethods?

noisesmith20:05:53

they work quite well for pr (used for printing data in a way that's usually readable) - there's just one thing to implement, and it makes sense to extend it to things outside clojure core (new libraries or java builtins)

phronmophobic20:05:10

i’ve seen them used a couple times for event processing when your events are represented with maps: eg.

(def my-event {:type :foo :a 10})
(defmulti process-event :type)

ssushant20:05:27

That’s an interesting use case, any examples from open source projects?

phronmophobic20:05:55

not off the top of my head ¯\(ツ)

phronmophobic20:05:09

basically, in other languages, you create a bunch of new types and use the normal OO single dispatch. in clojure, it’s possible to use plain ol’ data and dispatch based on the data itself

Matthew22:05:07

@ssushant multimethods are good for any type of polymorphism. For example, write a compiler or interpreter in Java and your best shot at walking the AST is with the visitor pattern. In CLJ you can just dispatch with something like eval for every node type. It’s just eval A, eval B, eval C, all the way down. More commonly I use multimethods for extensible switch statement like logic. Think something like a turn based game where at every turn the outcome is dependent on a few different aspects of state. Instead of a giant switch statement with cases and nested if statements you can just dispatch based on state and extend the multimethod indefinitely without ever needing to touch the older code. Pretty sure multimethods are also runtime extensible but I’ve never tried it needed it.

Charles H22:05:12

so is emacs still the best way to get your cloj on? Anyone satisfied with vim plugins like vim-fireplace? Just wondering if I should really put in the time to get re-acquainted with Emacs.

dpsutton22:05:14

3/5 coworkers are vimmers at my work. emacs is great but an investment and not necessary

noisesmith22:05:45

vim fireplace works great, I switched from emacs+cider

noisesmith22:05:25

for a long time I just used a standalone repl, I've experimented lately with fireplace (plus a neovim "terminal" buffer with a real repl client in it)

Charles H22:05:59

ok cool I was enjoying fireplace for koans last night so I guess I'll stick with that. Thanks!

noisesmith22:05:22

I also recommend vim-clojure-static and vim-sexp

noisesmith22:05:50

but really, vim is already so good at structure based editing you don't need as much extensions for lisp as you do in emacs imho

Charles H22:05:26

but can you slurp and barf? 😆

noisesmith22:05:46

you get objects for ( and [ and { that all work as expected, plus " and all work with c/y/d and a/i

dpsutton22:05:11

> (plus a neovim "terminal" buffer with a real repl client in it) is this libterm? (libvterm?)

noisesmith22:05:25

it's a built in neovim buffer type

noisesmith22:05:59

oh look at that, yeah it uses libvterm

dpsutton22:05:24

speedy? i was using an emacs module built on that but it couldn't keep up with output of sql loggings

noisesmith22:05:41

@charleshu yeah vim-clojure-sexp introduces slurp / barf / convolute

😎 4
noisesmith22:05:33

@dpsutton I have only noticed it lagging once, and unlike in emacs control-c has never taken longer than 20 seconds at worst :P

dpsutton22:05:45

i feel attacked 🙂

dpsutton22:05:50

i am jealous of that

noisesmith22:05:53

but I haven't tried connected to a true firehose haha

noisesmith22:05:17

whether syntax highlighting is parsing each line makes a big difference

noisesmith22:05:00

@dpsutton on the other hand I miss tramp and emacsclient since switching

noisesmith22:05:26

being able to connect a new view to an existing editor process is awesome and I miss it

dpsutton22:05:09

i'm not a remote machine user really and the few times i have tramp was buggy on bad remote machine prompts it couldn't parse or gets terrible if you disconnect an ssh tunnel to another machine while there are buffers open in emacs

noisesmith22:05:33

sure, everything awesome about emacs has sharp edges at the corners