Fork me on GitHub
#clojure
<
2018-06-22
>
msolli09:06:41

I want to turn a map like this… {:foo ["a" "b"], :bar ["x" "y"]} … into a vector like this… [{:foo "a", :bar "x"} {:foo "b", :bar "y"}] The size of the vectors in the map’s values is the same for all elements. Any ideas for how to solve this idiomatically?

alexyakushev09:06:33

@msolli :

(let [data {:foo ["a" "b"], :bar ["x" "y"]}
      ks (keys data)]
  (->> (vals data)
       (apply map vector) ;; Transpose the matrix of values
       (mapv (partial zipmap ks))))

alexyakushev09:06:25

The above will not work, however, if keys and vals may return elements in different order

msolli09:06:03

Hm, thanks, seems like they do for a regular hash-map. Docstring for both keys and vals state that they return things “… in the same order as (seq map)“.

alexyakushev09:06:58

Yeah, missed that part.

mpenet09:06:34

ordering is the same

mpenet09:06:45

yeah, the docstring changed a while back

dominicm11:06:13

What's the history of the naming of vec and vector? Are there any regrets?

dominicm11:06:41

It appears Clojure zip went for a make- prefix.

Alex Miller (Clojure team)12:06:29

Well I wasn’t there but all data structures use their name as the constructor function - vector, hash-map, hash-set, list, array-map, etc

Alex Miller (Clojure team)12:06:00

I think of vec and set as coercers

Alex Miller (Clojure team)12:06:36

We typically use literal syntax instead of constructors whereas the coercing is more common so it’s convenient to have those be shorter

Alex Miller (Clojure team)12:06:57

But that’s all just me post-rationalizing

dominicm14:06:16

In Clojure zip that convention wasn't followed, if I am interpreting correctly. We were trying to name some functions for coercing and creating Java periods. A shortened version is hard to contrive.

Alex Miller (Clojure team)17:06:23

zip-map is map as in map (the operation), not as in hash-map (the collection) - that is, it’s seen as a sequence function more than as a constructor (but I totally get why people say that too)

dominicm11:06:41

It appears Clojure zip went for a make- prefix.

Alex Miller (Clojure team)12:06:30

zip almost certainly is echoing that use in other functional langs

tord12:06:40

I need to read and write TOML files. I see that there are already several Clojure libraries for this. Is there any reason to prefer one of them over the others, or are they all pretty much interchangeable?

jumar13:06:35

I've never used that but quickly looking at options only the last one seems to support writing: * https://github.com/lantiga/clj-toml/blob/0.4.0-instaparse/src/clj_toml/core.clj * https://github.com/manicolosi/clojoml * https://github.com/ilevd/toml That's also the one most recently updated.

jmckitrick14:06:35

I’m having an odd issue with stest/instrument and :stub in the context of deftest.

jmckitrick14:06:14

On the line where I call stest/instrument in a test I get this error: Caused by: java.lang.ClassCastException: clojure.lang.AFunction$1 cannot be cast to clojure.lang.MultiFn

jmckitrick14:06:45

Yet executing that very line in the REPL correctly instruments the function in question, along with the stubbing behavior.

jmckitrick14:06:58

The stacktrace is deep within spec -> test.check, and I cannot figure out the reason for the difference in behavior.

jmckitrick14:06:23

Maybe instrument is meant for use in test.check but not clojure.test ?

Alex Miller (Clojure team)15:06:11

If you’re running in lein, you may be running into lein’s monkey patching of clojure.test

Alex Miller (Clojure team)15:06:24

if so, you can try :monkeypatch-clojure-test false in project.clj (and most likely lose nothing you care about)

jmckitrick16:06:17

That’s good to know!

roklenarcic14:06:02

Hm here's a weird problem. If I turn on reflection warnings, and I evaluate ring-core file src/ring/middleware/session/cookie.clj I get two reflection warnings:

Reflection warning, crypto/random.clj:28:12 - call to static method encodeHex on org.apache.commons.codec.binary.Hex can't be resolved (argument types: unknown).
Reflection warning, crypto/random.clj:28:3 - call to java.lang.String ctor can't be resolved.
But then if I check out crypto-random project and evaluate that random.clj file, I get no reflection warnings.

roklenarcic14:06:43

