Fork me on GitHub
#clojure
<
2020-03-27
>
deas08:03:13

Hallo everybody!

deas08:03:40

Where is the place to talk about graalvm - specifically native-image?

vijaykiran09:03:40

@deas maybe #graalvm

👍 4
roklenarcic12:03:18

I was thinking how, given that (a-map :a-keyword) is same as (:a-keyword a-map) it’s weird how we picked (sub-thing big-thing) form as canonical instead of (big-thing sub-thing)

dominicm12:03:01

(let [m (maybe-map)] (m :foo)) => 💥 Exception

eval-on-point12:03:04

especially when you consider that map and function are synonyms

dominicm12:03:26

We use the sub-thing because it's usually constant.

dominicm12:03:57

I'd never do (k m) I'd always use (get m k) in that case. If I knew the map, not the k, I'd do ({:foo 1} k) .

👍 12
athomasoriginal14:03:21

For Macros. I always hesitate to jump to them if possible. My question is, what is the opinion on resorting to a macro just to reduce boilerplate code? e.g instead of having to write the following over and over:

; try several-line-of-logic
;     catch
; log-something
; do-something-else
we encapsulate in a macro?

Alex Miller (Clojure team)14:03:15

you're not resorting, that's what macros are for

athomasoriginal14:03:50

Follow up Q: what are the things we should look out for when using macros? I read a lot about abuse of macros.

isak14:03:03

How much boilerplate would it save you? Doesn't seem like much from the example you gave

athomasoriginal14:03:47

pretend that several-line-of-logic , log-something and do-something-else are 2x more each.

andy.fingerhut14:03:46

Another reason in favor of using a macro for something like that is if you believe you will have dozens of occurrences of it, and you believe you are likely to want to change the behavior of all of them in a consistent way, via changing the macro definition later.

💯 4
isak14:03:49

Look out for using patterns that break the IDE (cursive). For it to work with Cursive, it usually needs to be similar to an existing built in form, like def, defn, let, etc

👍 4
athomasoriginal14:03:12

I will keep an eye out for that, isak.

cavan14:03:40

Is there a straighforward way to use figwheel with phantomjs for testing

athomasoriginal15:03:35

I don’t show phantomjs but the general idea is outlined here: https://betweentwoparens.com/clojurescript-test-setup#headless-browser-testing - maybe it will guide a little.

colinkahn15:03:37

What is the idiomatic way to make into safe for both lists and vectors/sets?

