Fork me on GitHub
#beginners
<
2017-02-13
>
olessavluk00:02:50

Hi there! I’m reading Aphyr's post - https://aphyr.com/posts/306-clojure-from-the-ground-up-state And have a problem with the exercise #5 (at the bottom of the page). Here is my current solution: https://gist.github.com/olessavluk/efcd425cda7dc37eff991ba45d34e7c3 I’m getting NullPointerException with it. But, when I replace future with delay it work fine. Answer is correct in both cases.

olessavluk00:02:19

Any ideas why do I get NullPointerException when using future and do not have it when using delay?

Alex Miller (Clojure team)03:02:59

you are reading @work outside the dosync transaction so it could be emptied by the time you enter the transaction. In that case, (ensure work) returns nil, then first will return nil, and then + will throw an NPE - is that what you’re seeing?

olessavluk09:02:12

Thank you, Alex! You are right about the problem cause

olessavluk10:02:28

Also wondering is it ok to execute recur inside the dosync? like this:

(defn do-work []
  "Take the first element of `work` and add it to `sum`, until all of the `work` is done(empty)"
  (dosync
   (when-not (empty? @work)
     (alter sum + (first (ensure work)))
     (alter work rest)
     (recur))))
Does that mean that the transaction will be started on the first function call and terminated only after the last call? Does this have some performance problems?

ordnungswidrig10:02:22

olessavluk: there might be a minimal performance overhead. You should be able to move the dosync out of the recursion reasily

tbaldridge13:02:44

@ordnungswidrig I disagree, every time you go into a dosync you have to re-lock all the refs.

tbaldridge13:02:00

but of-course, measure first, then decide

ordnungswidrig13:02:14

Oh really? I thought it would only check for an existing lock but you’re right, the scope can be different.

olessavluk13:02:39

hm, interesting. but how can I move recur out of dosync to test this?

tbaldridge13:02:47

The only problem with doing more work inside a dosync (transaction) is that you have to throw out more work in the case of a conflict

tbaldridge13:02:35

but summing a few integers is likely to be a small enough amount of work that you might as well just do it that way

olessavluk13:02:33

but call to recur mean that there will be a conflict with a 100% probability, isn't it?

ordnungswidrig13:02:59

I think that totally depends on the kind of work done

tbaldridge13:02:32

no, conflicts happen with other threads, you only have one thread here

tbaldridge13:02:54

this all being said, STM is very rarely used in production Clojure code. 99.99% of the time the better route is a atom:

tbaldridge14:02:17

(atom {:sum 42 :work [1 2 3]})

olessavluk14:02:36

yes, but this is just an exercise to better understand how the refs works and when to use them or atoms

tbaldridge14:02:54

that's kindof the problem, there isn't really one

tbaldridge14:02:37

understanding them is fine, I'm just saying don't worry if you don't "get" them right away. I've been programming Clojure professionally for almost 5 years, and I've never seen them in use, or at least in a use that required them.

olessavluk14:02:29

hm, I thought this is one of the coolest things in clojure so most of the time atoms are enough or you don't even need to share the memory for writing?

olessavluk14:02:45

> no, conflicts happen with other threads, you only have one thread here I’m running this function in two threads:

(defn calc []
  "Calculate `sum` using two workers"
  (let [a (future (do-work))
        b (future (do-work))]
    @a @b)
  @sum)
(full code - https://clojurians.slack.com/archives/beginners/p1486947230008234) So in my case I will calculate every transaction two (or even more times) because I have recur which will modify refs inside a dosync? Is that correct?

jeffh-fp14:02:37

I would guess that one thread would get done first, update sum and work, and then the other thread would retry with an empty work and basically do nothing. Is that what you're seeing?

schmee14:02:02

I feel like the Clojure STM is underrated, but on the other hand I’ve never encountered a situation where an atom didn’t suffice either ¯\(ツ)

tbaldridge16:02:44

Yea, it is underrated, but I see it as a example of how much leverage you can get out of other models, CAS (atoms) + immutable data are just so powerful and simple, they often trump other more complicated methods.

tbaldridge16:02:58

The same applies to CSP (core.async), agents, actors, etc.

tbaldridge16:02:41

Never underestimate the performance you can get out of a single thread when it is properly optimized, and unconstrained cross-thread synchronization.

mss23:02:59

having some trouble thinking through a java interop issue, would love some input. newish to the jvm so pardon me for working out the kinks in my mental model. I need to implement an instance that satisfies an interface with a single method. this instance will then be passed to a method that expects a java.lang.Class. in an ideal world I’d love to be able to pass the body of the function in at instance creation time, as opposed to specifying it in advance I initially reached for invoking reify, but it seems like reify doesn’t actually return a class instance. if there’s a way to cast it to a class that would be ideal. based on https://cemerick.com/2011/07/05/flowchart-for-choosing-the-right-clojure-type-definition-form/ I then figured defrecord would be the ideal function to use. my q is: can I avoid creating a defrecord for each invocation of a different function body? in a situation like:

(defn make-an-instance [my-func]
  (defrecord MyRecord []
    my.interface
    (interface-method [this]
       (my-func))))
I would assume that each invocation of make-an-instance creates a new MyRecord object that won’t get garbage collected – I hope I’m wrong about that. would appreciate any insight that anyone can provide