Claude
Skills
Sign in
Back

janet

Included with Lifetime
$97 forever

Write idiomatic Janet code. Use when writing, refactoring, or reviewing Janet (.janet) code. Covers functional patterns, performance tradeoffs, common gotchas, data structure idioms, PEG parsing, and Janet-specific pitfalls.

Writing & Docs

What this skill does


# Janet Style Guide

This skill guides the generation of idiomatic Janet code covering functional patterns, performance tradeoffs, common gotchas, and Janet-specific idioms.

## Core Principles

### Functional Style by Default
- **Always prefer functional style** unless there is a clear performance penalty or tradeoff
- Use imperative/mutable approaches only when performance demands it (large collections, hot paths, string building)
- When mutation is used for performance, document why and keep scope limited

### Prefer `let` Over Sequential `def`
Use `let` to group related bindings instead of sequential `def` statements:
```janet
# Good - let groups bindings clearly
(defn process [data]
  (let [items (data :items)
        count (length items)
        filtered (filter valid? items)]
    (map transform filtered)))

# Avoid - sequential defs
(defn process [data]
  (def items (data :items))
  (def count (length items))
  (def filtered (filter valid? items))
  (map transform filtered))
```
Reserve `def` for top-level definitions and cases where bindings are interspersed with side effects or control flow that makes `let` awkward.

### Prefer Immutability (When Practical)
- Use `def` for immutable bindings instead of `var`
- Favor pure functions without side effects
- When mutation is necessary, limit scope and make it explicit

### Embrace Higher-Order Functions
Use Janet's rich set of functional primitives:
- `map`, `filter`, `reduce` for collection transformations
- `keep`, `keep-indexed` for filtering with transformation
- `mapcat` for flat-mapping operations
- `partition`, `take`, `drop` for sequence manipulation
- `comp`, `partial`, `complement` for function composition
- `juxt` for parallel application

### Favor Expressions Over Statements
- Use `if`, `cond`, `case` as expressions that return values
- Prefer `when` for single-branch conditionals with side effects
- Use `->>` and `->` threading macros for pipeline clarity
- Chain operations rather than using intermediate variables

### Use Destructuring
Destructure function parameters and let bindings for clarity:
```janet
# Good - destructuring
(defn process [{:name name :age age}]
  (string name " is " age))

# Avoid - manual extraction
(defn process [person]
  (def name (person :name))
  (def age (person :age))
  (string name " is " age))
```

## Common Functional Patterns

### Collection Transformation Pipelines
```janet
# Good - functional pipeline
(->> data
     (filter some-pred?)
     (map transform)
     (reduce combine init))

# Avoid - procedural loops
(var result init)
(each item data
  (when (some-pred? item)
    (set result (combine result (transform item)))))
```

### Building Data Structures
```janet
# Good - expression-based construction
(def users
  (map (fn [{:name n :age a}]
         {:name n :adult? (>= a 18)})
       raw-data))

# Avoid - imperative building
(var users @[])
(each person raw-data
  (array/push users
    {:name (person :name)
     :adult? (>= (person :age) 18)}))
```

### Conditional Logic
```janet
# Good - cond expression
(def status
  (cond
    (< score 60) :fail
    (< score 80) :pass
    :excellent))

# Avoid - nested ifs with mutation
(var status nil)
(if (< score 60)
  (set status :fail)
  (if (< score 80)
    (set status :pass)
    (set status :excellent)))
```

### Function Composition
```janet
# Good - compose smaller functions
(def process
  (comp
    (partial filter valid?)
    (partial map normalize)
    (partial sort compare)))

# Use threading for readability
(defn analyze [data]
  (->> data
       (filter valid?)
       (map normalize)
       (sort compare)
       (take 10)))
```

### Recursion and Reduce
```janet
# Good - tail-recursive
(defn factorial [n]
  (defn fac-iter [n acc]
    (if (<= n 1)
      acc
      (fac-iter (- n 1) (* n acc))))
  (fac-iter n 1))

# Good - reduce for aggregation
(defn sum [xs] (reduce + 0 xs))

# Avoid - imperative loop for simple aggregation
(defn sum [xs]
  (var total 0)
  (each x xs (set total (+ total x)))
  total)
```

