Bill O'Brien
Bill O'Brien

Reputation: 872

Clojure: delete item from a ref?

I created the ref "people" below, and want the function delete-person to delete an item from the data structure within a transaction.

(defrecord Person [name favorite-color])

(def people (ref ()))

(defn add-person [new-person]
  (dosync
    (alter people conj new-person)))

(add-person (Person. "Joe" "Red"))
(add-person (Person. "Sam" "Blue"))

;; how do I make this function work? 
;; would @people (destructured) have to be the second argument to filter? 
(defn delete-person [name-to-delete]
  "Delete named person from ref"
  (dosync
    (alter people filter #(not= (:name %) name-to-delete))))

(delete-person "Joe")

IllegalArgumentException Don't know how to create ISeq from: 
user$delete_person$fn__1407$fn__1408  clojure.lang.RT.seqFrom (RT.java:542)

The function below works because I filter on the destructured ref, but how do I do it in a transaction to mutate the data?

   (filter #(not= (:name %) "Sam") @people)
=> (#user.Person{:name "Joe", :favorite-color "Red"})

Upvotes: 1

Views: 88

Answers (1)

Carcigenicate
Carcigenicate

Reputation: 45742

As there error says, you're trying to iterate a function. This is coming about because when you write:

(alter people filter #(not= (:name %) name-to-delete))

The unwrapped people becomes the first argument to filter, not the last.

You'll need to use an full fn, or use partial:

(alter people
    (fn [ps] (filter #(not= (:name %) name-to-delete) ps)))

Or

(alter people
    (partial filter #(not= (:name %) name-to-delete))) 

These make alter pass the unwrapped people as the last argument to filter, instead of implicitly as the first.


I'll note though:

  • As @cfrick brought up in the comments, using lazy sequences in a transaction may have the potential to cause problems. I can't offhand think of a scenario where it would, but it feels wrong. It could be argued that the realization of a lazy sequence is a side effect, and side effects shouldn't take place in a transaction, since transactions may run multiple times in the event of a conflict. Multiple realizations shouldn't cause a problem, but I can't say definitively that it's safe (honestly, I never use refs).

  • Make sure you actually need refs and transactions here. Transactions are for when you need to sequence multiple alterations to data, and need to be able to catch when the data involved has been changed part way through a transaction. If you just need a simple mutable container though, atom are much simpler.

Upvotes: 4

Related Questions