Fork me on GitHub
#clojure
<
2016-09-19
>
fenak00:09:20

is exceptions considered bad practice for error handling in clojure? i googled and came across https://adambard.com/blog/acceptable-error-handling-in-clojure/ post, is this the community standard for?

lopalghost01:09:54

@fenak: I'm not sure there is a community standard, though that's a totally reasonable way to approach errors Exceptions are fine too, though

fenak01:09:19

@lopalghost good - i’m writing some code that uses a java library, but don’t know how should i expose the errors to my clojure part.

jasonjckn02:09:03

can I do alter var root on a macro?

gfredericks02:09:27

yes, but you have to know how macros work

gfredericks02:09:48

in particular that they are functions with two extra magical arguments

gfredericks02:09:09

otherwise you will have arity issues

gfredericks02:09:27

also depending on what you want to do it might not even make sense

gfredericks02:09:49

i.e., you'd have to do it before compiling the relevant code that uses the macro

eraserhd02:09:13

Is there some protocol in Clojure for this already?

gfredericks02:09:33

So you want a fmap?

eraserhd02:09:31

(Transducers are more general, I've taking to calling it xfmap.)

gfredericks02:09:13

you could always um

eraserhd02:09:14

I'm trying to make a reasonable default for the xfmap arg that's like clojure.walk/postwalk.

gfredericks02:09:21

what's that library

eraserhd02:09:44

Wait. clojure.walk needs to do it, of course.

gfredericks02:09:58

I think it has its own protocol

gfredericks02:09:05

no I was thinking of...

eraserhd02:09:22

Er, that's actually what I meant.

eraserhd02:09:14

clojure.walk just has a big cond.

eraserhd02:09:56

Now I want a protocol to dispatch on the second arg. 😄

jasonjckn02:09:01

@gfredericks thanks, I saw the 'magic' args

jasonjckn02:09:03

pretty amazing this is even possible

jasonjckn02:09:08

although i am struggling lol

gfredericks02:09:45

@eraserhd smells like a multimethod

eraserhd02:09:01

meh, I'll skip this for now.

eslachance03:09:27

Sorry for x-posting from #beginners but... kinda stalled here. Very basic question. Why does this code tell me Parameter declaration "let" should be a vector? feels like I'm missing something small.

(defn start-socket
  (let [ws-address (:url (:body (http/get (str API "/gateway") {:as :json})))]
    (connect-socket ws-address)))

ghadi03:09:12

@eslachance translating compiler-ese: you're missing the function arguments, and the compiler thinks the let is the args

eslachance03:09:28

yeah... did not catch parameters as being the function params

eslachance03:09:33

added [] to first line.

eslachance03:09:42

I knew it was something small 😒

ghadi03:09:54

So, good news. Clojure 1.9 gets rids of that cryptic message and shows you something amazing

ghadi03:09:40

user=> (defn start-socket
  (let [ws-address (:url (:body (http/get (str API "/gateway") {:as :json})))]
    (connect-socket ws-address)))
CompilerException java.lang.IllegalArgumentException: Call to clojure.core/defn did not conform to spec:
In: [1] val: (let [ws-address (:url (:body (http/get (str API "/gateway") {:as :json})))] (connect-socket ws-address)) fails spec: :clojure.core.specs/arg-list at: [:args :bs :arity-1 :args] predicate: vector?
In: [1 0] val: let fails spec: :clojure.core.specs/arg-list at: [:args :bs :arity-n :bodies :args] predicate: vector?
:clojure.spec/args  (start-socket (let [ws-address (:url (:body (http/get (str API "/gateway") {:as :json})))] (connect-socket ws-address)))
, compiling:(NO_SOURCE_PATH:1:1)

eslachance03:09:52

Is that released or still in beta?

ghadi03:09:56

alpha still

ghadi03:09:23

it tells you a path to exactly where in the s-exp it can't parse

eslachance03:09:44

Alright. Wasn't really that cryptic to be honest. I just glazed over the word "Parameter", even though I know exactly what that means

eslachance03:09:17

The let threw me off my game. My very noobish game ^_^

eslachance03:09:22

No on to figure out why my websocket isn't working

eslachance03:09:57

Maybe you have some experience with stylefruits/gniazdo?

eslachance03:09:52

I'm trying to troubleshoot this, connecting to a local websocket server works fine, but connecting to a remove wss:// address I'm not receiving the expected message (should be a HELLO of some sort)

jasonjckn03:09:21

so if I have a macro like

(defmacro ui2
  [& forms]
  (let [t (with-meta (gensym "ui_") {:anonymous true})]
    `(do (om.next/defui ~t ~@forms) ~t)))
you can see it does gensym to get a unique name to def, let's say I call ui2 in a function e.g.
(defn mk-ui [] (ui2 ...))
then I call mk-ui repeatedly in my code, since macros are expanded at compile time, how would I get a unique symbol for each call to mk-ui

jasonjckn03:09:52

if that macro is expanded at compile time, it'll pick a unique symbol name once, as oppose to once per call to mk-ui

nha07:09:20

Morning, I have a question regarding clojure zippers and would appreciate some help: http://stackoverflow.com/questions/39558330/clojure-xml-zipper-walk-and-cut

ikitommi08:09:32

@arohner there is a clojure client gen in SwaggerHub. Just poked them if that’s open source and somewhere.

crankyadmin10:09:28

Hi, how would I go about accessing a Scala implicit? A lá .toDF liked use here https://spark.apache.org/docs/latest/sql-programming-guide.html#inferring-the-schema-using-reflection

crankyadmin10:09:42

Or a slightly different question, how do I go about using import spark.implicits._ in Clojure

mpenet12:09:01

@ghadi 1.9 replaces a cryptic error message with another larger cryptic error message. I personally know what it means, it's data, etc, but I doubt it makes beginners experience better ("Parameter declaration "let" should be a vector" is arguably better in that case).

mpenet12:09:19

hopefully this will improve with time

mpenet12:09:37

rust / elm are good inspirations for these things

mpenet12:09:38

I hit an annoying example earlier: a set as predicate will not even suggest what values are acceptable with s/explain*

mpenet12:09:57

if inline yes, otherwise no.

mpenet12:09:45

in short, there's room for improvement (so far Schema is better at error reporting imho)

octahedrion12:09:54

can I define recursive specs in Spec ?

abdullahibra12:09:55

is there a better solution than what i did?

abdullahibra12:09:25

trace is the summation of diagonal of matrix

abdullahibra12:09:44

user=> (trace-mat (cl/matrix [[1 2 3] [4 5 6] [7 8 9]])) 15.0

Chris O’Donnell13:09:14

@abdullahibra it looks like clatrix has its own trace function; you can look at its source for a good solution at http://quantisan.github.io/clatrix/

credulous13:09:28

Crossposted from #clojurescript… Deploying a uberjar to ec2 (ubuntu 14.1, openjdk 8), and I get this error which I didn’t get locally or on another local server: Failed to get driver instance for jdbcUrl=jdbc:

credulous13:09:09

project.clj does seem to have the right dependency…

:dependencies [
                  [ring "1.5.0"]
                  [metosin/compojure-api "1.1.8"]
                  [cheshire "5.6.3"]
                  [clj-time "0.12.0"]
                  [im.chit/cronj "1.4.4"]
                  [hiccup "1.0.5"]
                  [cljs-ajax "0.5.8"]
                  [secretary "1.2.3"]
                  [reagent-utils "0.2.0"]
                  [reagent "0.6.0"]
                  [org.clojure/clojure "1.8.0"]
                  [org.clojure/clojurescript "1.9.229" :scope "provided" :exclusions [org.clojure/clojure]]
                  [selmer "1.0.7"]
                  [markdown-clj "0.9.89"]
                  [ring-middleware-format "0.7.0"]
                  [metosin/ring-http-response "0.8.0"]
                  [bouncer "1.0.0"]
                  [org.webjars/bootstrap "4.0.0-alpha.3"]
                  [org.webjars/font-awesome "4.6.3"]
                  [org.webjars.bower/tether "1.3.3"]
                  [org.clojure/tools.logging "0.3.1"]
                  [compojure "1.5.1"]
                  [ring-webjars "0.1.1"]
                  [ring/ring-defaults "0.2.1"]
                  [mount "0.1.10"]
                  [buddy "1.1.0"]
                  [cprop "0.1.9"]
                  [org.clojure/tools.cli "0.3.5"]
                  [luminus-nrepl "0.1.4"]
                  [abengoa/clj-stripe "1.0.4"]
                  [luminus-migrations "0.2.6"]
                  [conman "0.6.0"]
                  [org.postgresql/postgresql "9.4.1209"]
                  [org.webjars/webjars-locator-jboss-vfs "0.1.0"]
                  [com.novemberain/monger "3.0.2" :exclusions [com.google.guava/guava]]
                  [luminus-immutant "0.2.2"]]

credulous13:09:48

All Google wants me to do is add org.postgresql/postgresql, which I have. Not sure how to diagnose.

andrei14:09:39

@credulous you can debug your deps using lein deps :tree

andrei14:09:55

and you probably also need [org.clojure/java.jdbc “0.3.x"]

gon14:09:24

@credulous some web containers fail to classloading the jdbc driver. try explicitly putting this before connection code (Class/forName "org.postgresql.Driver")

credulous14:09:50

OK, thanks, will try now

plexus14:09:18

@credulous some friends of mine ran into that recently, the DATABASE_URL that Heroku gives you is not in the format that JDBC understands. There's a package to convert it https://github.com/ariaru/workshop-manager/commit/fa47e23226ee4a07c8590347fa287eeb66762032#diff-fdee768673c47c26b8e610ec61e0a508R21

credulous14:09:02

@plexus Thanks, I’m not using heroku though

plexus14:09:48

oh sorry, I misread that, might be a different issue

plexus14:09:48

still I'd look into the URL you're using, compare it with how it looks on the other server

andrei14:09:24

@credulous I think @gon is right, we use Korma and we specifically tell it to load the postgresql driver.

borkdude14:09:53

It might be me, but (doc spit): "Options passed to http://clojure.java.io/writer"

credulous14:09:48

@gon @andrei That did the trick, thanks!

borkdude14:09:58

a spec would be cool here 🙂

kalouantonis15:09:29

Hi everyone. I'm trying to split a channel in to multiple channels. I'm using a file object like this {:id #uuid ..., :path "somewhere.txt"} and I have a pub/sub channel that uses :id as the topic. I was just wondering if that's bad practice? Having single use subscriber channels?

matan17:09:32

Unlike major versions of scala, are latest versions of clojure backward compatible?

matan17:09:21

Do you ever need to worry about which version a clojure jar had been compiled with, using it as a dependency in your clojure application?

hiredman17:09:47

the answer to that is kind of complicated, largely because the recommended way to package clojure is as source, not compiled to jvm bytecode

matan17:09:41

interesting... @hiredman I'll come back for your full answer which you are composing now I think 🙂

hiredman17:09:41

you can do what is called AOT compilation, where you save the compiled bytecode, and people will often do that for applications (for all the wrong reasons)

hiredman17:09:18

but that is bad practice for libraries you are distributing

hiredman17:09:47

and I would argue bad practice for your applications (aot compilation has quirks that will bite you)

Alex Miller (Clojure team)17:09:15

@matan we endeavor to make Clojure largely backwards compatible. it’s relatively rare that we introduce a change that breaks that and most older Clojure code will still work today.

Alex Miller (Clojure team)17:09:12

we also do consider whether existing AOT compiled Clojure code will be affected when we modify portions of the runtime invoked by AOT compiled code (calls to RT, Reflector, etc) and often leave shims to make that work (most people don’t encounter this both because those signatures don’t change much and because usually I test for that kind of thing in advance when I suspect it’s an issue)

Alex Miller (Clojure team)17:09:11

one area that will be more prominent in Clojure 1.9 is that due to the added specs there was some syntax that was accepted and either worked or was ignored in the past that will now fail due to more stringent enforcement of what we considered to be accepted syntax all along

Alex Miller (Clojure team)17:09:34

I think it’s a matter of perspective whether you consider that a “breaking" change or not.

hiredman17:09:22

coming form scala, it is likely you will think you need to AOT compile everything to bytecode, you don't. at best it does nothing at worst you will aot compile some open source library and someone else will try to use it and it will break everything for them

mhjort17:09:16

@matan @alexmiller I was working few years in a project where we were building more than dozen of micro services. One of the key reasons we started switching those services from Scala to Clojure was the backwards compatibility. With Scala we had a lot of issues with version upgrades but with Clojure usually none. So I have really good experience with Clojure upgrades.

bfabry17:09:06

just yesterday I finished killing the last bits of AOT in a codebase. very pleased. judicious use of Clojure.var and shock-horror java can usually get you out of it

georgek17:09:11

I’m trying to create a helper fn in one namespace that, when called, defn’s some fn’s in the calling namespace. I’ve tried something like this: (defn define-fns [] (require ‘[stuff]) (defn fn1 [] (+ 1 1)) ) then call it in a namespace: (define-fns) (println (fn1)) I get a runtime ‘unable to resolve symbol’ in the calling namespace. I’ve tried adding (in-ns ‘calling.namespace) which makes no difference. 1) Is this approach a bad idea in general? (Basically it will eliminate a lot of replicated boilerplate) 2) What’s the ideomatic way to approach situations like this?

bfabry17:09:23

@georgek a macro as the entry point for your helper function, or just a macro altogether, would probably be a better idea. Because then the generated 'boilerplate' code gets inserted in the namespace where it was called, and evaluated there

bfabry17:09:41

ie

(defmacro define-fns []
`(do
  (require [~'stuff])
  (defn ~'fn1 [] (+ 1 1))))

georgek17:09:45

That’s what I suspected. How do you properly define a fn in a macro? When i’ve tried: (defmacro define-things [] `(defn some-name# [v1#] (stuff))) I get wierd errors

georgek17:09:53

nice, thanks!

georgek17:09:59

I’ll give that a try

bfabry17:09:06

boot.user=> (defmacro define-things
       #_=>   []
       #_=>   `(defn some-name#
       #_=>        [v1#]
       #_=>       (stuff)))
#'boot.user/define-things
boot.user=> (macroexpand
macroexpand     macroexpand-1
boot.user=> (macroexpand '(define-things))
(def some-name__2332__auto__ (clojure.core/fn ([v1__2333__auto__] (boot.user/stuff))))
macroexpand is super useful with stuff like this. afaict your example should work, except stuff is undefined and you're giving the function a random name

georgek17:09:54

oh yeah, the random name is a problem. I see what I didn’t quite get w.r.t properly defining a name inside the macro. That explains why the fn I thought defined couldn’t actually be found. thanks!

matan17:09:55

@hiredman @alexmiller @mhjort @bfabry thanks a lot. so how do you deploy and start clojure applications outside a development environment then? what's the best practice?

seancorfield17:09:52

@matan you’re going to get a variety of suggestions there but the most common will probably be to produce an "uberjar" (containing all the dependencies, but mostly in source form), and then you can just java -jar it.

hiredman17:09:19

something like java -cp ... clojure.main -m your.main.namespace where your.main.namespace contains a '-main' function works great

seancorfield17:09:41

Or lein run if you’re using Leiningen 🙂

seancorfield17:09:14

(`lein uberjar` then java -jar target/myproject…-standalone.jar for the "uberjar" approach)

hiredman17:09:35

one of the reasons people aot compile is to get a Main-Class for use with java's -jar flag

Jeremie Pelletier17:09:36

I really like the uberjar approach - devops doesn’t even needs to know they’re running clojure that way 🙂

Alex Miller (Clojure team)17:09:12

@hiredman you can do that with a thin shim though, don’t need to AOT the world

Jeremie Pelletier17:09:18

plus you’re deploying a jar without any dependencies on your build toolchain

hiredman17:09:36

you can shim to launch, or use clojure's -m flag

seancorfield17:09:51

@hiredman @alexmiller Yeah, I wish there was more "best practice" out there about having a shim for -main and dynamically loading the rest… It’s a Frequently Asked Question from folks new to Clojure.

bfabry17:09:13

most of my clojure projects are being deployed into some gigantic java behemoth like hadoop or google dataflow, so I have a java class that implements their required junk framework interface and then that calls into clojure. If I was doing a pure clojure app I would have a single very small clojure namespace aot'd as an entry point

Alex Miller (Clojure team)17:09:16

if someone drafts a PR, I’m happy to add it to the http://clojure.org/guides/faq

Alex Miller (Clojure team)17:09:39

or as a separate guide, not sure how long that would be

seancorfield17:09:53

Heh, touché @alexmiller — someone should get right on that… quietly steps back into the shadows 🙂

matan17:09:29

I wasn't aware uberjars may contain sources rather than bytecode until now....

Jeremie Pelletier17:09:42

thats good to know! I wasn’t aware you could AOT just -main and not everything it touches

hiredman17:09:52

a jar is a zipfile, you can put anything in there

hiredman17:09:19

@lambdacoder: it takes a bit of care

Jeremie Pelletier17:09:20

yeah, I just assumed since the main ns requires everything else, the whole programs gets AOT’d transitively

hiredman17:09:25

I don't think it is worth it

matan17:09:31

so java -jar which was suggested above in this context, will it compile all the sources?

hiredman17:09:35

-m is there, works great

Jeremie Pelletier17:09:02

java -jar is to launch a jarfile, not compile it

hiredman17:09:37

phil pushed to get -m added to clojure.main exactly for this purpose

bfabry17:09:55

java -jar will just invoke the .class file referenced in the manifest as main. so you need to have at least one compiled class, with a main method in it

seancorfield17:09:59

You main ns doesn’t need to require anything at compile time, it can (require …) at runtime, then (resolve …) and call into that. So when you AOT that ns, you get just that file’s stuff (and Clojure itself, unfortunately) but the rest of your app and all its libraries remain in source form.

Jeremie Pelletier18:09:34

nice, I somehow always frowned on dynamic requires for some reason 😛

matan18:09:37

@lambdacoder @seancorfield so what should I make of the earlier suggestion above ― > @matan you’re going to get a variety of suggestions there but the most common will probably be to produce an "uberjar" (containing all the dependencies, but mostly in source form), and then you can just java -jar it. ?

seancorfield18:09:41

(until the AOT transitive code bug is fixed)

seancorfield18:09:58

@matan Like I said, you’ll get multiple suggestions 🙂

matan18:09:48

@seancorfield indeed right now they are all kind of mixed in my head what a small can of worms 🙂

Jeremie Pelletier18:09:51

@matan I think the simplest route is to run “lein uberjar” and then run “java -jar” on the *.jar file it outputs

bfabry18:09:10

@lambdacoder there is more to the story than that though

matan18:09:12

@lambdacoder wouldn't that AOT?

seancorfield18:09:17

Historically, at World Singles, we’ve deployed source and used Leiningen (and now Boot) to simply run the functions we want. But we are moving toward uberjar deployment as we move to Docker since it makes devops much simpler.

hiredman18:09:53

it does take AOT

Jeremie Pelletier18:09:55

oh yeah that will AOT everything

hiredman18:09:07

uberjar doesn't imply aot

hiredman18:09:23

to make -jar work like that you will need to AOT though

bfabry18:09:25

but java -jar implies a main, which implies aot

seancorfield18:09:33

As noted above, you can set it up to AOT just your main entry point and nothing else.

hiredman18:09:35

no it doesn't

hiredman18:09:55

you can pass -jar a main class to run

bfabry18:09:03

fwiw,

(defn- main []
  (require '[my.application.system])
  (my.application.system/start))

hiredman18:09:21

so java -jar some.jar clojure.main -m your.main.namespace works without any aot

seancorfield18:09:26

You mean (defn -main [] …) but yeah.

bfabry18:09:30

yes, that is true

hiredman18:09:45

look, just use -m, and you don't need any of these acrobatics

seancorfield18:09:46

The nice thing about @hiredman’s suggestion is you can have a single JAR with multiple -main functions in different namespaces and choose which one to run at startup time (right?).

hiredman18:09:04

actually you can do that with aot too

Jeremie Pelletier18:09:28

isnt the main class just an argument of the JVM anyways?

hiredman18:09:31

you just explictly pass the main class

seancorfield18:09:36

☝️:skin-tone-2:

hiredman18:09:04

when using -m the main class is clojure.main which interprets the -main option

hiredman18:09:31

-m can also take a reference to a var, so you can have an entry point function that isn't named -main

matan18:09:07

these things make me nervous deploying applications.... a position I would not like to be in. when I'm deploying stuff, the last thing I want to be thinking about is whether I'm doing something that will make it behave differently than in test environments or backfire at the language infrastructure level. there's too many other concerns to look after when deploying, it's a bad time for shoehorning.

Jeremie Pelletier18:09:27

it looks worse than it actually is

matan18:09:28

So I hope there'd be something authoritative about how all these things interrelate and recepies for what the commands should be.

seancorfield18:09:34

There’s no One True Way.

Jeremie Pelletier18:09:47

what you’re changing is how the program’s entry point is called

Jeremie Pelletier18:09:55

not the environment or the program

matan18:09:30

@seancorfield that's why I used the plural word "recepies" 🙂

Jeremie Pelletier18:09:36

and i feel its a legacy of the java ecosystem: theres already a lot of ways to run your java programs to begin with

matan18:09:07

I'll start off with the lein approach (should I say hack?) I think. Cons: possibly one extra process to complicate managing production

Jeremie Pelletier18:09:39

you could also run your tests the same way you run production: using -m

Jeremie Pelletier18:09:56

instead of making your live environment like your test one, make your test environment like the live one

Jeremie Pelletier18:09:11

and keep developing with the repl 🙂

Jeremie Pelletier18:09:56

at that point its probably better called staging, but thats just nomenclature 🙂

bfabry18:09:57

@matan personally I'd go with hiredman's suggestion of java -jar clojure.main -m your.main.ns. less devops. no dependency on lein in prod. conceptually simple.

matan18:09:24

@bfabry but no consensus above on whether this is AOT or not (?)

bfabry18:09:35

@matan there is zero aot required for that

Jeremie Pelletier18:09:37

this wont be AOT, unless you want it to

matan18:09:39

@lambdacoder yep, it is just hard for me to compose the full procedure and rational about the -m approach, from the interwining comments here.

Jeremie Pelletier18:09:51

its a two parts process: generate a jar file containing clojure and your program’s sources, then launch that on the JVM telling clojure to boot your program

hiredman18:09:09

the last place I worked use to package a tarball of jar files and launcher scripts

Jeremie Pelletier18:09:16

lein uberjar will take care of the first, java -jar <uberjar> clojure.main -m <your main> will do the rest

Jeremie Pelletier18:09:25

unless I typo’d something

Jeremie Pelletier18:09:47

you dont need AOT to use uberjar

Jeremie Pelletier18:09:54

id just do it for the convenience of having a single jar

hiredman18:09:59

there are a lot of options, generally what forces you to choose between them is some outside constraint

hiredman18:09:14

not because one is "better" or "blessed"

matan18:09:40

I think each approach can be an answer there on SO

Jeremie Pelletier18:09:43

yeah, all of this is useless if devops wants a *.war file

hiredman18:09:49

that being said, use lein uberjar to build an uberjar, launch it with -m, that don't AOT

Jeremie Pelletier18:09:48

lein can automate your entire release process actually, from pre/post build tasks to extra packaging to signing to tagging and whatnot

matan18:09:20

@lambdacoder that sounds interesting as well

Jeremie Pelletier18:09:54

check out :release-tasks in project.clj

matan18:09:18

Okay. I will add that as one answer on SO

matan18:09:27

A disclaimer about cognitive limitations: Maybe very theoretically I would manage to separate the different threads and viable methods here along with their rationalizations, but it's been a whirlwind. Some of you would be much better at cohesively explaining and rationalizing them up there for common good. http://stackoverflow.com/questions/39579669/how-to-package-and-start-your-clojure-application I will do my best in return, to contribute to clojure open source projects, like I've done elsewhere, but alas I cannot offer anything beyond that....

hiredman18:09:19

in the interest of completeness, lein actually defaults the Main-Class in ubjerjars to clojure.main, when you don't specify a main class to lein, so you can even leave that out when running the uberjar using -jar

matan18:09:50

As the so-called "owner" of the SO page about how to start a new scala project, I suspect this will become a popular (and more importantly useful!) page.

hiredman18:09:40

you don't know anything about clojure, so having you own a page about how to do stuff doesn't seem like it would be useful

matan18:09:19

come on, I didn't mean owning anything, it was just a silly metaphor

dpsutton18:09:27

i think he meant he asked a question on stack overflow, not that he answered it.

dhruv120:09:06

i am trying to remove keys from my map. i need to apply a function over the key first to see if i need to remove it.

dhruv120:09:37

here is the code I have

(->> '({"1" "some-value"}
       {"1/2" "some value"}
       {"1-gds" "something"}
       {"" ""}
       {{:a :b} "some-other-value"})
     (remove (fn [[k v]] (or (empty? k) (map? k)))))

dhruv120:09:00

1. Unhandled java.lang.UnsupportedOperationException
   nth not supported on this type: PersistentArrayMap
but i get this error. I’ve no idea why

hiredman20:09:47

that is the error you get when you try to sequential destructure a map, which is not sequential

hiredman20:09:21

it looks like you are treating maps as pairs

dhruv120:09:25

hmm. i see

dhruv120:09:32

yes, i see the problem

hiredman20:09:34

a map is collection of pairs

hiredman20:09:41

(more or less)

hiredman20:09:05

usually people use a two element vector for a pair in clojure

dhruv120:09:46

using a vector and removing the “keys” i don’t need works a lot better

dhruv120:09:49

thanks again

smw21:09:20

Hey! I want to write a few unit tests for a private function. Is this sort of thing considered legit? Best practice is considered not to test private functions?

smw21:09:22

;; Referring to the fully-qualified var *does* work.
  (is (= 4 (#'example/foo 2))))

smw21:09:31

Is there a better way to do it?

hiredman21:09:37

this is disputed, but I think the best way is to not write private functions

smw21:09:48

Interesting 🙂

hiredman21:09:16

here come the disputes

smw21:09:28

Does it matter if you’re trying to have some sort of public api? Just put the ‘private’ stuff in a different ns?

hiredman21:09:04

usually yeah, something like that is what tends to happen

trptcolin21:09:08

agreed w/ @hiredman 😉 yeah exactly - i think the fact that you want to/can test it is a signal that it's some legit thing you might want to reuse or replace

smw21:09:28

Yeah. Ok. I’ll give it a shot.

trptcolin21:09:00

even in OO-land where people tend care more about encapsulation, i've heard people say private fns are a class waiting to be extracted

grzm21:09:15

That's an insightful way to think about it. Nice

bostonaholic21:09:50

@trptcolin “private fns are a namespace waiting to be extracted” 😜

seancorfield21:09:32

Re: private functions — my experience is that I’ve often made private functions public later on as I’ve discovered uses for them beyond the namespace in which they were defined: unexpected reuse.

grzm21:09:55

Also makes testing more straightforward.

seancorfield21:09:38

I used to make fns private by default at first and only expose the "API" in each namespace, but now I almost never make a fn private. That was me letting go of my decades of OOP experience and going back to me FP roots 😆

smw21:09:57

Good stuff. Thanks for pushing me in the right direction.

grzm21:09:26

When using {:keys [foo bar baz]} destructuring, it doesn't seem to make a difference if I use symbols or keywords in the vector. Is there a "best practice"? Or maybe something subtle I'm missing?

smw21:09:29

I think it’s still sometimes useful to specifically demark public vs private api if you’re providing code for others to use...

sveri21:09:34

haha, reminds me of when I started with clojure. Oh yea, thats a utility function, make it private and this one, and this one too and, almost all the functions. And some time later I was like, uhm, what for? What does it do to make them private?

smw21:09:44

but I can see how just putting that in another namespace solves that.

sveri21:09:14

@smw thats what I just wanted to say, an api has to be exposed in its own namespace, thats what I concluded for me some time ago

bfabry21:09:59

@grzm I believe keywords only work by accident, and may actually blow up with the new specs for the let form in clj 1.9

grzm21:09:14

They haven't blown up yet 🙂

grzm21:09:33

But you're saying I should just use symbols.

bfabry21:09:53

yup you're right, just checked that. but yeah I'm 100% sure symbols are the correct thing to use

bfabry21:09:35

ah, there we go, keywords explode on namespaced maps

bfabry21:09:07

boot.user=> (let [#:foo{:keys [:second]} #:foo{:second 'bar}] second)
#object[clojure.core$second__6211 0x73045ded "clojure.core$second__6211@73045ded"]
clojure.lang.Compiler$CompilerException: java.lang.IllegalArgumentException: Call to clojure.core/let did not conform to spec:
vs
boot.user=> (let [#:foo{:keys [second]} #:foo{:second 'bar}] second)
bar

grzm21:09:16

I must admit that I'm finding it difficult to read the namespaced maps.

grzm21:09:34

I hope I get used to it.

bfabry21:09:12

I suspect the amount of noise I packed on to one line there didn't help 🙂

grzm21:09:46

That said, I wouldn't expect the first example you provided to work, as the non-namespaced part of the key isn't a keyword: only the whole thing is. (or at least that's how I think of it)

grzm21:09:38

No, I'm finding it hard to read them in my own code, or the output rather. I haven't found myself writing them yet.

seancorfield22:09:10

We’re starting down the namespaced keyword path as we’re adopting clojure.spec. So far I’m liking the extra clarity they bring but it does mean you have to work a bit harder to prevent code becoming too dense.

grzm22:09:36

I'm definitely using namespaced keywords, I'm just having trouble reading #:blah{:second 'bar :some-num 3}

smw22:09:50

I totally get namespaced keywords, just not so much namespaced maps

bfabry22:09:55

the error message for that spec exception btw is a good argument that the parse tree that managed the most depth before failing should maybe come first

akiva22:09:59

Hi, sorry, but I’d like to touch back to the AOT conversation. I was always under the impression that AOT is what you did with uberjar. I’m fine with shims. I have one to use fork() with systemd in Ubuntu.

bfabry22:09:19

In: [0 0] val: #:foo{:keys [:second]} fails spec: :clojure.core.specs/local-name at: [:args :bindings :binding :sym] predicate: simple-symbol?
In: [0 0 0] val: ([:foo/keys [:second]]) fails spec: :clojure.core.specs/seq-binding-form at: [:args :bindings :binding :seq] predicate: (cat :elems :clojure.core.specs/binding-form) :rest (? (cat :amp #{(quote &)} :form :clojure.core.specs/binding-form)) :as (? (cat :as #{:as} :sym :clojure.core.specs/local-name))), Extra input
In: [0 0 0 0] val: :foo/keys fails spec: :clojure.core.specs/local-name at: [:args :bindings :binding :map :mb 0 :sym] predicate: simple-symbol?
In: [0 0 0 0] val: :foo/keys fails spec: :clojure.core.specs/seq-binding-form at: [:args :bindings :binding :map :mb 0 :seq] predicate: (cat :elems (* :clojure.core.specs/binding-form) :rest (? (cat :amp #{(quote &)} :form :clojure.core.specs/binding-form)) :as (? (cat :as #{:as} :sym :clojure.core.specs/local-name)))
In: [0 0 0 0] val: :foo/keys fails spec: :clojure.core.specs/map-bindings at: [:args :bindings :binding :map :mb 0 :map] predicate: coll?
In: [0 0 0 0] val: :foo/keys fails spec: :clojure.core.specs/map-special-binding at: [:args :bindings :binding :map :mb 0 :map] predicate: map?
In: [0 0 0 1 0] val: :second fails spec: :clojure.core.specs/ns-keys at: [:args :bindings :binding :map :nsk 1] predicate: simple-symbol?
In: [0 0 0 0] val: :foo/keys fails spec: :clojure.core.specs/map-bindings at: [:args :bindings :binding :map :msb 0] predicate: #{:as :or :syms :keys :strs}
going to quickly learn a heuristic of "jump to explanation with the longest In form" if that's common

bfabry22:09:25

@akiva uberjar just packages up all your dependencies and resources into a jar and creates a manifest (basically). if some of those resources are compiled class files it will include them, but they don't need to be there

akiva22:09:01

Gotcha. I think when I got into Clojure, whatever I had read at first was all about AOT with a few caveats and I just went with that.

smw23:09:31

newbie question:

smw23:09:39

how can I make this non-lazy?

smw23:09:41

(defn summarize-hits [hits]
  ;(info hits)
  (for [hit hits] (summarize-hits hit)))

smw23:09:47

do I have to use into?

bwstearns23:09:49

Maybe just do (map summarize-hits hits)?

bostonaholic23:09:57

@smw you can wrap in doall

smw23:09:05

map does the same thing? right? makes it lazy?

seancorfield23:09:08

@bwstearns that’s still lazy

smw23:09:22

(doall (for…) ?

gfredericks23:09:31

do you intend to make that recursive?

bfabry23:09:33

@smw do you want the results of the calls to summarize-hits? in what data structure do you want them?

bostonaholic23:09:46

yes, or (doall (summarize-hits ...

smw23:09:48

I’d like a vector, I think.

seancorfield23:09:03

(mapv summarize-hit hits) would be non-lazy

bfabry23:09:17

@smw then I would (mapv summarize-hits hits) (and I would change the name of that function to summarize-hit)

smw23:09:18

Actual: java.lang.IllegalArgumentException: Don't know how to create ISeq from: clojure.lang.Keyword

smw23:09:57

ahh, mapv

smw23:09:59

that’s wonderful

seancorfield23:09:22

I assume summarize-hits and summarize-hit are two separate functions? Otherwise you’re calling yourself recursively there...

smw23:09:05

Yep. I started with map and no other function...

smw23:09:16

but been fighting with the lazyness thing

bfabry23:09:25

fwiw if you don't care about the return results of summarize-hit then run! is another good option

smw23:09:46

gotcha. I tried doseq, as well, but I do care.

bwstearns23:09:47

@seancorfield That's what I read it as at first. Is there any other logic? It seems like something bad should happen when it bottoms out on hit not being iterable?

smw23:09:16

(defn summarize-hit [hit]
  (select-keys hit [:verb :ecr_code]))

(defn summarize-hits [hits]
  ;(info hits)
 (mapv summarize-hit hits))

smw23:09:25

after much refactoring, that’s where I am.

smw23:09:00

err, need another paren there 🙂

smw23:09:25

and mapv does exactly what I want, thanks.

bfabry23:09:41

cool cool. I don't really see any particular reason why that can't be left lazy, but to each their own.

bfabry23:09:05

maybe you're trying to print it or something I guess

smw23:09:23

yeah, creating log output… possibly I can switch it back to lazy, but it was making debugging hell.

smw23:09:38

Thanks so much for your (collective) help. I was struggling with that for a while.

smw23:09:01

Also, midje is really cool. especially being able to do what amount to mocks with (provided) and (against-background)

bfabry23:09:56

ha, I agree, midje is really cool, and just plain friendlier than clojure.test, but you might want to beware that most of the clojure community has decided on clojure.test, and so that's what most of the tooling and other libs are geared around. just a friendly caution

smw23:09:28

Yep. Totally willing to take that risk, at least at dev time.

smw23:09:57

If I’m going to release something for public consumption, I might port (the easy) tests to clojure.test.

seancorfield23:09:23

(if you do want to walk on the wild side — but slightly less wild than Midje — you could take a look at Expectations… nice, simple, idiomatic BDD-style testing)

seancorfield23:09:17

From a style p.o.v. it’s usually not worth having a function that just maps another function over its argument — just call map (or mapv as needed) directly instead

smw23:09:32

Yeah, that’s what I was doing originally.

seancorfield23:09:43

i.e., I would not define summarize-hits at all, just call (mapv summarize-hit hits) where it was needed.

smw23:09:48

But I ran into some issues, so was trying to get better debugging than the error I pasted above.

smw23:09:07

initially it was just part of a let binding list

bfabry23:09:32

I agree with sean on that. that extra layer of abstraction might obscure a better abstraction later

smw23:09:48

Yeah. I have a tendency to err the other way on this… was disappointed that you couldn’t nest anonymous function literals the other day 🙂

smw23:09:08

but I find embracing let to be the ideal compromise

smw23:09:32

my only problem with long let forms is that when things go wrong, sometimes it’s hard to understand exactly which piece is breaking.

smw23:09:57

probably I need to remember I can pull out spyscope or something

smw23:09:07

Is there any significant difference between (use) and (require :refer :all)? (I wouldn’t use either except when exercising things in the repl)

smw23:09:53

Awesome. First one’s much easier to type 🙂

akiva23:09:48

Someone really needs to collect all of Stuart’s posts into a blog or… waaaaait a second here.

akiva23:09:31

In all seriousness, I think it’d make a great 64-, 96-, or 128-page pamphlet.

akiva23:09:07

I look forward to all of what @ericnormand and @danielcompton publish weekly but I get giddy when Stuart writes something new.