0

I've got a simple to-float function:

(defn to-float [v] (cond
                     (nil? v) nil
                     (string? v) (Float/parseFloat v)
                     :else (float v)))

And a record:

(defrecord Product [Id UseRecommendation ReferenceProductId OnePrice TotalPrice
                    ManualTotalPrice MarketTotalPrice PreviousTotalPrice PurchaseTotalPrice
                    Tax Deposit RuleId RoleId PrimaryProduct Size])

For some of the keys(?) I wish to make sure they are float before passing them to map->Product (constructor?). Sorry I'm not familiar with the terminology here.

Anyway, my first solution was very verbose:

(defn init->Product [args]
  (let [converted-args (assoc args
                         :TotalPrice         (to-float (:TotalPrice args))
                         :ManualTotalPrice   (to-float (:ManualTotalPrice args))
                         :MarketTotalPrice   (to-float (:MarketTotalPrice args))
                         :PreviousTotalPrice (to-float (:PreviousTotalPrice args))
                         :PurchaseTotalPrice (to-float (:PurchaseTotalPrice args))
                         :Tax                (to-float (:Tax args))
                         :Deposit            (to-float (:Deposit args)))]
    (map->Product converted-args)))

Second one was better but has lots of "noise" in terms of apply, merge, hash-map and so forth:

(defn init->Product [args]
  (let [apply-float (fn [keys] (merge args (apply merge (map #(hash-map % (to-float (% args))) keys))))]
    (map->Product (apply-float '(:TotalPrice :ManualTotalPrice :MarketTotalPrice :PreviousTotalPrice :PurchaseTotalPrice :Tax :Deposit)))))

The third solution uses recursion, which isn't easy to comprehend for people who are new to Clojure either:

(defn init->Product [args-orig]
  (let [apply-float (fn [args keys] (if (empty? keys) args (let [key (first keys)] (recur (assoc args key (to-float (key args))) (rest keys)))))]
    (map->Product (apply-float args-orig '(:TotalPrice :ManualTotalPrice :MarketTotalPrice :PreviousTotalPrice :PurchaseTotalPrice :Tax :Deposit)))))

Can you think of something simpler? I have a feeling there exists an utility function I'm missing here, or I fail to see how reduce or some other could be used.

Edit: This is more like how I would do it in Python

(defn init->Product [args-orig]
  (let [apply-float (fn [args keys] (merge args (into {} (map (fn [key] [key (to-float (key args))]) keys))))]
    (map->Product (apply-float args-orig '(:TotalPrice :ManualTotalPrice :MarketTotalPrice :PreviousTotalPrice :PurchaseTotalPrice :Tax :Deposit)))))
NikoNyrh
  • 3,578
  • 2
  • 18
  • 32

1 Answers1

3

Instead of (assoc m :x (f (:x m))) you may use (update m :x f). So, I would rewrite your init->Product function as follows:

(defn init->Product [args]
  (let [converted-args (reduce #(update %1 %2 to-float)
                               args
                               [:TotalPrice
                                :ManualTotalPrice
                                :MarketTotalPrice
                                :PreviousTotalPrice
                                :PurchaseTotalPrice
                                :Tax
                                :Deposit])]
    (map->Product converted-args)))
OlegTheCat
  • 4,443
  • 16
  • 24
  • Shouldn't `convert-args` actually be `converted-args`? Also, can't you just use `(keys args)` instead of `[:TotalPrice :ManualTotalPrice :MarketTotalPrice :PreviousTotalPrice :PurchaseTotalPrice :Tax :Deposit]`? – Sam Estep Jul 01 '16 at 10:52
  • I assumed that `to-float` should be called only on some subset of `(keys args)`. Look at the definition of `Product` record: there are fields that are not converted to float. – OlegTheCat Jul 01 '16 at 11:00
  • Ah, I missed that. Carry on, then. :) – Sam Estep Jul 01 '16 at 11:09
  • Great, thanks! That is more like what I had in mind, `update` made this simple to implement. – NikoNyrh Jul 01 '16 at 11:57