Fork me on GitHub
#beginners
<
2017-12-11
>
noisesmith00:12:36

but spec isn't a type system. we have types but we don't have a type system

noisesmith00:12:45

hmm... maybe I'm wrong about that. It's not static typing but maybe it counts as some kind of type system.

noisesmith00:12:24

anyway, what people are generally talking about when they talk about typesystems, and what clojure lacks even with spec, is static type checking

athomasoriginal00:12:53

Yes, not a type system in the traditional sense, but given that one could def, for example, what the arguments of a function are expected to look like, it serves a similar purpose, no? Perhaps I can go about this question in another way, when experienced Clojure developers see spec, how do they anticipate incorporating it into their code? Each developer will likely have a different answer, but curious on what excites people about this in general.

sova-soars-the-sora02:12:50

newbie question: how can I assoc new items into an atom?

noisesmith02:12:22

(swap! a assoc :k v :k2 v2 ...)

noisesmith02:12:26

or for deeper updates (swap! a assoc-in [:k :l :m] v)

sova-soars-the-sora02:12:05

thank you @noisesmith! it was the assoc-ini I was forgetting.

sova-soars-the-sora02:12:00

I just devised a nice way to send users updates for only the thing they're looking at. by keeping track of what tag they're viewing in an atom that holds all the user-ids , the swap! is instrumental

sova-soars-the-sora02:12:50

well, more like :tag [user ids] to be clear ...

sova-soars-the-sora02:12:25

Hmmm, I want to add to a vector though, not just have one user-id per tag

sova-soars-the-sora02:12:03

append instead of overwrite scrambles to clojuredocs

noisesmith02:12:08

then (swap! a update :tag conj uid)

noisesmith02:12:23

but if you also remove uids from tags, it makes more sense to use a set

sova-soars-the-sora02:12:20

Tell me more about sets

noisesmith02:12:47

