clojure-plus

repository·main·Indexed 42 days ago

https://github.com/tonsky/clojure-plus

A project to improve the Clojure standard library developer experience. It provides enhanced core macros (if+, when+, cond+), a debugging macro (#p), improved data printing for built-in structures and Java objects, readable exception stacktrace printing, enhanced test reporting via clojure+.test, and a GC-efficient replacement for clojure.walk.

Tokens
3.7K
Snippets
13
Records
15
Agent score
4%

What's inside clojure-plus

  1. Overview of clojure-plus namespaces

    main

    The clojure-plus project provides several namespaces to improve the Clojure standard library experience:

    NamespacePurpose
    clojure+.coreAdvanced versions of if+, when+, cond+
    clojure+.hashp#p debugging macro (better than println)
    clojure+.printImproved printing of built-in datastructures and classes
    clojure+.errorImproved exception stacktrace printing
    clojure+.testOutput capture, improved reporting, and unified run function
    clojure+.core.serverstart-server with better defaults, random ports, and .port-file
    clojure+.walkclojure.walk equivalent that avoids duplicating data structures
  2. Improve exception printing with clojure+.error

    main

    The clojure+.error namespace provides a cleaner, more readable, and Clojure-aware exception printer. It solves common issues with the standard Clojure exception printer by:

    • Decoding Clojure stack traces: It converts internal Clojure stack elements (like clojure.main$repl_opt) back into familiar Clojure forms (like clojure.main/repl-opt).
    • Improved Formatting: It provides a cleaner layout that is easier to parse visually than the standard #error map.
    • Color Support: It supports colored terminal output for better readability.
    • Readable Data: It can print exceptions in a format that is both human-readable and valid Clojure data (compatible with (read)).
    • Reversed Stack Traces: It can reverse the stack trace so the error message and the point of origin appear at the bottom, preventing the need to scroll up for long traces.
    ;; Example of a cleaned-up, Clojure-aware stack trace
    (println (Exception. "Oops"))
    
    Exception: Oops
      user/eval
      Compiler.eval                         Compiler.java 7700
      Compiler.eval                         Compiler.java 7655
      clojure.core/eval                     core.clj 3232
      clojure.main/repl/read-eval-print/fn  main.clj 437
      ...
  3. Enhance test reporting with clojure+.test

    main

    The clojure+.test namespace enhances clojure.test by providing more readable, visually structured output for test failures and improved error reporting.

    Key features include:

    • Visual Hierarchy: testing contexts are represented with tree-like structures (└╴).
    • Improved Comparisons: For is checks, expected and actual values are printed on separate lines.
    • Namespace Inclusion: Test names automatically include their namespace (e.g., test-ns/test-name).
    • Output Capture: Silences debug output from successful tests while preserving it for failures (controlled by :capture-output?).
    • Clean Stack Traces: Filters out clojure.test machinery from exception reports and identifies the specific test where an uncaught exception occurred.
    • Interruptible Tests: Unlike standard clojure.test, runs via clojure+.test/run are interruptible.

    Note: clojure+.test does not replace clojure.test; you still use deftest, is, and testing from the standard library.

    (clojure+.test/install!)
  4. Perform efficient tree transformations with clojure+.walk

    main

    The clojure+.walk namespace provides a drop-in replacement for clojure.walk designed to reduce Garbage Collection (GC) pressure.

    While standard clojure.walk recreates data structures even if they haven't changed, clojure+.walk (e.g., postwalk or prewalk) checks if the result of a transformation is identical? to the original. If the data hasn't changed, it reuses the existing structure. This utilizes structural sharing and is significantly more efficient for deep trees where only small parts are modified.

    (let [m {:a 1, :b 2}]
      ;; Returns true because identity was preserved
      (identical? m (clojure+.walk/postwalk identity m)))
  5. Install and uninstall clojure+.error

    main

    To activate the enhanced exception printing, call clojure+.error/install!. You can pass an optional configuration map as shown in the configuration guide.

    To revert to the standard Clojure exception printer, call clojure+.error/uninstall!.

    (clojure+.error/install!)
    
    ;; To restore default Clojure behavior
    (clojure+.error/uninstall!)
  6. Configure clojure+.error settings

    main

    You can customize the behavior of the exception printer using clojure+.error/install!.

    To enable reversed stack traces (showing the error message at the bottom), use {:reverse? true}.

    Available configuration keys:

    • :clean?: (boolean) Whether to clean up the output.
    • :collapse-common?: (boolean) Whether to collapse common stack elements.
    • :trace-transform: (function) A function to transform the trace.
    • :color?: (boolean) Whether to use colors in the terminal.
    • :reverse?: (boolean) Whether to reverse the stack trace.
    • :root-cause-only?: (boolean) Whether to show only the root cause.
    • :indent: (number) Indentation level.
    ```clojure
    ;; Install with specific configuration
    (clojure+.error/install! {:reverse? true})
    
    ;; Default configuration structure
    (clojure+.error/install!
      {:clean?           true
       :collapse-common? true
       :trace-transform  nil
       :color?           true
       :reverse?         false
       :root-cause-only? false
       :indent           2})
  7. Use #p for improved debugging

    main

    The clojure+.hashp namespace provides the #p macro, a faster and more informative alternative to println. It returns the original value, allowing it to be used inline or within threading macros (->, ->>).

    To use it, you must call (clojure+.hashp/install!) (recommended for dev mode).

    (require '[clojure+.hashp :as hashp])
    (hashp/install!)
    
    ;; Usage example
    (defn f []
      (let [x 5
            y #p (+ x 2)]
        (* x y)))
    
    ;; Usage in threading macros
    (-> x
        hsql/format
        #p first
        #p (sql-pretty)
        println)
  8. Use when+ to share local variables in a body

    main

    The when+ macro is similar to if+, but it wraps the body in an implicit do block. You can use :let inside an and condition to make variables available in the body.

    (when+ (and
             (= 1 2)
             :let [x 3
                   y (+ x 4)]
             (> y x))
      
      ;; body: x and y visible here!
      (+ x y 5))
  9. Use cond+ for advanced conditional logic

    main

    The cond+ macro extends cond with several capabilities:

    • :let: Define new variables between conditions.
    • :do: Insert imperative code that executes before continuing evaluation.
    • :else: Standard fallback branch.
    • Nested :let: Declare variables inside and conditions within a branch, making them visible to the matching branch.
    ;; Define variables between conditions
    (cond+
      false   :false
      :let    [x 1]
      (= 1 x) (str x))
    
    ;; Insert imperative code
    (cond+
      (= 1 a) :false
      :do     (println a) 
      :else   :true)
    
    ;; Declare variables inside conditions
    (cond+
      (and
        (= 1 1)
        :let [x 2
              y (+ x 1)]
        (> y x))
      [x y])
  10. Improve data printing and reading with clojure+.print

    main

    The clojure+.print namespace provides cleaner, more elegant string representations for Clojure built-in datastructures, classes, and Java objects (like atom, file, ns, int-array, etc.). It also provides reader tags (e.g., #atom, #file, #ints) to make constructing these objects easier.

    Warning: This representation does not track identity. Printing an atom and reading it back will produce a new object instance.

    Installation:

    1. (require 'clojure+.print)
    2. (clojure+.print/install!)

    Customization:

    • (clojure+.print/install-printers! {:include ['queue 'file]})
    • (clojure+.print/install-readers! {:exclude ['bytes 'ns]})
    (require 'clojure+.print)
    (clojure+.print/install!)
    
    ;; Example transformations
    ;; (atom 123) => #atom 123
    ;; (io/file "/") => #file "/"
    ;; (int-array [1 2 3]) => #ints [1 2 3]
  11. Run tests using clojure+.test/run

    main

    The clojure+.test/run function replaces the confusing variety of clojure.test functions (run-tests, run-all-tests, run-test-var, run-test) with a single, unified interface.

    It accepts various inputs including:

    • Symbols
    • Vars
    • Namespaces
    • Regexes

    Calling it without arguments runs all tests in the project.

    ;; Run all tests
    (clojure+.test/run)
    
    ;; Run specific tests (example usage)
    (clojure+.test/run 'my-namespace.test-var)