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"
)
Top comments (0)