Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsBlogPro
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges
  • Chrome Extension
  • Skill Manager

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Convert Python Clojure

ASecurity

Convert Python code to idiomatic Clojure. Use when migrating Python projects to Clojure, translating Python patterns to idiomatic Clojure, or refactoring Python codebases to leverage functional programming. Extends meta-convert-dev with Python-to-Clojure specific patterns.

8 stars
0 votes
0 copies
0 views
Added 2/8/2026
developmentpythongojavasqlexpressfastapiflasktestingrefactoringapi

Works with

cliapi

Security Analysis

A100/100

Scanned 2/10/2026

$npx -y skills add aRustyDev/ai --skill convert-python-clojure --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Convert Python Clojure?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Convert Python Clojure
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/arustydev-convert-python-clojure/badge)](https://www.skillsdirectory.com/skills/arustydev-convert-python-clojure)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
Files
SKILL.md
---
name: convert-python-clojure
description: Convert Python code to idiomatic Clojure. Use when migrating Python projects to Clojure, translating Python patterns to idiomatic Clojure, or refactoring Python codebases to leverage functional programming. Extends meta-convert-dev with Python-to-Clojure specific patterns.
---

# Convert Python to Clojure

Convert Python code to idiomatic Clojure. This skill extends `meta-convert-dev` with Python-to-Clojure specific type mappings, idiom translations, and tooling for transforming imperative, object-oriented Python code into functional, immutable Clojure.

## This Skill Extends

- `meta-convert-dev` - Foundational conversion patterns (APTV workflow, testing strategies)

For general concepts like the Analyze → Plan → Transform → Validate workflow, testing strategies, and common pitfalls, see the meta-skill first.

## This Skill Adds

- **Type mappings**: Python types → Clojure types (dynamic → dynamic with immutability)
- **Idiom translations**: Python patterns → idiomatic Clojure (OOP → functional)
- **Error handling**: Exceptions → explicit error values
- **Async patterns**: asyncio → core.async
- **Data structures**: Mutable collections → immutable persistent collections
- **REPL workflow**: Script-based → REPL-driven development

## This Skill Does NOT Cover

- General conversion methodology - see `meta-convert-dev`
- Python language fundamentals - see `lang-python-dev`
- Clojure language fundamentals - see `lang-clojure-dev`
- Reverse conversion (Clojure → Python) - see `convert-clojure-python`
- ClojureScript - see `convert-python-clojurescript`

---

## Quick Reference

| Python | Clojure | Notes |
|--------|---------|-------|
| `int` | `int` / `long` / `BigInt` | Clojure has arbitrary precision |
| `float` | `double` | 64-bit floating point |
| `bool` | `true` / `false` | Direct mapping |
| `str` | `String` | Java strings (immutable) |
| `bytes` | `byte-array` | Mutable byte array |
| `list[T]` | `[...]` | Vector (indexed, immutable) |
| `tuple` | `[...]` or `(list ...)` | Vector or list |
| `dict[K, V]` | `{:key val}` | Hash map (immutable) |
| `set[T]` | `#{...}` | Hash set (immutable) |
| `None` | `nil` | Absence of value |
| `class` | `defrecord` / `deftype` | Data structures |
| `def func():` | `(defn func [] ...)` | Function definition |
| `lambda x: x*2` | `#(* % 2)` or `(fn [x] (* x 2))` | Anonymous function |
| `for x in xs:` | `(doseq [x xs] ...)` | Side-effecting iteration |
| `[x for x in xs]` | `(for [x xs] ...)` | Lazy sequence comprehension |
| `try/except` | `(try ... (catch ...))` | Exception handling |
| `async def` | core.async channels | Different concurrency model |

## When Converting Code

1. **Analyze source thoroughly** before writing target
2. **Map types first** - Python and Clojure are both dynamic but Clojure emphasizes immutability
3. **Embrace immutability** - replace in-place mutations with functional transformations
4. **Use REPL-driven development** - Clojure is designed for interactive development
5. **Adopt functional idioms** - avoid classes, prefer pure functions and data transformations
6. **Handle edge cases** - None→nil, exceptions, mutable state
7. **Test equivalence** - same inputs → same outputs

---

## Type System Mapping

### Primitive Types

| Python | Clojure | Notes |
|--------|---------|-------|
| `int` | `int` / `long` | Automatic promotion to BigInteger |
| `float` | `double` | 64-bit IEEE 754 |
| `bool` | `true` / `false` | Lowercase boolean literals |
| `str` | `String` | Java.lang.String (immutable) |
| `bytes` | `byte-array` | Mutable Java byte array |
| `bytearray` | `byte-array` | Same as bytes in Clojure |
| `None` | `nil` | Represents absence |
| `...` (Ellipsis) | - | No equivalent |

**Note on Integers**: Both Python and Clojure support arbitrary precision integers. Clojure automatically promotes from `long` to `BigInteger` on overflow.

### Collection Types

| Python | Clojure | Notes |
|--------|---------|-------|
| `list[T]` | `[...]` | Vector - indexed, immutable, O(log32 n) |
| `tuple[T, U]` | `[...]` | Vector (immutable by default) |
| `tuple[T, ...]` | `(list ...)` | List for sequential access |
| `dict[K, V]` | `{:key val}` | Hash map - immutable, O(log32 n) |
| `set[T]` | `#{...}` | Hash set - immutable |
| `frozenset[T]` | `#{...}` | Sets are immutable by default |
| `collections.deque` | `clojure.lang.PersistentQueue` | Immutable queue |
| `collections.OrderedDict` | `(array-map ...)` or `linked-hash-map` | Maintains insertion order |
| `collections.defaultdict` | Map + `get-in` with default | Use `(get m k default)` pattern |
| `collections.Counter` | `frequencies` function | Built-in frequency counter |
| `range(n)` | `(range n)` | Lazy sequence |

### Composite Types

| Python | Clojure | Notes |
|--------|---------|-------|
| `@dataclass` | `defrecord` | Named fields, map-like access |
| `class` (data) | `defrecord` or plain map | Prefer maps for simple data |
| `class` (behavior) | Protocols + `defrecord` | Polymorphism via protocols |
| `typing.NamedTuple` | `defrecord` | Named, typed fields |
| `typing.TypedDict` | Plain map `{:key val}` | Maps with keyword keys |
| `enum.Enum` | Keyword enum | `#{:state/pending :state/done}` |
| `typing.Union[T, U]` | Tagged map or multimethod | `{:type :int :value 42}` |
| `typing.Optional[T]` | `nil` or value | `nil` represents absence |
| `typing.Callable` | `fn` or `IFn` | First-class functions |
| `typing.Protocol` | Clojure protocol | Polymorphism |

---

## Idiom Translation

### Pattern 1: List Comprehensions → Sequence Operations

**Python:**
```python
# List comprehension
squared_evens = [x * x for x in numbers if x % 2 == 0]

# Nested comprehension
pairs = [(x, y) for x in range(3) for y in range(2)]

# Generator expression
total = sum(x * x for x in numbers if x % 2 == 0)
```

**Clojure:**
```clojure
;; for - lazy sequence comprehension
(def squared-evens
  (for [x numbers
        :when (even? x)]
    (* x x)))

;; Nested for - cartesian product
(def pairs
  (for [x (range 3)
        y (range 2)]
    [x y]))

;; Direct transformation with threading
(def total
  (->> numbers
       (filter even?)
       (map #(* % %))
       (reduce +)))
```

**Why this translation:**
- Python list comprehensions are eager; Clojure `for` is lazy (more efficient)
- Clojure's threading macros (`->>`) make pipelines clearer
- `reduce` is idiomatic for aggregation

### Pattern 2: Dictionary Operations → Map Operations

**Python:**
```python
# Get with default
value = config.get("timeout", 30)

# Setdefault pattern
cache.setdefault(key, expensive_compute())

# Dictionary comprehension
squared = {k: v * v for k, v in items.items()}

# Merging dictionaries
merged = {**dict1, **dict2}
```

**Clojure:**
```clojure
;; Get with default
(def value (get config :timeout 30))

;; Lazy computation with update
(def cache (update cache key #(or % (expensive-compute))))

;; Or with caching (using memoize)
(defn get-cached [cache key compute-fn]
  (if-let [v (get cache key)]
    v
    (let [v (compute-fn)]
      (assoc cache key v))))

;; Map transformation
(def squared
  (into {} (map (fn [[k v]] [k (* v v)]) items)))

;; Merging maps
(def merged (merge dict1 dict2))
```

**Why this translation:**
- Clojure maps are immutable; use `assoc`, `update`, `merge` for "changes"
- Keywords (`:timeout`) are idiomatic for map keys
- `into` + `map` is the comprehension pattern for maps

### Pattern 3: Classes → Records and Maps

**Python:**
```python
from dataclasses import dataclass

@dataclass
class User:
    id: int
    name: str
    email: str

    def full_info(self):
        return f"{self.name} ({self.email})"

# Usage
user = User(id=1, name="Alice", email="alice@example.com")
print(user.name)
print(user.full_info())
```

**Clojure:**
```clojure
;; defrecord for structured data
(defrecord User [id name email])

;; Constructor and access
(def user (->User 1 "Alice" "alice@example.com"))
(println (:name user))  ; Map-like access

;; Functions operate on data
(defn full-info [user]
  (str (:name user) " (" (:email user) ")"))

(println (full-info user))

;; Alternative: plain map (often preferred for simple data)
(def user {:id 1 :name "Alice" :email "alice@example.com"})
(println (:name user))
(println (full-info user))
```

**Why this translation:**
- Clojure separates data from behavior (functions operate on data structures)
- `defrecord` provides type identity and performance benefits
- Plain maps are often simpler and more flexible than records
- Functions are defined separately, not as methods

### Pattern 4: Iteration → Sequence Operations

**Python:**
```python
# Imperative loop with mutation
result = []
for item in items:
    if item > 0:
        result.append(item * 2)

# Enumerate
for i, item in enumerate(items):
    print(f"{i}: {item}")

# Zip
for name, age in zip(names, ages):
    print(f"{name} is {age}")
```

**Clojure:**
```clojure
;; Functional transformation (immutable)
(def result
  (->> items
       (filter pos?)
       (map #(* % 2))))

;; map-indexed (like enumerate)
(doseq [[i item] (map-indexed vector items)]
  (println (str i ": " item)))

;; map for pairing (like zip)
(doseq [[name age] (map vector names ages)]
  (println (str name " is " age)))
```

**Why this translation:**
- Clojure favors pure transformations over imperative loops
- `filter`, `map`, `reduce` are core sequence operations
- `doseq` is for side effects (like printing), `for` is for lazy sequences
- `map-indexed` and `map vector` replace Python's enumerate and zip

### Pattern 5: None Handling → nil Handling

**Python:**
```python
# None checks
if user is not None:
    name = user.name
else:
    name = "Anonymous"

# Or with walrus
if (user := get_user(id)) is not None:
    process(user)

# Default value
name = user.name if user else "Anonymous"
```

**Clojure:**
```clojure
;; nil checks with when-let
(when-let [user (get-user id)]
  (process user))

;; Or with if-let
(def name
  (if-let [user user]
    (:name user)
    "Anonymous"))

;; Or with threading (some-> stops on nil)
(def name
  (some-> user :name (str " is here")))

;; Default value with or
(def name (or (:name user) "Anonymous"))
```

**Why this translation:**
- Clojure uses `nil` instead of `None`
- `when-let` and `if-let` bind and test in one step
- `some->` and `some->>` short-circuit on `nil` (like optional chaining)
- `or` returns first truthy value (like Python's `or`)

### Pattern 6: Exceptions → Explicit Error Handling

**Python:**
```python
# Raising exceptions
def divide(a, b):
    if b == 0:
        raise ValueError("Division by zero")
    return a / b

# Catching exceptions
try:
    result = divide(10, 0)
except ValueError as e:
    print(f"Error: {e}")
    result = None

# Finally block
try:
    file = open("data.txt")
    data = file.read()
finally:
    file.close()
```

**Clojure:**
```clojure
;; Throwing exceptions (when appropriate)
(defn divide [a b]
  (if (zero? b)
    (throw (ex-info "Division by zero" {:a a :b b}))
    (/ a b)))

;; Catching exceptions
(def result
  (try
    (divide 10 0)
    (catch Exception e
      (println "Error:" (.getMessage e))
      nil)))

;; with-open for resource cleanup (like Python's with)
(with-open [rdr (clojure.java.io/reader "data.txt")]
  (def data (slurp rdr)))

;; Functional error handling (preferred for non-exceptional cases)
(defn safe-divide [a b]
  (if (zero? b)
    {:error "Division by zero"}
    {:ok (/ a b)}))

(let [result (safe-divide 10 0)]
  (if (:error result)
    (println "Error:" (:error result))
    (println "Result:" (:ok result))))
```

**Why this translation:**
- Clojure uses exceptions for truly exceptional cases
- For expected errors, return maps with `:ok` / `:error` keys (or use a library like `cats` for Either)
- `with-open` ensures resource cleanup (like Python's context managers)
- `ex-info` creates exceptions with data maps

### Pattern 7: Decorators → Macros or Higher-Order Functions

**Python:**
```python
# Function decorator
def logged(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}")
        result = func(*args, **kwargs)
        print(f"Finished {func.__name__}")
        return result
    return wrapper

@logged
def process(data):
    return len(data)

# Property decorator
class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def area(self):
        return 3.14159 * self._radius ** 2
```

**Clojure:**
```clojure
;; Higher-order function (decorator-like)
(defn logged [f]
  (fn [& args]
    (println "Calling" (str f))
    (let [result (apply f args)]
      (println "Finished" (str f))
      result)))

(def process (logged (fn [data] (count data))))

;; Or as a macro (compile-time transformation)
(defmacro defn-logged [name args & body]
  `(defn ~name ~args
     (println "Calling" ~(str name))
     (let [result# (do ~@body)]
       (println "Finished" ~(str name))
       result#)))

(defn-logged process [data]
  (count data))

;; "Property" via function (Clojure has no properties)
(defrecord Circle [radius])

(defn area [circle]
  (* 3.14159 (:radius circle) (:radius circle)))

(def c (->Circle 5))
(println (area c))
```

**Why this translation:**
- Python decorators → Clojure higher-order functions or macros
- Macros run at compile time, can transform syntax
- Clojure has no property syntax; use plain functions
- `defmacro` for code transformation, `defn` for runtime wrapping

### Pattern 8: Object-Oriented → Functional

**Python:**
```python
# Class with state and methods
class Counter:
    def __init__(self):
        self.count = 0

    def increment(self):
        self.count += 1

    def get(self):
        return self.count

counter = Counter()
counter.increment()
counter.increment()
print(counter.get())  # 2
```

**Clojure:**
```clojure
;; Immutable data + pure functions
(defn increment [counter]
  (update counter :count inc))

(defn get-count [counter]
  (:count counter))

;; Usage with threading
(-> {:count 0}
    (increment)
    (increment)
    (get-count)
    (println))  ; 2

;; For mutable state, use atoms
(def counter (atom {:count 0}))

(defn increment! [counter]
  (swap! counter update :count inc))

(defn get-count! [counter]
  (:count @counter))

(increment! counter)
(increment! counter)
(println (get-count! counter))  ; 2
```

**Why this translation:**
- Clojure prefers immutable data + pure functions over mutable objects
- `->` threading macro passes result through function chain
- For necessary state, use atoms (`swap!` for updates, `@` for reads)
- Separate data (maps) from behavior (functions)

### Pattern 9: String Formatting → str and format

**Python:**
```python
# f-strings
name = "Alice"
age = 30
message = f"Hello {name}, you are {age} years old"

# format method
message = "Hello {}, you are {} years old".format(name, age)

# % formatting
message = "Hello %s, you are %d years old" % (name, age)
```

**Clojure:**
```clojure
;; str concatenation
(def name "Alice")
(def age 30)
(def message (str "Hello " name ", you are " age " years old"))

;; format (uses Java String.format)
(def message (format "Hello %s, you are %d years old" name age))

;; Or using clojure.pprint for complex formatting
(require '[clojure.pprint :refer [cl-format]])
(def message (cl-format nil "Hello ~A, you are ~D years old" name age))
```

**Why this translation:**
- `str` concatenates arguments (simple, idiomatic)
- `format` uses Java's `String.format` (printf-style)
- `cl-format` (Common Lisp format) for advanced formatting

### Pattern 10: Async/Await → core.async

**Python:**
```python
import asyncio

async def fetch_user(user_id):
    await asyncio.sleep(0.1)  # Simulate I/O
    return {"id": user_id, "name": f"User {user_id}"}

async def main():
    user = await fetch_user(123)
    print(user)

asyncio.run(main())
```

**Clojure:**
```clojure
(require '[clojure.core.async :as async :refer [go <! >! chan]])

;; core.async uses channels for communication
(defn fetch-user [user-id]
  (go
    (<! (async/timeout 100))  ; Simulate I/O
    {:id user-id :name (str "User " user-id)}))

(defn main []
  (let [result-chan (fetch-user 123)
        user (<!! result-chan)]  ; <!! blocks, <! for within go block
    (println user)))

(main)

;; Or with go blocks and channels
(go
  (let [user (<! (fetch-user 123))]
    (println user)))
```

**Why this translation:**
- Python's async/await → Clojure's `go` blocks and channels
- `<!` takes from channel (inside `go`), `<!!` blocks (outside `go`)
- `>!` puts onto channel, `>!!` blocks
- Different paradigm: CSP (channels) vs promises/futures

---

## Error Handling

### Python Exceptions → Clojure Approaches

| Python | Clojure | Notes |
|--------|---------|-------|
| `raise Exception("msg")` | `(throw (Exception. "msg"))` | Direct exception |
| `raise ValueError(...)` | `(throw (ex-info "msg" {:data ...}))` | Exception with data |
| `try: ... except E: ...` | `(try ... (catch E e ...))` | Catching exceptions |
| `try: ... finally: ...` | `(try ... (finally ...))` | Cleanup block |
| Return `None` for errors | Return `nil` or `{:error ...}` | Explicit error values |

### Exception Handling Translation

**Python:**
```python
def load_config(path):
    try:
        with open(path) as f:
            data = json.load(f)
        return data
    except FileNotFoundError:
        print(f"Config file not found: {path}")
        return None
    except json.JSONDecodeError as e:
        print(f"Invalid JSON: {e}")
        return None
    finally:
        print("Cleanup")
```

**Clojure:**
```clojure
(require '[clojure.data.json :as json])

(defn load-config [path]
  (try
    (-> path
        slurp
        json/read-str)
    (catch java.io.FileNotFoundException e
      (println "Config file not found:" path)
      nil)
    (catch Exception e
      (println "Invalid JSON:" (.getMessage e))
      nil)
    (finally
      (println "Cleanup"))))
```

**Why this translation:**
- Similar try/catch/finally structure
- Clojure uses Java exception classes
- `slurp` reads entire file (like Python's `read()`)
- `json/read-str` parses JSON string

### Functional Error Handling (Preferred)

**Python (using optional types):**
```python
from typing import Optional

def safe_divide(a: int, b: int) -> Optional[float]:
    if b == 0:
        return None
    return a / b

result = safe_divide(10, 0)
if result is not None:
    print(f"Result: {result}")
else:
    print("Division by zero")
```

**Clojure (using maps or nil):**
```clojure
;; Returning nil for errors
(defn safe-divide [a b]
  (when-not (zero? b)
    (/ a b)))

(if-let [result (safe-divide 10 0)]
  (println "Result:" result)
  (println "Division by zero"))

;; Or using explicit error maps
(defn safe-divide [a b]
  (if (zero? b)
    {:error "Division by zero"}
    {:ok (/ a b)}))

(let [result (safe-divide 10 0)]
  (if (:error result)
    (println "Error:" (:error result))
    (println "Result:" (:ok result))))
```

**Why this translation:**
- Nil represents absence/failure (like Python's None)
- Maps with `:ok`/`:error` keys make errors explicit
- Functional error handling avoids exception overhead for common cases

---

## Concurrency Patterns

### Python Threading/Asyncio → Clojure Concurrency

| Python | Clojure | Notes |
|--------|---------|-------|
| `threading.Thread` | `(Thread. ...)` or futures | Java threads |
| `asyncio.run(coro)` | `(go ...)` or `(future ...)` | Async execution |
| `asyncio.gather(*tasks)` | `(async/alts! ...)` or `pmap` | Concurrent ops |
| `asyncio.Queue` | `(chan)` | Async channel |
| `threading.Lock` | `(atom)`, `(ref)`, or Java locks | Coordinated state |
| `concurrent.futures` | `future`, `promise` | Async results |

### Asyncio → core.async

**Python:**
```python
import asyncio

async def fetch_data(url):
    await asyncio.sleep(0.1)  # Simulate I/O
    return f"Data from {url}"

async def main():
    # Concurrent execution
    results = await asyncio.gather(
        fetch_data("url1"),
        fetch_data("url2"),
        fetch_data("url3")
    )
    for result in results:
        print(result)

asyncio.run(main())
```

**Clojure:**
```clojure
(require '[clojure.core.async :as async :refer [go <! >! chan]])

(defn fetch-data [url]
  (go
    (<! (async/timeout 100))  ; Simulate I/O
    (str "Data from " url)))

;; Concurrent execution with channels
(defn main []
  (let [urls ["url1" "url2" "url3"]
        channels (map fetch-data urls)]
    ;; Collect results
    (doseq [ch channels]
      (println (<!! ch)))))

(main)

;; Or using alts! for first-to-complete
(go
  (let [urls ["url1" "url2" "url3"]
        channels (map fetch-data urls)
        [result ch] (async/alts! channels)]
    (println "First result:" result)))
```

**Why this translation:**
- Python's async/await uses event loop; Clojure uses CSP channels
- `go` blocks are lightweight (like goroutines)
- Channels (`chan`) pass values between go blocks
- `alts!` is like `select` in Go (first-to-complete)

### Threading → Atoms and Futures

**Python:**
```python
from concurrent.futures import ThreadPoolExecutor

def process_item(item):
    return item * 2

with ThreadPoolExecutor(max_workers=4) as executor:
    results = list(executor.map(process_item, range(10)))
    print(results)
```

**Clojure:**
```clojure
;; pmap - parallel map (uses futures)
(def results
  (pmap #(* % 2) (range 10)))

(println (doall results))  ; Force realization

;; Or explicit futures
(def results
  (doall (map #(future (* % 2)) (range 10))))

;; Dereference futures to get values
(def values (map deref results))
(println values)

;; Or use thread pool explicitly
(import '[java.util.concurrent Executors])
(def executor (Executors/newFixedThreadPool 4))

(def tasks
  (map #(.submit executor ^Callable (fn [] (* % 2))) (range 10)))

(def results (map #(.get %) tasks))
(.shutdown executor)
```

**Why this translation:**
- `pmap` is parallel map (automatic thread pool)
- `future` creates async task, `deref` or `@` waits for result
- Can use Java executors for fine-grained control

### State Management

**Python:**
```python
import threading

counter = 0
lock = threading.Lock()

def increment():
    global counter
    with lock:
        counter += 1

threads = [threading.Thread(target=increment) for _ in range(100)]
for t in threads:
    t.start()
for t in threads:
    t.join()

print(counter)  # 100
```

**Clojure:**
```clojure
;; Atoms for uncoordinated state
(def counter (atom 0))

(defn increment! []
  (swap! counter inc))

;; Parallel updates (thread-safe)
(doall (pmap (fn [_] (increment!)) (range 100)))

(println @counter)  ; 100

;; Or refs for coordinated transactions
(def account-a (ref 100))
(def account-b (ref 200))

(defn transfer [from to amount]
  (dosync
    (alter from - amount)
    (alter to + amount)))

(transfer account-a account-b 50)
(println @account-a @account-b)  ; 50 250
```

**Why this translation:**
- Atoms for independent state (`swap!` for atomic updates)
- Refs for coordinated state (`dosync` for transactions)
- No locks needed - Clojure's concurrency primitives are thread-safe

---

## Common Pitfalls

### 1. Mutable State → Immutable Data

**Problem:**
```clojure
;; Python: in-place mutation
# items.append(value)
# items[0] = new_value

;; Clojure: trying to mutate
(def items [1 2 3])
(conj items 4)  ; Returns new vector, doesn't modify items!
(println items)  ; Still [1 2 3]
```

**Solution:**
```clojure
;; Rebind with new value
(def items (conj items 4))  ; Now items is [1 2 3 4]

;; Or use atoms for mutable state
(def items (atom [1 2 3]))
(swap! items conj 4)
(println @items)  ; [1 2 3 4]

;; Or work with local bindings
(let [items [1 2 3]
      items (conj items 4)
      items (conj items 5)]
  (println items))  ; [1 2 3 4 5]
```

**Why this matters:** Clojure's persistent data structures are immutable by default.

### 2. Truthiness Differences

**Problem:**
```clojure
;; Python: empty collections are falsy
# if items:  # True for [1, 2], False for []

;; Clojure: empty collections are truthy!
(if [] "truthy" "falsy")  ; => "truthy"
```

**Solution:**
```clojure
;; Explicitly check for emptiness
(if (seq items)
  "has items"
  "empty")

;; Or use empty?
(if-not (empty? items)
  "has items"
  "empty")
```

**Why this matters:** Only `nil` and `false` are falsy in Clojure. Empty collections are truthy.

### 3. Sequence Realization

**Problem:**
```clojure
;; Lazy sequences aren't realized until needed
(def nums (map #(do (println "Computing" %) (* % 2)) [1 2 3]))
;; Nothing printed yet!

(count nums)  ; Now it prints "Computing 1" "Computing 2" "Computing 3"
```

**Solution:**
```clojure
;; Force realization with doall
(def nums (doall (map #(do (println "Computing" %) (* % 2)) [1 2 3])))
;; Immediately prints

;; Or use doseq for side effects
(doseq [x [1 2 3]]
  (println "Computing" x))
```

**Why this matters:** Clojure sequences are lazy by default. Side effects in lazy sequences may not execute when expected.

### 4. Keyword vs String Keys

**Problem:**
```clojure
;; Python: strings as keys
# user = {"name": "Alice", "age": 30}

;; Clojure: mixing keywords and strings
(def user {"name" "Alice" :age 30})  ; Inconsistent!
(:name user)  ; nil (looking for keyword, but key is string)
```

**Solution:**
```clojure
;; Use keywords consistently
(def user {:name "Alice" :age 30})
(:name user)  ; "Alice"

;; Or strings consistently (less idiomatic)
(def user {"name" "Alice" "age" 30})
(get user "name")  ; "Alice"
```

**Why this matters:** Keywords (`:name`) are idiomatic for map keys in Clojure. They're faster and work as functions.

### 5. Namespace Collisions

**Problem:**
```clojure
;; Python: methods are namespaced by class
# user.get("name")
# config.get("timeout")

;; Clojure: same function name across namespaces
(require '[clojure.set :as set])
(set/union #{1 2} #{2 3})  ; Must qualify or alias
```

**Solution:**
```clojure
;; Always use namespace aliases
(require '[clojure.string :as str]
         '[clojure.set :as set])

(str/upper-case "hello")
(set/union #{1 2} #{2 3})

;; Or refer specific functions
(require '[clojure.string :refer [upper-case lower-case]])
(upper-case "hello")
```

**Why this matters:** Clojure namespaces prevent collisions but require explicit imports.

### 6. Integer Division

**Problem:**
```clojure
;; Python 3: / always returns float
# 5 / 2  # 2.5

;; Clojure: / returns ratio for integers
(/ 5 2)  ; 5/2 (ratio), not 2.5
```

**Solution:**
```clojure
;; Convert to double for floating-point division
(/ 5.0 2)  ; 2.5

;; Or use quot for integer division
(quot 5 2)  ; 2

;; Force ratio to double
(double (/ 5 2))  ; 2.5
```

**Why this matters:** Clojure preserves exact ratios. Use `double` or floating-point literals for decimals.

### 7. Variadic Functions

**Problem:**
```clojure
;; Python: *args, **kwargs
# def func(*args, **kwargs):
#     print(args, kwargs)

;; Clojure: rest args only (no keyword args)
(defn func [& args]
  (println args))

(func 1 2 3)  ; (1 2 3)
```

**Solution:**
```clojure
;; Use destructuring for keyword-style args
(defn func [& {:keys [name age] :or {age 0}}]
  (println name age))

(func :name "Alice" :age 30)  ; "Alice 30"
(func :name "Bob")  ; "Bob 0" (default age)

;; Or use maps explicitly
(defn func [opts]
  (println (:name opts) (:age opts 0)))

(func {:name "Alice" :age 30})
```

**Why this matters:** Clojure doesn't have keyword arguments. Use map destructuring or explicit maps.

### 8. Global Mutable State

**Problem:**
```clojure
;; Python: global keyword
# counter = 0
# def increment():
#     global counter
#     counter += 1

;; Clojure: def creates immutable binding
(def counter 0)
(defn increment []
  (def counter (inc counter)))  ; Bad! Creates new binding

(increment)
(println counter)  ; Still 0!
```

**Solution:**
```clojure
;; Use atoms for mutable state
(def counter (atom 0))

(defn increment! []
  (swap! counter inc))

(increment!)
(println @counter)  ; 1

;; Or pass state explicitly (functional style)
(defn increment [counter]
  (inc counter))

(-> 0
    increment
    increment
    increment
    println)  ; 3
```

**Why this matters:** `def` creates new vars; use atoms for mutable state or pass state explicitly.

---

## Tooling

### Translation Tools

| Tool | Purpose | Notes |
|------|---------|-------|
| Manual translation | Full control | Recommended for production |
| REPL experimentation | Interactive development | Core Clojure workflow |
| `clj-python` | Python-Clojure interop | Call Python from Clojure (libpython-clj) |

### Development Environment

| Python | Clojure | Purpose |
|--------|---------|---------|
| `python` | `clj` or `lein repl` | REPL |
| `pip` / `uv` | Leiningen or tools.deps | Package management |
| `pytest` | `clojure.test` | Testing framework |
| `mypy` | `clojure.spec` | Runtime validation |
| `black` | `cljfmt` | Code formatting |
| `pylint` | `eastwood`, `kibit` | Linting |

### Common Library Equivalents

| Python Package | Clojure Library | Purpose |
|----------------|-----------------|---------|
| `requests` | `clj-http` | HTTP client |
| `aiohttp` | `http-kit` | Async HTTP |
| `flask` / `fastapi` | `ring` + `compojure` | Web framework |
| `pydantic` | `clojure.spec` | Data validation |
| `click` / `argparse` | `tools.cli` | CLI parsing |
| `logging` | `tools.logging` | Logging |
| `datetime` | `clj-time` | Date/time |
| `pathlib` | `clojure.java.io` | File I/O |
| `json` | `clojure.data.json` | JSON parsing |
| `re` | `clojure.string` | Regex |
| `sqlite3` | `clojure.java.jdbc` | Database |
| `pandas` | `tech.ml.dataset` | Data frames |
| `numpy` | `core.matrix` | Numerical computing |

---

## Examples

### Example 1: Simple - HTTP GET Request

**Before (Python):**
```python
import requests

def fetch_user(user_id):
    response = requests.get(f"https://api.example.com/users/{user_id}")
    response.raise_for_status()
    return response.json()

try:
    user = fetch_user(123)
    print(f"User: {user['name']}")
except requests.HTTPError as e:
    print(f"HTTP error: {e}")
```

**After (Clojure):**
```clojure
(require '[clj-http.client :as http]
         '[clojure.data.json :as json])

(defn fetch-user [user-id]
  (-> (str "https://api.example.com/users/" user-id)
      http/get
      :body
      (json/read-str :key-fn keyword)))

;; Usage
(try
  (let [user (fetch-user 123)]
    (println "User:" (:name user)))
  (catch Exception e
    (println "HTTP error:" (.getMessage e))))
```

**Key changes:**
- `requests.get` → `clj-http.client/get`
- Dictionary access `user['name']` → keyword access `(:name user)`
- `json/read-str` with `:key-fn keyword` converts JSON keys to keywords
- Similar try/catch structure

### Example 2: Medium - Configuration Parser

**Before (Python):**
```python
from pathlib import Path
import json
from dataclasses import dataclass

@dataclass
class Config:
    host: str
    port: int
    timeout: int = 30

    def validate(self):
        if not (1 <= self.port <= 65535):
            raise ValueError(f"Invalid port: {self.port}")

def load_config(path):
    if not path.exists():
        raise FileNotFoundError(f"Config not found: {path}")

    with path.open() as f:
        data = json.load(f)

    config = Config(**data)
    config.validate()
    return config

config = load_config(Path("config.json"))
print(f"Server: {config.host}:{config.port}")
```

**After (Clojure):**
```clojure
(require '[clojure.data.json :as json]
         '[clojure.spec.alpha :as s])

;; Define spec for validation
(s/def ::host string?)
(s/def ::port (s/and int? #(<= 1 % 65535)))
(s/def ::timeout (s/and int? pos?))
(s/def ::config (s/keys :req-un [::host ::port]
                        :opt-un [::timeout]))

(defn load-config [path]
  (when-not (.exists (clojure.java.io/file path))
    (throw (ex-info "Config not found" {:path path})))

  (let [config (-> path
                   slurp
                   (json/read-str :key-fn keyword)
                   (merge {:timeout 30}))]  ; Default value
    (when-not (s/valid? ::config config)
      (throw (ex-info "Invalid config" (s/explain-data ::config config))))
    config))

;; Usage
(let [config (load-config "config.json")]
  (println (format "Server: %s:%d" (:host config) (:port config))))
```

**Key changes:**
- `@dataclass` → plain map with `clojure.spec` validation
- Default values via `merge`
- `clojure.spec` for validation (runtime checks)
- `ex-info` for exceptions with data

### Example 3: Complex - Data Processing Pipeline

**Before (Python):**
```python
from collections import defaultdict
from dataclasses import dataclass
from typing import List

@dataclass
class Transaction:
    user_id: int
    amount: float
    category: str

def process_transactions(transactions: List[Transaction]):
    # Group by user
    by_user = defaultdict(list)
    for txn in transactions:
        by_user[txn.user_id].append(txn)

    # Calculate totals per category for each user
    results = {}
    for user_id, txns in by_user.items():
        category_totals = defaultdict(float)
        for txn in txns:
            category_totals[txn.category] += txn.amount

        # Only users with total > 100
        total = sum(category_totals.values())
        if total > 100:
            results[user_id] = {
                "total": total,
                "by_category": dict(category_totals),
                "count": len(txns)
            }

    return results

transactions = [
    Transaction(1, 50.0, "food"),
    Transaction(1, 75.0, "transport"),
    Transaction(2, 200.0, "food"),
    Transaction(1, 25.0, "food"),
]

results = process_transactions(transactions)
for user_id, stats in results.items():
    print(f"User {user_id}: {stats}")
```

**After (Clojure):**
```clojure
(defn process-transactions [transactions]
  (->> transactions
       ;; Group by user
       (group-by :user-id)

       ;; Transform each user's transactions
       (map (fn [[user-id txns]]
              (let [;; Calculate category totals
                    by-category (->> txns
                                     (group-by :category)
                                     (map (fn [[cat items]]
                                            [cat (reduce + (map :amount items))]))
                                     (into {}))

                    total (reduce + (vals by-category))
                    count (count txns)]

                [user-id {:total total
                         :by-category by-category
                         :count count}])))

       ;; Filter users with total > 100
       (filter (fn [[_ stats]] (> (:total stats) 100)))

       ;; Convert to map
       (into {})))

;; Usage
(def transactions
  [{:user-id 1 :amount 50.0 :category "food"}
   {:user-id 1 :amount 75.0 :category "transport"}
   {:user-id 2 :amount 200.0 :category "food"}
   {:user-id 1 :amount 25.0 :category "food"}])

(def results (process-transactions transactions))

(doseq [[user-id stats] results]
  (println (format "User %d: %s" user-id stats)))
```

**Key changes:**
- `@dataclass` → plain maps
- Imperative loops → functional pipeline with `->>` threading
- `defaultdict` → `group-by` function
- `for` loops → `map`, `filter`, `reduce`
- Immutable transformations throughout
- More declarative, less mutable state

---

## See Also

For more examples and patterns, see:
- `meta-convert-dev` - Foundational patterns with cross-language examples
- `lang-python-dev` - Python development patterns
- `lang-clojure-dev` - Clojure development patterns
- `patterns-concurrency-dev` - Async/channels patterns across languages
- `patterns-serialization-dev` - JSON/EDN serialization patterns

Attribution

aRustyDevaRustyDev
View sourceSee grades on GitHubMore from aRustyDev →
SSkills Directory ProSkills Directory

Get any skill into Claude in one click.

Download any skill as a ZIP for Claude.ai, Claude Desktop, or .claude/skills. $9/mo.

See Pro

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills Directory ProSkills Directory

Get any skill into Claude in one click.

Download any skill as a ZIP for Claude.ai, Claude Desktop, or .claude/skills. $9/mo.

See Pro

Related Skills

Clean Code

Pragmatic coding standards - concise, direct, no over-engineering, no unnecessary comments

304955 votes

Browser Extension Developer

Use this skill when developing or maintaining browser extension code in the `browser/` directory, including Chrome/Firefox/Edge compatibility, content scripts, background scripts, or i18n updates.

285172 votes

Seo Optimizer

SEO optimization with keyword analysis, readability assessment, technical validation, content quality. Use for search rankings, blog posts, content audits, or encountering keyword density, readability scores, meta tags, schema markup errors.

2222 votes

Google Official Seo Guide

Official Google SEO guide covering search optimization, best practices, Search Console, crawling, indexing, and improving website search visibility based on official Google documentation

1862 votes

Tanstack Start

Build a full-stack TanStack Start app on Cloudflare Workers from scratch — SSR, file-based routing, server functions, D1+Drizzle, better-auth, Tailwind v4+shadcn/ui. Use whenever the user mentions TanStack Start, asks to scaffold a full-stack Cloudflare app with SSR, wants an SSR dashboard, or asks for a React 19 + Cloudflare Workers app with file-based routing and server functions — even if they don't name TanStack Start specifically. No template repo — Claude generates every file fresh per ...

10341 votes
View all in development →