(defn add-id [a tag id] (swap! a update tag (fnil conj #{}) id)) (defn remove-id [a tag id] (swap! a update tag disj id))

noisesmith02:12:59

with sets you can call disj to remove a single element by value

sova-soars-the-sora02:12:23

oh awesome. that's exactly what I was looking for

noisesmith02:12:55

and to check for membership, you can either use the set as a function, or use contains?

sova-soars-the-sora02:12:34

(fnil ) is new to me

sova-soars-the-sora02:12:43

seems very useful and flexible .

noisesmith02:12:05

Clojure 1.9.0
+user=> ((fnil conj #{}) nil 1)
#{1}
+user=> ((fnil conj #{}) #{2} 1)
#{1 2}
+user=> ((fnil conj #{}) [] 1)
[1]

noisesmith02:12:28

yes, fnil and update are a useful combination

sova-soars-the-sora02:12:03

so in the example above, is a an (atom #{} )

noisesmith02:12:29

it's (atom {}) - wouldn't really work the same if the atom was a set

sova-soars-the-sora02:12:01

Oh right, but each tag within it has a val that is represented by a set

noisesmith02:12:10

the code is much simpler if the atom is a set, just (swap! a conj x) or (swap! a disj x)

sova-soars-the-sora02:12:38

Do you think it makes more sense to create a bunch of atoms, one for each keyword I have, and use the simplified code to add/remove uids that are viewing that tag, or try and mash them all into one tag-viewers atom?

noisesmith02:12:42

the problem with multiple atoms is it makes adding tags at runtime awkward

sova-soars-the-sora02:12:33

Ah yeah that makes sense.

noisesmith02:12:51

if that's something you'll never need, I guess you could consider it - but I think having one item with all the tags as keys in a hashmap would be easier

sova-soars-the-sora02:12:10

Hashmap. Have yet to use one in clojure.

sova-soars-the-sora02:12:14

wait isn't a normal map in clojure a hash map?

noisesmith02:12:38

yes - I prefer the term hashmap because we have the function map which is totally unrelated

noisesmith02:12:43

it means the same thing

sova-soars-the-sora02:12:52

Ah, thanks for the clarification.

sova-soars-the-sora02:12:26

Yes, one atom with many tags as Keys and vectors of connected user-ids as vals

noisesmith02:12:05

I mean on an abstract mathematical level both a hashmap and the map function describe a set of relations from one set of values to another, where each item in the first set maps to exactly one item in the second set

noisesmith02:12:18

but that's not the level we code on, that's just a formalism 😄

sova-soars-the-sora02:12:59

Right! i am not a run-time compiler that cannot tell the difference haha

sova-soars-the-sora02:12:21

Okay I have a question. ( add-id [atom tag id) makes sense

sova-soars-the-sora02:12:38

when a user disconnects, I don't really have the pleasure of knowing which tag they were looking at, I just know they disconnected and their id

sova-soars-the-sora02:12:47

how can I get rid of every instance of their Id in the map

noisesmith02:12:53

oh - that's trickier isn't it

noisesmith02:12:03

you could use (swap! a #(into {} (map (fn [[k v]] [k (disj v id)])) %))

sova-soars-the-sora02:12:10

beautiful. could you give me some commentary on what that is doing?

noisesmith02:12:39

into takes three args here - a collection {}, a transducer (map ...) and the original

noisesmith02:12:02

it uses the transducer on each item in the original collection to generate the value to put in the result

noisesmith02:12:56

it's very similar to calling map on the hash-map and then putting the result into a new hash-map, but unlike using map that way it doesn't need to make a lazy-seq

noisesmith02:12:43

+user=> (into #{} (map inc) #{1 2 3 4})
#{4 3 2 5}

noisesmith02:12:20

+user=> (into #{} (comp (map inc) (filter even?)) #{1 2 3 4})
#{4 2}

sova-soars-the-sora02:12:49

so for the line (swap! a #(into {} (map (fn [[k v]] [k (disj v id)])) %)) i can just supply id and the rest is known

sova-soars-the-sora02:12:08

that's awesome and groovy.

noisesmith02:12:35

clojure has some nice ways to work with data, once you get into the flow it can be addictive

sova-soars-the-sora02:12:37

i noticed you used (comp) in the one

noisesmith02:12:17

yeah - it was to show how you can combine transducing functions (and subtly it shows that they compose differently than normal function application because ... reasons)

sova-soars-the-sora03:12:28

cool. that was a fairly painless introduction into transducers ^.^

sova-soars-the-sora03:12:59

Dude thanks so much

sova-soars-the-sora03:12:26

Now I just have to make sure every connected user gets a default id of some sort instead of nil

alexlykos11:12:05

Hi, I am calling a function from a clojure library which in turn calls a java method from a java library. The problem is instead of getting the result I get a #object[.... , how I instead get the actual result? So a bit more details: In the beginning of the file I do a (:require qbits.alia.timestamp-generator :refer :all), then I run (atomic-monotonic), and I get: #object[com.datastax.driver.core.AtomicMonotonicTimestampGenerator 0x6ba19932 com.datastax.driver.core.AtomicMonotonicTimestampGenerator@6ba19932], the library function is this: https://github.com/mpenet/alia/blob/master/modules/alia/src/qbits/alia/timestamp_generator.clj#L8

alexlykos11:12:40

It worked! Thank you very much! @ghsgd2

New To Clojure14:12:55

Is there do..while loop in Clojure?

do {
  n *= counter--;
} while (counter > 0);

manutter5114:12:14

I’d use looprecur for that, assuming there wasn’t a more functional alternative that eliminated the loop completely

rdoering14:12:44

This seems to compute something like n*(counter!), have a look at https://gist.github.com/akonring/7804273

New To Clojure14:12:44

@manutter51, @pcbalodi, @ralf Thank you! @pcbalodi I need to do check after executing body, not before so it's not exactly while.

madstap15:12:30

There's probably a nicer, functional way of doing what you want without an explicit loop. But do-while is an easy macro to write.

madstap15:12:41

(defmacro do-while [test & body]
  '(loop []
     ~@body
     (when ~test
       (recur))))

madstap15:12:13

Wow, jinxed

New To Clojure15:12:01

@U0J9LVB6G

CompilerException java.lang.RuntimeException: Unable to resolve symbol: body in this context

mdrago102615:12:16

@ghsgd2 I don’t think there are any built-in constructs, but this answer on SO suggests (https://stackoverflow.com/questions/8675911/do-while-loop-in-clojure)

(defmacro do-while
  [test & body]
  `(loop []
     ~@body
     (when ~test
       (recur))))

mdrago102615:12:44

which is very similar to the actual while macro

leonoel15:12:13

(while (do body condition))

New To Clojure15:12:52

@leonoel Useful, but I need to return result value (body). I'm using this hack currently:

(letfn [(body [] body-expr)]
    (loop [result (body)]
        (if (not condition)
            result
            (recur (body)))))

Prakash15:12:17

u can do something like this -

(-> (repeatedly body)
      (drop-while condition)
      (first))

Prakash15:12:58

but I am not sure if this is the idiomatic way to do this

ssansovich19:12:08

Is there an accepted way to handle errors from network requests in the context of an application (I don’t mean at the function level). Is it “better” to throw an exception and let it propagate up the call stack, have each function catch/throw independently, return error maps (`{:error :not-found}`) and then check them in the calling function, or something else entirely?

ghadi21:12:17

exceptions are ok with some principles around them

ghadi21:12:36

they are a way of life in java and you don't make a top-level scope aware of specific low-level exception types... Generally catch your exceptions as close to the source as possible and transform your exceptions to error values

ghadi21:12:00

using an ordinary map or a data-bearing exception ex-info

ghadi21:12:48

this is a really good question

ghadi21:12:22

Exceptions aren't huge in the clojure space like in python, but are just a fact of life because JVM

ssansovich21:12:34

Whoops sorry I didn’t see this earlier - thanks for the response!

ssansovich21:12:32

“Generally catch your exceptions as close to the source as possible and transform your exceptions to error values” effectively gets to the heart of what I was asking. Thank you.

ssansovich21:12:31

To confirm, does that mean a lot of your code ends up looking something like this?

(let [result (make-api-call "/users")]
  (if (:error result)
    (response/bad-request)
    (response/ok result)))

hawari02:12:50

I would like to know more about this as well, as of now, I let all exceptions be thrown in the "inner layer" of the code (ex. code that send HTTP request, database query, etc). Leaving it to the "outer layer" to catch it (in this case a ring middleware) and compose an error response. Is there a downside to this? I've found that it makes things easier to me, as I don't have to make try catch block in each function that can throw an exception. But I'm not sure if it will ultimately become a nuisance or potentially even a problem in the future.

New To Clojure20:12:09

Have unusual issue. The same code returns correct result when run under CIDER debug but returns just [] when run without debug. Code: https://repl.it/repls/DefensiveGlisteningFrillneckedlizard

noisesmith20:12:01

@ghsgd2 there’s a few problems with that code, but the main issue is that for is lazy and does nothing if nothing consumes the lazy-seq it generates. When you have debugging on the debugger consumes the values for is generating, so it accidentally works.

noisesmith20:12:26

you can fix the immediate problem by replacing for with doseq, it’s the same syntax but actually does the thing you want

New To Clojure20:12:57

@noisesmith Got it! Guess the other issue it's non-tail recursion which could overflow stack. But that's only the 1st version of code. Thank you very much, @noisesmith!

noisesmith20:12:46

that’s one issue, another one is that the usage of a mutable state here is gratuitous, and that definitions in that order don’t actually compile the first time because you can’t use forward-references

noisesmith20:12:51

also, as a minor thing, #(conj % %2) can always be replaced with conj

dpsutton20:12:14

there's a race condition in it

dpsutton20:12:23

the debugger makes one runner of the race always win

noisesmith20:12:51

where is the race?

noisesmith20:12:04

I don’t see anywhere a second thread would be introduced

noisesmith20:12:19

oh - the deref could happen before the swap! completes right

dpsutton20:12:21

nevermind. i was thinking that it would immediately return the @result without "waiting" for the other function to complete

New To Clojure20:12:42

@noisesmith >the usage of a mutable state here is gratuitous Going to replace with volatile >definitions in that order don’t actually compile the first time Yeah, will use declare. That's because I develop mostly in REPL. >`#(conj % %2)` can always be replaced with conj Indeed, haven't noticed.

noisesmith20:12:49

yeah, it always waits for swap! - there’s no way for it to go out of sync

dpsutton20:12:02

even when those are "done" inside of a for loop?

noisesmith20:12:08

@ghsgd2 volatile is still mutable, it’s just faster

noisesmith20:12:28

@dpsutton there’s no such thing as a for loop, for is a comprehension, and it never uses a new thread

noisesmith20:12:53

either the for executes and the swap! is waited for, or it doesn’t execute and there’s no swap! to wait for

noisesmith20:12:01

well you could make it happen in another thread, but it won’t make a new thread on its own behalf

dpsutton20:12:18

(defn flatten-1
  [items result]
  (for [element items]
    (if (sequential? element)
      (flatten-1 element result)
      (do
        (prn "i'm doing something")
        (swap! result #(conj % %2) element)))))

dpsutton20:12:32

when you call the original flatten you never see the "i'm doing something".

dpsutton20:12:22

but i'm conflating race and lazy and i think you already pointed this out didn't you

noisesmith20:12:50

yes - laziness is thread safe, the error here is expecting anything lazy to happen for side effects

New To Clojure20:12:16

That's better version. But it looks like cheating because postwalk does all heavy-lifting (and also there's postwalk default result sequence which is ignored but still consumes memory during computation). I wonder how it's implemented but I'll keep inventing my own versions for now.

(defn flatten
  [elements]
  (let [result (volatile! [])]
    (clojure.walk/postwalk (fn [x]
                             (if (not (sequential? x))
                               (vswap! result conj x)))
                           elements)
    @result))

noisesmith20:12:45

that will act weird if you have sets or hash-maps in the input

noisesmith20:12:45

you can use tree-seq and filter to do this without mutation and avoid odd behaviors with nested non-sequential data (if you pick the right predicate on tree-seq)

New To Clojure20:12:26

@noisesmith (tree-seq branch? children root) tree-seq needs root. I have none.

noisesmith20:12:52

“root” is just your input

noisesmith20:12:30

branch? is a query that decides if you can go deeper, children turns the tree into something you can recurse on, and root is the current input

noisesmith20:12:50

so (tree-seq sequential? seq elements)

New To Clojure21:12:59

@noisesmith It could be updated to handle sets and maps as well

(fn [x] ;; postwalk 1st arg
    (if (not (or (sequential? x) (set? x) (map? x)))
        (vswap! result conj x)))
And then all numbers and keys will be returned in one vector. With your advice (maps actually are ignored but that's not an issue here):
user> (filter #(not (or (sequential? %) (set? %) (map? %))) (for [elt (tree-seq sequential? seq [1 [2 [[3]] [4 [[5]]] 6 7] 8 {:key 9} ])] elt))

(1 2 3 4 5 6 7 8)

noisesmith21:12:41

you don’t need for here

noisesmith21:12:09

and you can use (complement coll?) or #(not (coll? %)) as a predicate to filter

noisesmith21:12:14

or better yet use remove coll?

noisesmith21:12:10

@ghsgd2 the reason I say you don’t need for (for [x coll] x) is just (seq x)

noisesmith21:12:33

and your tree-seq is already calling seq, so you don’t need that explicitly either

noisesmith21:12:54

which leaves us with (remove coll? (tree-seq coll? seq input))

noisesmith21:12:11

(if you want that 9 from inside the hash-map that is)

noisesmith21:12:18

if you don’t want the 9, (remove coll? (tree-seq sequential? seq input))

New To Clojure21:12:32

@noisesmith Impressive! 🙂 The only caveat here it's that tree-seq uses non-tail recursion inside so it will overflow stack on big trees.

noisesmith21:12:47

yes, everything that consumes trees including post-walk does that

noisesmith21:12:00

for clojure built ins at least

noisesmith21:12:50

you can move the stack usage into heap usage but you end up with slower code on the normal case, and more complex code to boot (basically using continuations on the heap instead of stack data)

noisesmith21:12:41

@ghsgd2 also I think the fact that the self-calls are wrapped in lazy-seq means that this already happens with tree-seq, because lazy-seq lifts the continuation into a function on the heap internally

noisesmith21:12:51

I’d need to double check that claim though

noisesmith21:12:18

yes, checking the source - postwalk isn’t lazy, so it will overflow with large inputs, but tree-seq is creating a lazy-seq at each step so if consumed properly won’t overflow

noisesmith21:12:44

the embedded mapcat may or may not risk a concat-bomb for very large inputs though…

New To Clojure21:12:22

Thank you very much, @noisesmith! Good to know how it works.

noisesmith21:12:03

it always feels nice to replace many lines of mutating code with a small one liner

New To Clojure21:12:45

Is it a good idea to use clojure.zip at all? Considering what's written in http://z.caudate.me/why-clojure-zip-sucks/

shaun-mahood21:12:17

What are you trying to do with them? I think there's often abstractions built on top of them that can be used directly.

shaun-mahood21:12:07

For understanding, I found the videos at https://tbaldridge.pivotshare.com/categories/zippers/1999/media really valuable to help me start figuring them out

New To Clojure21:12:49

@U054BUGT4 In this case I'm exploring what Clojure has to be prepared for upcoming tasks (I'm solving tasks at http://exercism.io).

New To Clojure21:12:08

Thank you a lot!

shaun-mahood21:12:59

Personally, I wouldn't focus too much on zippers until you have a need for them (or just get the basic understanding and move on). There are pretty useful things that you can do with them though, I just haven't found it to be that common for me to need them directly.

noisesmith21:12:08

considering other things zacaudate has said publicly I would seek a second opinion in general

noisesmith21:12:18

(I’m not familiar with that article though)

schmee21:12:01

I tried zippers a couple of time and I find them very hard to use

New To Clojure21:12:04

he said that if there's a tree [[1 2 3] []] zipper can't put cursor into 2nd empty list to add some elements

New To Clojure21:12:20

because cursor sticks to elements

New To Clojure21:12:32

not to positions inside tree

schmee21:12:04

to me zippers are an overly complicated way to do things in order to remain functional, but since clojure has mutability, we can use that instead

schmee21:12:32

but try them out and see if they work for you 🙂

Drew Verlee21:12:23

When dealing with very data driven basis, how does on go about understanding there options? Like, if I'm too pass a function a map, how do you know the options for that map? I guess the ideal thing is a spec or schema right?

ghadi21:12:00

schema spec

dpsutton21:12:06

his argument is

1. Lack of guard rails - nil punning for most movements out of the zipper as well as using :end as end.
2. Viewing the cursor as 'on an object in the tree' as opposed to 'within the tree'.

13tales22:12:12

Hey there. I have a newbie question that I’d love some help with: what’s the best idiomatic way to instantiate a Java class with a default constructor, and then invoke one of its methods? I’d like to use a method from the Apache Commons Math library as the first argument to the map function, but it’s not static, so I have to instantiate the class first. Just wondering how best to do this.

shaun-mahood22:12:38

This may not be the best way but it's what I've picked up trying to answer the same question (these are from recent interop code) For functions that return a value,

(-> (ReportClientDocument.)
    (.getDatabaseController))
should create a new ReportClientDocument, call the getDatabaseController function, and return the result of that method call. To update the instance and return it,
(doto (ReportClientDocument.)
    (.setReportAppServer ReportClientDocument/inprocConnectionString))
should create a ReportClientDocument, call setReportAppServer, and return the updated instance.

shaun-mahood22:12:38

You can also do

(def my-report (ReportClientDocument.))
or equivalent and use it from there

13tales22:12:36

(If I want to do this, I also have to wrap the Java method invocation in a lambda, right?)

timgilbert22:12:53

You might also try something like:

(defn my-fn [xs]
  (let [inst (Math/Whatever.)]
    (map #(.method inst %1) xs)))