(let [xs '(1 2 3 4)]
  (into (empty xs) xs)) ; => (4 3 2 1)

(let [xs [1 2 3 4]]
  (into (empty xs) xs)) ; => [1 2 3 4]

colinkahn15:03:42

(defn some-transform [xs]
  (->> xs
       (map my-transform)
       (into (empty xs))))

colinkahn15:03:49

That is closer to what I’m doing, are you saying there’s a way to use into without empty or that I should be avoiding the need for lists to use it at all?

colinkahn15:03:20

ah ok, yeah I was leaning towards that but wasn’t sure

Alex Miller (Clojure team)15:03:20

why are you trying to retain the type of xs

colinkahn15:03:37

for sets mainly

colinkahn15:03:08

I can do (into (if (set? xs) #{} []))

Alex Miller (Clojure team)15:03:19

are you really using sets?

Alex Miller (Clojure team)15:03:13

in general, I would be suspicious of that function above - it's way better to just say (map my-transform xs) at the place you need it

colinkahn15:03:17

In my case I’m modifying some value at a user provided path but the function applies the path operation to all items in collections like lists/vectors/sets if it’s encountered. IE I don’t have control of the data and the structure should be retained in the return

colinkahn15:03:15

Here is the function with the specifics removed:

(defn upper-case-path
  [m [k & rest-paths]]
  (if-some [v (get m k)]
    (cond
      (and (empty? rest-paths)
           (string? v))
      (assoc m k (clojure.string/upper-case v))

      (sequential? v)
      (->> (map #(upper-case-path % rest-paths) v)
           (into (empty v))
           (assoc m k))

      (map? v)
      (assoc m k (upper-case-path v rest-paths))

      :else m)
    m))

(upper-case-path {:foo {:bar [{:baz "hello"}]}}
                 [:foo :bar :baz]) 
; => {:foo {:bar [{:baz "HELLO"}]}}

Alex Miller (Clojure team)15:03:39

if you really need that level of control, then you might have to write custom per-coll type code. in general, I find anything like that to smell kind of fishy

colinkahn15:03:58

I guess in this case my sequential? check wouldn’t even catch sets, so I could just always put lists into vectors and handle sets in a separate cond case

souenzzo17:03:14

@U0CLLU3QT checkout #specter (sp/transform sp/ALL inc coll) will do (0 1) => (1 2), [0 1] => [1 2] and #{0 1} => #{1 2} as you expect

souenzzo17:03:44

But I'm with Alex. I think that you don't need to preserve arrays/vectors/set's.

souenzzo17:03:33

(defn upper-case-path
  [coll & paths]
  (sp/transform (sp/multi-path
                  (for [path paths]
                    (interleave path (repeat (sp/if-path coll? sp/ALL sp/STAY)))))
                string/upper-case
                coll))

colinkahn17:03:06

@U2J4FRT2T haven’t used specter before but that snippet is making me want to

coby17:03:37

wait, why wouldn't you want to preserve coll types?

souenzzo17:03:37

after #specter, upper-case-path is the kind of function that I don't write I just write (sp/transform [:the :path :i :want] the-function-i-want the-data-i-know) It solve some problems when you can't control your data Once you get in control of your data, this kind of function that "fixes your data inplace" dosen't make sense anymore.

souenzzo17:03:58

Why would you transform :app.user/name in upper case? Just create a :app.user/name-in-uppercase

colinkahn18:03:34

That’s just the example I’m using, in the real usecase they’re transforms applied before being stored somewhere, so the keys matter

sergey.shvets19:03:03

I'm trying to implement a record that will have multiple fields and a context map that consumers can use as a regular map. Something like (defrecord A [private-field1 private-field2 context]) and I want all (assoc record :field val) to assoc to context field. I figured that it should be easy to do a deftype and implement IPersistentMap, but can't find a reference of all the methods required for IPersistentMap. Can someone point me into the right direction or how to implement a thing in the first paragraph with records? Thanks

hiredman19:03:22

you can just use the defrecord like a regular map

hiredman19:03:29

private fields are silly

4
sergey.shvets19:03:16

I don't care about private fields, it is just I don't know all the fields in the record in advance

hiredman19:03:24

that is fine

hiredman19:03:28

records act like maps

sergey.shvets19:03:47

so I do something like (defrecord A []) ?

hiredman19:03:56

user=> (defrecord A [])
user.A
user=> (assoc (->A) :x 1)
#user.A{:x 1}
user=>

hiredman19:03:33

ah, you mean you can't always fill in the those bits

hiredman19:03:42

maybe just use a map

4
sergey.shvets19:03:03

I'll have a couple of fields that will be always present and a bunch of helper methods to modify them. I thought that I will put them in an interface and implement those using a record. I want to make sure that consumers can't mess up with those fields in any way.

sergey.shvets19:03:21

I can leave them as namespaced keywords, but then it will be hard to consume these records with "filter" or any other methods, since you will always need to keep those extra fields in mind

quadron20:03:33

when doing require, is there a subtractive form for :refer :all? for example '[prelude.core :refer :all-but [something-not-needed]]

Graham Seyffert20:03:36

:exclude , I think?

Graham Seyffert20:03:16

Yeah -

(require '[clojure.string :refer :all :exclude [lower-case]])
WARNING: reverse already refers to: #'clojure.core/reverse in namespace: user, being replaced by: #'clojure.string/reverse
WARNING: replace already refers to: #'clojure.core/replace in namespace: user, being replaced by: #'clojure.string/replace
=> nil
(lower-case "ABC")
Syntax error compiling at (/private/var/folders/lb/q65kb_ys1g71l4n2fj7m8jvh0000gn/T/form-init7403812543420763679.clj:1:1).
Unable to resolve symbol: lower-case in this context
(require '[clojure.string :refer :all])
=> nil
(lower-case "ABC")
=> "abc"

✔️ 4
sergey.shvets20:03:11

Is there a way to add a namespace to fields in a record?

noisesmith20:03:15

you can use any key for fields, but only un-namespaced keywords for basis fields

jumpnbrownweasel20:03:03

@bear-z, I don't have a lot of experience with trying to do what you described further above, but I have to say that it seems unnatural in Clojure to use an interface to enforce an API. It seems more natural to use a regular map with namespaced fields for the required fields, and use spec to enforce the API if needed. I'm not sure what you meant about difficulties consuming the records with filtering.

sergey.shvets21:03:13

@UBRMX7MT7 Yeah, I don't like the resulting code at all. It feels complicated and unnecessary. I guess the alternative is just to have a bunch of methods to work on a specific map.

noisesmith20:03:39

(ins)user=> (defrecord Foo [a b])
user.Foo
(ins)user=> (map->Foo {:a 0 :b 1 :some.important/key "OK" [1 2 3] true})
#user.Foo{:a 0, :b 1, :some.important/key "OK", [1 2 3] true}

noisesmith20:03:01

if you need validation, one option is to add a validator to the container holding the data

noisesmith20:03:03

(ins)user=> (def foo 1)
#'user/foo
(ins)user=> (set-validator! #'foo number?)
nil
(ins)user=> (def foo 2)
#'user/foo
(ins)user=> foo
2
(ins)user=> (def foo :b)
Execution error (IllegalStateException) at clojure.main/main (main.java:40).
Invalid reference state
(ins)user=> foo
2

noisesmith20:03:24

this also works on agents/refs/atoms

noisesmith20:03:38

but importantly this isn't a property of the data object (that's immutable) but a property of the container enforcing the data objects it can contain

sergey.shvets21:03:50

Thanks @noisesmith. That should work.

borkdude21:03:42

what is the proper way to get rid of reflection here?

(let [fs (java.nio.file.FileSystems/newFileSystem (.toPath (io/file "bb.zip")) nil)
      to-extract (.getPath fs "bb" (into-array String []))]
  (java.nio.file.Files/copy to-extract (.toPath (io/file "bb"))
                            ^"[Ljava.nio.file.CopyOption;"
                            (into-array java.nio.file.CopyOption [])))
The double mention of java.nio.file.CopyOption feels verbose, but removing either results in a reflection warning or ClassCastException

noisesmith22:03:54

one would think into-array with an explicit class would not need an annotation

hiredman22:03:07

into-array is just a function, so it isn't like the compiler determines functions return types from Class object's you pass in to the function

johnj22:03:33

java interop is going to be verbose, it's java after all

borkdude22:03:03

so you're saying, this is the correct way to do it?

johnj22:03:56

no idea, just that when trying to use java libs, it sort of removes all the beauty of the language

johnj22:03:34

which for non-trivial apps, its frequent

borkdude22:03:34

it gets a little bit uglier than in Java itself because of the into-array stuff for passing varargs

Graham Seyffert22:03:26

Would make-array work a little better here? or no?

Graham Seyffert22:03:41

(make-array java.nio.file.CopyOption 0)
=> #object["[Ljava.nio.file.CopyOption;" 0x3fd7ca93 "[Ljava.nio.file.CopyOption;@3fd7ca93"]

Graham Seyffert22:03:57

Wonder if that’ll still generate the reflection warning…

borkdude22:03:52

yes, still reflection

seancorfield23:03:49

What I tend to do in situations like this is hide the creation off in a function somewhere and type hint the function return. That at least makes the code using the function result clean.

seancorfield00:03:49

https://github.com/seancorfield/next-jdbc/blob/master/src/next/jdbc/prepare.clj#L78-L80 (I'm open to discussion on whether using class and :tag here is more readable than `^"L...;")

👏 4
borkdude07:03:50

^{:tag (class (into-array String []))
interesting

dominicm22:03:45

I guess the clojure compiler doesn't support <T> type things.

hiredman22:03:02

generics, and they don't exist

hiredman22:03:32

they are a fiction of the java compiler and don't exist in the bytecode

dominicm22:03:58

But they don't need to do they?

hiredman22:03:37

since they don't exist there is no reason for clojure to support them

dominicm22:03:34

It's useful to express the return type of the function in terms of the arguments to the function.

hiredman22:03:55

you are trying to use type hints as a type system which they are not

hiredman22:03:22

they a means to communicate type information to the compiler, and since generics don't exist, the compiler doesn't need them

8
dominicm22:03:01

I don't need generics. I need either a into-array for all possible arrays, or a way for into array to produce compiler information based on arguments.

hiredman22:03:11

a macro would cover some subset of into-array uses, because into-array can be passed a non-class literal, e.g. (let [a String] (into-array a [])) which a macro is going to have trouble with

dominicm22:03:33

Yeah. I was definitely intending it would need to be compile time constant.

noisesmith22:03:06

it would be an interesting use case for a reader macro - create the array at a set size with hints to the compiler, eg.

#array-of[String 12]

noisesmith22:03:23

it would not make much sense to privide literal contents though(?)

dominicm22:03:25

Maybe a macro would work.