DEV Community

Cover image for Clojure Is Awesome!!! [PART 22]
André Borba
André Borba

Posted on

Clojure Is Awesome!!! [PART 22]

What If DRY? 👀

(ns clojure-is-awesome.dry
  (:require [clojure.spec.alpha :as s]
            [clojure.string :as str]))

(s/def ::id string?)
(s/def ::amount (s/and number? pos?))
(s/def ::date #(instance? java.time.LocalDate %))
(s/def ::category #{"income" "expense"})
(s/def ::description (s/and string? #(not (str/blank? %))))
(s/def ::transaction (s/keys :req-un [::id ::amount ::date ::category ::description]))
(s/def ::transactions (s/coll-of ::transaction :kind vector))

(defn create-transaction
  "Creates a transaction with centralized spec validation."
  [id amount date category description]
  (let [transaction {:id id :amount amount :date date :category category :description description}]
    (if (s/valid? ::transaction transaction)
      transaction
      (throw (ex-info "Invalid transaction" {:errors (s/explain-data ::transaction transaction)})))))

(defn calculate-by-category
  "Calculates totals for a given category."
  [transactions category]
  (->> transactions
       (filter #(= (:category %) category))
       (map :amount)
       (reduce + 0)))

(defn format-transaction
  "Formats a transaction for display."
  [transaction]
  (str (.toString (:date transaction)) " - " (:category transaction) " - " (:description transaction) ": $" (:amount transaction)))

(defn generate-report
  "Generates a financial report with reusable, composable functions."
  [transactions]
  {:pre [(s/valid? ::transactions transactions)]}
  (let [income (calculate-by-category transactions "income")
        expenses (calculate-by-category transactions "expense")
        balance (- income expenses)
        formatted-transactions (map format-transaction transactions)]
    (str "Financial Report\n"
         "Total Income: $" income "\n"
         "Total Expenses: $" expenses "\n"
         "Balance: $" balance "\n"
         "Transactions:\n"
         (str/join "\n" formatted-transactions))))

(comment
  (def sample-transactions
    [(create-transaction "t1" 1000 (java.time.LocalDate/of 2025 7 1) "income" "Salary")
     (create-transaction "t2" 200 (java.time.LocalDate/of 2025 7 2) "expense" "Groceries")
     (create-transaction "t3" 500 (java.time.LocalDate/of 2025 7 3) "income" "Freelance")
     (create-transaction "t4" 150 (java.time.LocalDate/of 2025 7 4) "expense" "Utilities")])

  (calculate-by-category sample-transactions "income")  ;; => 1500
  (calculate-by-category sample-transactions "expense") ;; => 350
  (generate-report sample-transactions)
  ;; => "Financial Report\nTotal Income: $1500\nTotal Expenses: $350\nBalance: $1150\nTransactions:\n2025-07-01 - income - Salary: $1000\n2025-07-02 - expense - Groceries: $200\n2025-07-03 - income - Freelance: $500\n2025-07-04 - expense - Utilities: $150"
)
Enter fullscreen mode Exit fullscreen mode

Top comments (0)