Figured it out. cryto-random uses ancient commons-codec 1.6, and ring-core uses newer commons-codec 1.11, which has more overloads of encodeHex, which causes the old code to produce reflection warnings.

roklenarcic14:06:59

Seems like lib authors should annotate java interop with types even when not needed, because it can cause grief later.

roklenarcic15:06:50

@weavejester submitted PR in crypto-random.

roti15:06:40

does anyone have a recommendation for a cloud service for a toy clojure app?

roti15:06:58

(must have a free plan as well)

roti15:06:09

I tried google app engine a few years ago and it was a pain

hackeryarn16:06:35

I second heroku. Very easy deploy.

athomasoriginal16:06:01

Hey all, I am looking for advice on spec generation: I have a data structure like this

{:item_count 53  :items [ {} {} ...53 times]}
My goals is to write a spec to help generate this data structure, but I need to have item_count to be the total number of items in provided by the :items property...is this possible?

Alex Miller (Clojure team)17:06:46

Totally, you can s/and any arbitrary predicate

Alex Miller (Clojure team)17:06:51

(s/def ::item_count int?)
(s/def ::item map?) ;; whatever
(s/def ::items (s/coll-of ::item))
(s/def ::item-map
  (s/and (s/keys :req-un [::item_count ::items])
	       #(= (:item_count %) (count (:items %)))))
(s/valid? ::item-map {:item_count 2 :items [{} {}]}) ;; => true

Alex Miller (Clojure team)17:06:24

sorry, I didn’t read all of that above

Alex Miller (Clojure team)17:06:35

can also gen, using fmap

Alex Miller (Clojure team)17:06:05

(s/def ::item-map
  (s/with-gen
    (s/and 
		  (s/keys :req-un [::item_count ::items])
	    #(= (:item_count %) (count (:items %))))
    #(gen/fmap
	    (fn [items] (hash-map :item_count (count items) :items items))
	  	(s/gen ::items))))

Alex Miller (Clojure team)17:06:34

that is gen the items list, then use fmap to produce the right shaped map

jmckitrick17:06:27

Heroku is great, @roti

nickmbailey17:06:21

+1 heroku is pretty easy to get going and the free tier can take you pretty far

justinlee20:06:56

somewhere there was a discussion about where to put specs, and one of the suggestions was to stick them in their own namespace, so that the specs for program.widget would be in program.widget.specs. I’m trying to figure out how namespaced keywords would work in practice, but it seems to me that you need the specs to be in the same namespace or they won’t match up. Or am I missing something? Or maybe the idea is to put them in the same namespace but a different file?

noisesmith20:06:44

the keyword used needs to match between definition and usage

noisesmith20:06:53

the namespaces can be anything you find meaningful

rplevy20:06:59

@nickmbailey Not that it really matters but one of the reasons I appreciate Heroku is that they went out of their way to accommodate deploying Clojure fairly early on in its history

rplevy20:06:44

I think it was because they're users of Clojure and have always had Clojure advocates on the inside

rplevy20:06:57

but Datomic Ions might be the new "easiest sensible way to deploy a Clojure app"

justinlee20:06:42

@noisesmith What I mean is this kind of thing:

~$ clj
Clojure 1.9.0
user=> (in-ns 'spectest.spec)
#object[clojure.lang.Namespace 0x33308786 "spectest.spec"]
spectest.spec=> (clojure.core/require '[clojure.spec.alpha :as s])
nil
spectest.spec=> (s/def ::first-name clojure.core/string?)
:spectest.spec/first-name
spectest.spec=> (s/def ::person (s/keys :opt [::first-name]))
:spectest.spec/person
spectest.spec=> (in-ns 'spectest)
#object[clojure.lang.Namespace 0x5911e990 "spectest"]
spectest=> (clojure.core/require '[clojure.spec.alpha :as s])
nil
spectest=> (s/conform :spectest.spec/person {::first-name 42})
#:spectest{:first-name 42}
The spec didn’t really work because the first-name key is defined in one ns but used in another. This isn’t a problem with :req-un because it only matched up the name of the keyword

noisesmith20:06:11

that's just using aliases wrong

hiredman20:06:29

don't use '::'

Alex Miller (Clojure team)20:06:44

:: means “autoresolve in the context of the current namespace”

Alex Miller (Clojure team)20:06:57

you’re using it in two different namespaces and expecting the same result

hiredman20:06:03

if you explicitly state the namespace, then you won't have any problems when it resolves to something you don't expect

justinlee20:06:16

right, so I would need to construct the map using the spectest.spec namespace

hiredman20:06:43

or just namespace the keyword

justinlee20:06:48

(sorry it’s confusing that i called it spectest. should be widget vs. widget.spec or something)

noisesmith20:06:54

which, like I said, the keyword you use to define the spec and the one you use to check it need to match

hiredman20:06:10

:spectest.spec/first-name

justinlee20:06:12

right got it. the reason i was confused is because I could have sworn that someone was suggesting that by putting the specs in their own namespace, you could decide whether to load them or not. but that’s not really going to be possible if you want to use namespaced keywords

hiredman20:06:37

keyword namespaces are not the same thing as code namespaces

noisesmith20:06:49

namespaced keywords do not belong to namespaces

hiredman20:06:02

the only thing that links them is ::, which, see my earlier suggestion not to use it

justinlee20:06:52

yea okay. thanks that makes sense

hiredman20:06:06

:foo/bar can exist just fine without the namespace foo needing to exist

justinlee20:06:11

I think the answer is that as a matter of taste I would want the keywords to be :foo/bar rather than :foo.spec/bar. so I should probably just change the spec and not use :: there as you suggested (?)

hiredman20:06:35

that would be my suggestion

👍 4
imetallica21:06:28

Hey, I’m trying the following:

(POST "/:partner-id/antifraud-rules" []
       :path-params [partner-id :- ::antifraud/partner_id]
       :body-params [action :- ::antifraud/action
                     criteria :- ::antifraud/criteria]
       :summary "Creates a new rule for given partner."
       (try
        ;  (println body)
         (created "" {:rule (antifraud/add-rule-to-partner partner-id action {})})
         (catch PSQLException e
           (unprocessable-entity {:errors ["Invalid fields."]}))))
And my specs are:
(s/def ::action #{"allow" "deny"})
(s/def ::criteria (s/keys :opt-un [::email ::phonenumber ::document]))
And my error is:
{
  "spec": "(spec-tools.core/spec {:spec (clojure.spec.alpha/keys :req-un [:$spec44286/action :$spec44286/criteria]), :type :map, :keys #{:criteria :action}, :keys/req #{:criteria :action}})",
  "problems": [
    {
      "path": [],
      "pred": "map?",
      "val": null,
      "via": [],
      "in": []
    }
  ],
  "type": "compojure.api.exception/request-validation",
  "coercion": "spec",
  "value": null,
  "in": [
    "request",
    "body-params"
  ]
}
What I’m doing wrong? 😞

noisesmith21:06:07

looks like there should be a map containing :criteria and :action and instead you got nil?

imetallica21:06:12

The point is: I’m sending a JSON body with the criteria and action as keys

noisesmith21:06:04

what are you doing to allow the handler to use json?

imetallica21:06:01

I’m using the example from luminus:

(def antifraud-rules-routes
  (api
   {:swagger {:ui "/docs/external-antifraud-rules"
              :spec "/antifraud-rules.json"
              :data {:info {:version "1.0.0"
                            :title "Antifraud Rules Service"
                            :description "Create, update and remove rules from antifraud service."}}}}

   (context "/v1/partners" []
     :tags ["Configuration of antifraud."]
     :coercion :spec

     (POST "/:partner-id/antifraud-rules" []
       :path-params [partner-id :- ::antifraud/partner_id]
       :body-params [action :- ::antifraud/action
                     criteria :- ::antifraud/criteria]
       :summary "Creates a new rule for given partner."
       (try
        ;  (println body)
         (created "" {:rule (antifraud/add-rule-to-partner partner-id action {})})
         (catch PSQLException e
           (unprocessable-entity {:errors ["Invalid fields."]})))))))

noisesmith21:06:12

usually the way you get json input is via a ring middleware, wrap-json-body iirc

noisesmith21:06:39

if that middleware isn't being applied, that would be a simple reason that it wouldn't get any of your input

imetallica21:06:00

Oh I see now the problem

imetallica21:06:08

I’m missing the json middleware it seems

noisesmith21:06:22

it probably isn't being added by default because you might not want it applied to everything indescriminately? anyway, it's easy to double check by applying it