Tupelo
Tupelo: Clojure With A Spoonful of Honey
Install / Use
/learn @cloojure/TupeloREADME
= Making Clojure Even Sweeter
=== image:https://img.shields.io/clojars/v/tupelo.svg[link="http://clojars.org/tupelo"] <= Current Version (click for dep string) === image:https://cljdoc.org/badge/tupelo/tupelo[link="https://cljdoc.org/d/tupelo/tupelo/CURRENT"] <= Full API docs (click for cljdoc.org) ==== Old-style link:http://cloojure.github.io/doc/tupelo/[API Docs] on GitHub Pages (codox)
== Tupelo Overview
Have you ever wanted to do something simple but clojure.core doesn't support it? Or, maybe you are wishing for an enhanced version of a standard function. If so, then Tupelo is for you! Tupelo is a library of helper and convenience functions to make working with Clojure simpler, easier, and more bulletproof.
== Tupelo Organization
The functionality of the Tupelo library is divided into a number of namespaces, each with a single area of focus. These are:
==== Tupelo Core - A library of helper functions for core Clojure.
Please see the xref:tupelo-core-overview[tupelo.core] docs further below.
==== Tupelo-Forest - A library for searching & manipulating tree-like data structures
Please see the link:docs/forest.adoc[tupelo.forest] docs for further information.
==== Tupelo-Datomic - A library of helper functions for Datomic.
The tupelo-datomic library has been split out into an independent project. Please see https://github.com/cloojure/tupelo-datomic[the tupelo-datomic project] for details.
==== Tupelo CSV - Functions for using CSV (Comma Separate Value) files
The standard link:http://github.com/davidsantiago/clojure-csv[clojure-csv library] has well-tested and useful functions for parsing CSV (Comma Separated Value) text data, but it does not offer all of the convenience one may wish. Tupelo CSV emphasizes the idomatic Clojure usage of data, using sequences and maps. Please see the link:http://cloojure.github.io/doc/tupelo/tupelo.csv.html[tupelo.csv] docs.
==== Tupelo Parse - Functions to ease text parsing
Please see the link:http://cloojure.github.io/doc/tupelo/tupelo.parse.html[tupelo.parse] docs.
==== Tupelo String - Functions to ease string operations
Please see the link:http://cloojure.github.io/doc/tupelo/tupelo.string.html[tupelo.string] docs.
==== Tupelo Schema - Type Definitions
Enables type checking in Clojure code via link:https://github.com/plumatic/schema[Plumatic Schema]. Please see link:https://github.com/cloojure/tupelo/blob/master/src/tupelo/schema.clj[the source code] for definitions, and link:https://github.com/cloojure/tupelo-datomic/blob/master/test/tst/tupelo_datomic/bond.clj[the James Bond example code] for examples of the type-checking in action.
==== Tupelo Types - A collection of functions for testing object types
Please see the link:http://cloojure.github.io/doc/tupelo/tupelo.types.html[tupelo.types] docs.
==== Tupelo Misc - A grab bag of functions that don't fit anywhere else (yet!)
Please see the link:http://cloojure.github.io/doc/tupelo/tupelo.misc.html[tupelo.misc] docs.
==== tupelo.base64 - Convert to/from base64 encoding.
Please see the link:http://cloojure.github.io/doc/tupelo/tupelo.base64.html[tupelo.base64] docs.
==== tupelo.base64url - Convert to/from base64url encoding.
Please see the link:http://cloojure.github.io/doc/tupelo/tupelo.base64url.html[tupelo.base64url] docs.
==== Tupelo Y64 - Convert to/from the URL-safe Y64 encoding (Yahoo YUI library).
Please see the link:http://cloojure.github.io/doc/tupelo/tupelo.y64.html[tupelo.y64] docs.
[[tupelo-core-overview]]
== Tupelo Core Overview
Have you ever wanted to do something simple but clojure.core doesn't support it? Or, maybe
you are wishing for an enhanced version of a standard function. The goal of tupelo.core is to
add support for these convenience features, so that you have a simple way of using either
the enhanced version or the original version.
The goal in using tupelo.core is that you can just plop it into any namespace without
having to worry about any conflicts with clojure.core functionality. So, both the core functions
and the added/enhanced functions are both available for use at all times. As such, we
normally refer tupelo.core into our namespace as follows:
[source,clojure]
(ns my.proj (:use tupelo.core) (:require [clojure.string :as str] ... ))
=== Expression Debugging
Have you ever been debugging some code and had trouble printing out intermediate values? For example:
[source,clojure]
(-> 1 (inc) ; want to print value in pipeline after "(inc)" expression (* 2)) 4
Suppose you want to display the value after the (inc) function. You can't just insert a
(println ...) because the return value of nil will break the pipeline structure. Instead,
just use spy:
[source,clojure]
(-> 1 (inc) (spy) ; print value at this location in pipeline (* 2)) ; spy => 2 ; output from spy 4 ; return value from the threading pipeline
This tool is named spy since it can display values from inside any threading form without
affecting the result of the expression. In this case, spy printed the value 2 resulting from
the (inc) expression. Then, the value 2 continued to flow through the following expressions in
the pipeline so that the return value of the expression is unchanged.
You can add in a keyword message to label each spy output:
[source,clojure]
(-> 1 (inc) (spy :after-inc) ; add a custom keyword message (* 2)) ; :after-inc => 2 ; spy output is labeled with keyword message 4 ; return value is unchanged
Note that spy works equally well inside either a "thread-first" or a "thread-last" form
(e.g. using \-> or \->>), without requiring any changes.
[source,clojure]
(->> 1 (inc) (spy :after-inc) ; spy works equally with both -> and ->> forms (* 2)) ; :after-inc => 2 4
How does spy accomplish this trick? The answer is that the keyword message is assumed to be the
label, since interesting debug values are more likely to be strings, numbers, or collections like
vectors & maps (if both args are keywords, an exception is thrown; use some other technique for
debugging this use-case). Thus, spy can detect whether it is in a thread-first or thread-last
form, and then label the output correctly. A side benefit is that keywords like :after-inc or
just :110 are easy to grep for in output log files.
As a bonus for debugging, the value is output using (pr-str ...) so that numbers and strings are unambiguous in the output:
[source,clojure]
(-> 30 (+ 4) (spy :dbg) (* 10)) ; :dbg => 34 ; integer result = 34 340
(-> "3" (str "4") (spy :dbg) (str "0")) ; :dbg => "34" ; string result = "34" "340"
Sometimes you may prefer to print out the literal expression instead of a
keyword label. In this case, just use spyx (short for "spy expression") :
[source,clojure]
(it-> 1 ; tupelo.core/it-> (spyx (inc it)) (* 2 it)) ; (inc it) => 2 ; the expression is used as the label 4
In other instances, you may wish to use spyxx to display the expression, its
type, and its value:
[source,clojure]
(defn mystery-fn [] (into (sorted-map) {:b 2 :a 1})) (spyxx (mystery-fn)) ; (mystery-fn) => <#clojure.lang.PersistentTreeMap {:a 1, :b 2}>"
Non-pure functions (i.e. those with side-effects) are safe to use with spy.
Any expression supplied to spy will be evaluated only once.
Sometimes you may just want to save some repetition for a simple printout: [source,clojure]
(def answer 42) (spyx answer) ; answer => 42
To be precise, the function signatures for the spy family are:
[source,clojure]
(spy <expr>) ; print value of <expr> w/o custom message string
(spy <expr> :kw-label) ; works with ->
(spy :kw-label <expr>) ; works with ->>
(spyx <expr>) ; prints <expr> and its value
(spyxx <expr>) ; prints <expr>, its type, and its value
If you are debugging a series of nested function calls, it can often be handy to indent the spy
output to help in visualizing the call sequence. Using with-spy-indent will give you just what you
want:
[source,clojure]
(doseq [x [:a :b]] (spyx x) (with-spy-indent (doseq [y (range 3)] (spyx y)))) x => :a y => 0 y => 1 y => 2 x => :b y => 0 y => 1 y => 2
=== Literate Threading Macro
We all love to use the threading macros \-> and \->> for certain tasks, but they only work if
all of the forms should be threaded into the first or last argument.
The built-in threading macro as\-> can avoid this problem, but the order of the first expression
and the placeholder symbol is arguably backwards from what most users would expect. Also, there is
often no obvious name to use for the placeholder symbol. Re-using a good idea from Groovy (also
copied by Kotlin), we simply use the symbol it as the placeholder symbol in each expression
to represent the value of the previous result.
[source,clojure]
(it-> 1 (inc it) ; thread-first or thread-last (+ it 3) ; thread-first (/ 10 it) ; thread-last (str "We need to order " it " items." ) ; middle of 3 arguments ;=> "We need to order 2 items." )
Here is a more complicated example. Note that we can assign into a local let block from the it
placeholder value:
[source,clojure]
(it-> 3 (spy :initial it) (let [x it] (inc x)) (spy it :222) (* it 2) (spyx it)) ; :initial => 3 ; :222 => 4 ; it => 8 8 ; return value
More examples link:it-thread.adoc[can be found here].
The it\-> macro has a cousin cond-it\-> that allows you to thread the updated value through both the conditional and the action
expressions:
[source,clojure]
Related Skills
node-connect
344.1kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
96.8kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
344.1kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
344.1kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
