Fork me on GitHub
#beginners
<
2018-03-29
>
hawari.rahman1704:03:31

Is there a way to "arrange" the order of a key in a hash-map? I'm building a service which returns JSON, and I want to have control on which field gets displayed first in the JSON (e.g. I don't want the "id" and "type" field to be hidden somewhere in the middle of a dense object).

hawari.rahman1704:03:47

Will creating a type/record do the trick?

noonian04:03:35

The order of the key in JSON will likely depend on which library you are using to encode it from Clojure. You might get away with using a sorted-map

hawari.rahman1704:03:50

I'm using ring-json, which uses cheshire

noonian04:03:40

You might be able to get away with something like this:

(into (array-map :id 17 :type "some-type")
      {:an "unordered" :map "to dump in"})

hawari.rahman1706:03:38

Well, I guess I'll stick to the randomness then. I'm not going to convolute my function with that. I appreciate your inputs though @noonian

schmee09:03:03

@hawari.rahman17 if you use Jsonista (or something else that allows access to the underlying Jackson stuff), you can use Jackson to do this:

(ns foo
  (:require [jsonista.core :as json]
  (:import [com.fasterxml.jackson.core JsonGenerator]
           [com.fasterxml.jackson.databind SerializationFeature]

(def object-mapper
  (doto
    (json/object-mapper)
    (.configure SerializationFeature/ORDER_MAP_ENTRIES_BY_KEYS true)

(defn encode [event]
  (json/write-value-as-string event object-mapper))

schmee09:03:26

although, I just realized that you want them sorted in a custom order, not alphabetically... my bad opieop

hawari.rahman1710:03:26

No problem. Thanks anyway for taking notice of this, gave me another perspective on what can be done.

rdanielo11:03:55

CLJS related question

rdanielo11:03:07

Can anyone tell me why this fails trying to call on null

rdanielo11:03:09

(defn Get [url fun] (req/get url #((fun %2))))

max.forasteiro11:03:11

try

(defn Get [url fun] (req/get url #(fun %2)))
(i've remove one parenthesis from the anonymous function call)

rdanielo11:03:27

That worked, stupid me and stupid paren...

rdanielo11:03:13

while this just works fine ?

rdanielo11:03:26

(defn Get [url fun] (req/get url fun))

max.forasteiro11:03:05

Hi guys! How can I update a value inside a atom vector of maps? For example:

(def var (atom [{:a 1 :b 2} {:a 2 :b 2}]
and now I want to update the {:a 2 :b 2} to {:a 2 :b 3} Is there any easy way to do this? My first thought was to remove the element from the vector and then add it again

pcbalodi11:03:24

u can use update-in or assoc-in

max.forasteiro11:03:53

but how can i select the array element that I want to update?

pcbalodi11:03:40

u can use the index of the element of vector u want to update, like (assoc-in [{:a 1 :b 2}] [0 :b] 3) => [{:a 1 :b 3}]

bjr13:03:38

vectors are associative data structures of index->value. it’s reallly useful to remember that. it’s often why I stick to vectors instead of lazy seqs.

magra11:03:47

Is there a way to destructure varargs on type? This works but seems unreadable:

(defn myfun
 [x y & args]
 (let [tooltip (some #(when (string? %) %)
                     args)
       mykey   (or (some #(when (keyword? %) %)
                         args)
                   :default)]
   (do- stuff)))
What would be an idiomatic way to do this?

mfikes11:03:54

@magra Hrm. If that last argument were instead a map, you could use map destructuring to pull out what you want. It seems tricker to algorithmically cope with a list of typed args...

mfikes11:03:47

I guess you could have a string or keyword anywhere in that list.

magra11:03:25

@mfikes Maybe I will just write a function like string? that returns the string instead of boolean.

magra11:03:42

like get-first-string.

magra11:03:01

It would not be destructuring but readable.

mfikes11:03:44

Yeah, you could then use juxt to destructure as in

(let [[tooltip mykey] ((juxt first-string first-keyword) args)]
   ...

mfikes12:03:37

But, to be honest, I would just let tooltip and mykey

magra12:03:09

(get-first seq string? :default)

magra12:03:26

Thanks!!!

mfikes12:03:18

Yeah, that looks better

mfikes12:03:28

Make a reusable little fn

samcgardner13:03:17

Why does the first example here work and the second one not?

(defn sitemap
  [url]
  (def host (get (liburl/url url) :host))
  (def anchors (-> url
                   client/get :body
                   hickory/parse hickory/as-hickory
                   ((partial select/select (select/tag :a)))))
  (map :attrs anchors))
(defn sitemap
  [url]
  (def host (get (liburl/url url) :host))
  (def anchors (-> url
                   client/get :body
                   hickory/parse hickory/as-hickory
                   ((partial select/select (select/tag :a)))
                   (map :attrs)))
  anchors)

alexmiller13:03:27

Generally, you should never use def inside a defn - those create global vars with scope beyond the function

alexmiller13:03:30

use let instead

samcgardner13:03:44

oops, that's good to know. Thanks!

alexmiller13:03:11

on the second one not working, you’re using -> which is thread-first

alexmiller13:03:34

so it will effectively do (map anchors :attrs)

alexmiller13:03:05

in this case if all the other functions are single arg, you can just switch to ->>

samcgardner13:03:38

Supposing they weren't, how do I get around this? define a lambda that calls map with the args reversed or a partial function using map?

alexmiller14:03:01

I probably just wouldn’t use a thread macro to combine those two sets of calls

bjr14:03:10

you could use a thread-last at the end of the thread-first (-> url client/get ... (->> (map :attrs)))

alexmiller14:03:32

I find mixing them is generally harder to read than not using them at all

bjr14:03:34

though it’s not the most elegant approach

alexmiller14:03:42

and as-> can be used in some cases

samcgardner14:03:54

I think this function might be guilty of doing too much

alexmiller14:03:32

-> works best on a series of functions that take and return collections (maps, vectors, etc) - functions like conj, merge, assoc, etc

alexmiller14:03:58

->> works best on a series of functions that take and return sequences - functions like filter, map, reduce, etc

alexmiller14:03:13

b/c all collection functions take the collection first and all sequence functions take the sequence last

alexmiller14:03:27

I think the scope of your function is fine

samcgardner14:03:24

just use let bindings to structure things rather than trying to force it all into a thread-first macro?

alexmiller14:03:13

I find the ((partial select/select (select/tag :a))) hard to read too and maybe not worth including in that threaded expr

samcgardner14:03:52

Yeah I think pulling it apart some would help. Thanks for your time and expertise

ernie.deferia58415:03:58

can a defmethod be dispatched based on matching one of N values? i have a mutimethod that dispatches on a map key that represents a frequency of an event (:daily, :weekly, :monthly, :yearly [or :annual, :annually]). it is the "yearly or annually or annual" set of values that I would like to handle in a single defmethod handle.

alexmiller16:03:03

you can’t define it directly in this way, but you can define multiple defmethods that invoke the same function - some people have macro’ed that up to avoid the duplication

alexmiller16:03:16

or you can leverage multimethods ability to create arbitrary keyword hierarchies

alexmiller16:03:51

then you’d have a “parent” keyword and just define one method on that

ernie.deferia58416:03:15

ah, ok, had not consider that. thank you.

bjr16:03:44

I like implementing a defmethods macro — but it’s simpler to leave the defmethod dispatch duplication while handling the logic duplication with a separate function

ernie.deferia58416:03:41

@bjr @alexmiller thank you both for the feedback. i had considered moving the logic duplication to a function defined outside the multimethod hierarchy, but was seeking for a way to avoid that. will try the keyword hierarchy after grokking it. thanks again.

guilhermelionribeiro18:03:57

are the clojure koans a good way to start?

seancorfield18:03:05

@guilhermelionribeiro A lot of people like them as a starting point. Also check out Clojure for the Brave and True.

guilhermelionribeiro18:03:44

@seancorfield will do, thanks!!

ahmadnazir20:03:35

Hi guys, why do the following return the same result:

(clojure.string/split "a_b" #"_")
(clojure.string/split "a_b_" #"_")

ahmadnazir20:03:49

In the second case, I expect ["a" "b" ""] instead of ["a" "b"]

sundarj20:03:39

@ahmadnazir by default trailing empty strings are discarded

ahmadnazir20:03:13

Thanks! I didn't find that obvious from the documentation but now that you explain it, it makes sense.

sundarj20:03:35

yeah, unfortunately the docstring doesn't mention it

sundarj20:03:47

it uses the Java string split internally, so i looked at the Java docs

sundarj20:03:32

i'll ask in #clojure-dev to see if the docstring can be updated

alexmiller20:03:40

I believe this behavior has changed in Java since the function was originally written

sundarj20:03:01

ah, i see. that makes sense.

sundarj20:03:41

@alexmiller want me to file a Jira?

alexmiller20:03:55

sure. there might be one so do a search

alexmiller20:03:04

I vaguely recall this coming up before

sundarj20:03:23

ok, will do

sundarj20:03:23

ah, yeah. it's CLJ-1360

sundarj20:03:47

and also CLJ-1857 i guess

alexmiller20:03:26

Thanks, I incorporated the changes from CLJ-1857 into the CLJ-1360 patch and closed 1857

alexmiller20:03:38

will try to get that closed in 1.10

sundarj20:03:07

great, thank you! 🙂

sundarj20:03:22

pass -1 as the limit to get them back:

=> (clojure.string/split "a_b_" #"_" -1)
["a" "b" ""]

mfiano20:03:14

Does anyone here have any Common Lisp experience? I'm trying to figure out an idiomatic way to map a basic snippet of code to Clojure

bronsa20:03:16

I've used common lisp ~6 years ago but ask away, I'm sure that if I can't somebody else will be able to answer

mfiano20:03:26

I'd like a function that maps over a few different ranges of numbers, and puts them all into a flat sequence of numbers, like this: https://github.com/HackerTheory/crawler2/blob/master/neighborhood.lisp#L216-L231

mfiano20:03:50

This is mutating the same lexical results list. I need a functional/clojure way to do the same within the same function

mfiano20:03:53

So given (range 0 5), (range 0 3), and (range -3 1), this theoretical function would return (0 1 2 3 4 0 1 2 -3 -2 -1 0)

noisesmith20:03:29

(partial apply concat) returns a function that does that

mfiano20:03:44

The best I can think of is nested fors with flatten

sundarj21:03:10

yeah i think for is the way to go here, though you never need to nest fors

sundarj21:03:26

for takes multiple bindings, and you can do :when and :let inside the bindings vector

sundarj21:03:34

(for [x [1 2 3]
	  y [4 5 6]
	  :let [foo (+ x y)]
	  :when (> foo 7)]
  [x y])
([2 6] [3 5] [3 6])

sundarj21:03:39

and then you could apply concat that to collate them

sundarj20:03:58

=> (concat (range 0 5) (range 0 3) (range -3 1))
(0 1 2 3 4 0 1 2 -3 -2 -1 0)

noisesmith20:03:19

oh yeah, that's just concat

mfiano20:03:00

Ok, well, that is the basic idea, although like the linked code, this function would be a HOF that calls a function on each number, and puts that in the returned sequence instead of the number.

mfiano20:03:06

Sorry I left that part out

mfiano20:03:57

also has a conditional to see if it even will call the function or just skip the number

just_crashed20:03:49

if you need to include some numbers and exclude others, combine it with filter

mfiano20:03:12

That's just map + concat right? I need to map several times

just_crashed20:03:40

what do you mean "map several times"? I don't quite follow you there

noisesmith20:03:23

(into [] (comp cat (map f)) [coll1 coll2 ... colln])

mfiano20:03:13

The idea is I'm collecting a list of neighbor cells of a 2D grid given an origin. If I were for example to collect a cross shape with an arbitrary size, I'd need to map from left to right, then from top to 1 above origin. and then 1 below origin to bottom, in order to skip processing the origin twice (because it already was with the left to right pass)

just_crashed20:03:32

depending on the task, I would probably use a flat array to store matrix and generate the indices I need

just_crashed20:03:57

or maybe even use something like core.matrix if you need more advanced operations

mfiano20:03:52

Thanks. I'm porting that whole file that I linked, and that is the last function to write. It's basically a kernel for convolution-based image processing

just_crashed21:03:18

Oh, ok, so you're using nested vectors?

mfiano21:03:58

I'm not sure what you mean.

mfiano21:03:16

(defmethod collector :horizontal [_]
  (fn [{[_ max-extent] :extent :as kernel} f]
    (for [x (range (- max-extent) (inc max-extent))
          cell (neighbor-cell kernel x 0)
          :when cell]
      (f cell))))
Here is my function for processing a horizontal strip of cells given an origin. It's easy because there is no sparsity or multiple collections.

just_crashed21:03:29

Hmm, I'm not sure I understand it completely (sorry). What 'neighbor-cell' returns?

mfiano21:03:52

a record or nil

just_crashed21:03:49

You sure you didn't mean :let [cell (neighbor-cell ...)] then? Bindings in for work like a nested loop

just_crashed21:03:13

So you're iterating over whatever neighbor-cell returns, binding each result to cell

mfiano21:03:26

You're right

just_crashed21:03:54

Aha, ok then. So basically your 'neighbor-cell' returns the cell given x and y

just_crashed21:03:09

So you need to generate x,y coordinates for the cross shape

mfiano21:03:16

It could also return nil, hence the :when

mfiano21:03:42

Yeah and those coordinates are 3 different collections

mfiano21:03:10

left to right. top to 1 above origin. 1 below origin to bottom.

just_crashed21:03:22

Can't you just call range to generate basically two strips: one horizontal (x is fixed, y is (range start end)), one vertical (y is fixed, x is (range start end))?

mfiano21:03:25

I could but 1 of those strips needs to be split, so 3 total.

mfiano21:03:32

otherwise I am processing the middle cell twice

just_crashed21:03:10

Well yeah, but you'll concat everything together anyway, so no big deal?

mfiano21:03:53

Yeah I guess I'm just a newbie. I can create those 3 collections with a single for with 3 different bindings?

just_crashed21:03:37

I'd do something like (map vector (repeat x-of-origin) (range (- y-of-origin size) y-of-origin)) - it will give you a vertical strip above the origin

just_crashed21:03:50

a seq of vectors

just_crashed21:03:10

then the same for the strip below the origin, then for the horizontal strip

just_crashed21:03:24

then concat everything together and map\filter as you wish

mfiano21:03:16

Ah nice. That seems easy enough. Thanks

just_crashed21:03:31

Well, or you can apply "for" to get a full square around your origin, but you'll need to filter out the indices that don't fall into the cross anyway, so I'm not sure it'll be more concise.

just_crashed21:03:41

Glad if it helps!

gryclmn20:03:52

Hello all! 🙂 Extremely new here. Im wondering why I get the space between "John" and "!" in my output. => ((fn [name] (println "Hello, " name "!")) "John") Hello, John !

noisesmith20:03:22

@gryclmn: println adds spaces between args

gryclmn20:03:02

ha! How can I avoid this? I also apologize for not wrapping the code as "code", just saw that ability, new to Slack

noisesmith20:03:27

@mfiano: sounds like what you need is two steps, generating x/y pairs, then doing something with each pair

mfiano21:03:17

Thanks. I'll think about it. I'm actually porting that whole file that I linked, and this is the last function to write.

noisesmith20:03:48

@gryclmn: you can create a string with str then call println on that

sundarj20:03:26

=> (str "Hello, " "John" "!")
"Hello, John!"
=> (println (str "Hello, " "John" "!"))
Hello, John!
nil

gryclmn20:03:29

@noisesmith Thank you very much!

gryclmn20:03:42

@sundarj Thank you

mfiano22:03:26

(defn testfn []
  (map vector (range 0 5)))
Why would this give a NullPointerException when called if defined in a package, but not in the REPL?

alexmiller22:03:19

should be ok either place. can you share the NPE output?

mfiano22:03:33

What's NPE?

alexmiller22:03:42

NullPointerException

alexmiller22:03:52

looks like that error is coming out of slamhound, not your code

alexmiller22:03:47

well, I guess not, that’s just in the middleware

mfiano22:03:56

odd. it's the same function defined in a package and the REPL. when called with the fully qualified package name it errors, but works fine with the REPL package

alexmiller22:03:19

oh, it’s picking up mfiano.crawler.kernel/map not clojure.core/map

alexmiller22:03:34

so map means something different in the namespace

mfiano22:03:48

Oh I might have some funky refers going on there. Thanks

alexmiller22:03:09

yeah, be careful overlapping clojure.core fns :)