## Janet-Specific Idioms

### Sequence Processing
```janet
# Use keep for filter+map
(keep |(when (even? $) (* $ 2)) (range 10))

# Use partition for chunking
(partition 3 (range 10))

# Use interleave/interpose for combining
(interleave [:a :b :c] [1 2 3])
```

### Short Functions
Use `|` short-fn syntax for concise anonymous functions:
```janet
(map |(* $ $) (range 5))        # square
(filter |(> $ 10) numbers)      # greater than 10
(reduce |(+ $0 $1) 0 numbers)   # sum
```

### Pattern Matching
Use `match` for elegant conditional dispatch:
```janet
(defn describe [value]
  (match value
    [:ok x] (string "success: " x)
    [:err e] (string "error: " e)
    _ "unknown"))
```

### Struct/Table Construction
```janet
# Good - literal construction
(def config
  {:host "localhost"
   :port 8080
   :debug true})

# Use struct for immutable maps
(struct :a 1 :b 2 :c 3)

# Use zipcoll for key-value pairing
(zipcoll [:a :b :c] [1 2 3])
```

## Performance: When to Choose Imperative Style

Janet's mutable data structures are often faster than immutable ones. Use imperative/mutable approaches when:

### High-Performance Scenarios
- **Building large collections incrementally**: Use `@[]` and `array/push` instead of repeated concatenation
- **Accumulating results in loops**: Local mutation with `var` is faster than recursive accumulation
- **Hot paths and tight loops**: Mutation avoids allocation overhead
- **String building**: Use `buffer` with `buffer/push-string` instead of string concatenation
- **Large data processing**: Mutable operations avoid copying large structures

### Performance-Optimized Patterns

**Fast array building:**
```janet
# Fast - mutation for large results
(defn process-large [items]
  (def result @[])
  (each item items
    (when (expensive-pred? item)
      (array/push result (expensive-transform item))))
  result)

# Slower for large collections - creates intermediate arrays
(defn process-large [items]
  (->> items
       (filter expensive-pred?)
       (map expensive-transform)))
```

**Fast string building:**
```janet
# Fast - buffer mutation
(defn build-report [data]
  (def buf @"")
  (each item data
    (buffer/push-string buf "Item: ")
    (buffer/push-string buf (item :name))
    (buffer/push-string buf "\n"))
  (string buf))

# Slower - string concatenation
(defn build-report [data]
  (reduce (fn [acc item]
            (string acc "Item: " (item :name) "\n"))
          "" data))
```

**Fast accumulation:**
```janet
# Fast - local mutation
(defn sum-squares [numbers]
  (var total 0)
  (each n numbers
    (+= total (* n n)))
  total)

# Slower - functional reduce (minor difference, but matters in hot paths)
(defn sum-squares [numbers]
  (reduce (fn [acc n] (+ acc (* n n))) 0 numbers))
```

**Fast table/struct construction:**
```janet
# Fast - build mutable then freeze
(defn group-by [key-fn items]
  (def groups @{})
  (each item items
    (def k (key-fn item))
    (if-let [group (groups k)]
      (array/push group item)
      (put groups k @[item])))
  (table/to-struct groups))  # Return immutable if needed
```

### When Functional Style Still Wins
- **Small collections**: Overhead is negligible, clarity matters more
- **One-pass transformations**: `map`/`filter` are well-optimized
- **Code that's not performance-critical**: Readability and maintainability trump speed
- **When immutability prevents bugs**: Thread safety, easier reasoning about code

### Hybrid Approach
Combine both styles for optimal results:
```janet
# Functional pipeline with imperative inner loop for performance
(defn analyze-data [raw-data]
  (->> raw-data
       (filter valid?)
       (partition-by get-category)
       (map (fn [batch]
              # Imperative processing of each batch
              (def result @{})
              (each item batch
                (def key (item :id))
                (put result key (expensive-compute item)))
              result))))
```

## Anti-Patterns to Avoid

#
Files: 1
Size: 16.4 KB
Complexity: 20/100
Category: Writing & Docs

Related in Writing & Docs