{"id":30458031,"url":"https://github.com/j-cr/speck","last_synced_at":"2025-08-23T17:06:10.596Z","repository":{"id":62434672,"uuid":"141345359","full_name":"j-cr/speck","owner":"j-cr","description":"A concise and composable syntax for your function specs","archived":false,"fork":false,"pushed_at":"2019-10-07T10:07:52.000Z","size":33,"stargazers_count":53,"open_issues_count":1,"forks_count":1,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-08-17T05:22:48.550Z","etag":null,"topics":["clojure","spec"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/j-cr.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-07-17T21:27:34.000Z","updated_at":"2024-05-31T07:53:07.000Z","dependencies_parsed_at":"2022-11-01T21:01:59.409Z","dependency_job_id":null,"html_url":"https://github.com/j-cr/speck","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/j-cr/speck","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/j-cr%2Fspeck","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/j-cr%2Fspeck/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/j-cr%2Fspeck/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/j-cr%2Fspeck/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/j-cr","download_url":"https://codeload.github.com/j-cr/speck/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/j-cr%2Fspeck/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271755878,"owners_count":24815494,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-23T02:00:09.327Z","response_time":69,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","spec"],"created_at":"2025-08-23T17:02:24.512Z","updated_at":"2025-08-23T17:06:10.568Z","avatar_url":"https://github.com/j-cr.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# speck\n\n\u003e speck /spɛk/\n\u003e \n\u003e 1. a tiny spot.\n\n[Speck][] is a tiny library for your tiny [specs][]. It allows you to write\nconcise [function specs][] right inside your `defn`s and plays nice with others\nbecause it doesn't introduce any custom defn wrappers. See for yourself:\n\n[Speck]: https://github.com/j-cr/speck\n[specs]: https://clojure.org/guides/spec\n[function specs]: https://clojure.org/guides/spec#_spec_ing_functions\n\n```clojure\n(defn say-hello\n  #|[(s/? string?) =\u003e string?]\n  ([]     (say-hello \"world\"))\n  ([name] (str \"Hello, \" name \"!\"))) \n```\n\nOk, admittedly this is a rather stupid example, so here's one from the official\ndocs instead:\n\n```clojure\n;;; before:\n\n(s/fdef ranged-rand\n  :args (s/and (s/cat :start int? :end int?)\n               #(\u003c (:start %) (:end %)))\n  :ret int?\n  :fn (s/and #(\u003e= (:ret %) (-\u003e % :args :start))\n             #(\u003c (:ret %) (-\u003e % :args :end))))\n\n(defn ranged-rand [start end]\n  (- start (long (rand (- end start)))))\n\n\n;;; after:\n\n(defn ranged-rand\n  #|[start :- int?, end :- int?  =\u003e  int?\n     |-  (\u003c start end)\n     |=  (and (\u003e= % start) (\u003c % end))]\n  [start end]\n  (- start (long (rand (- end start))))) \n```\n\n\n\n## Setup\n\n[![Clojars Project](https://img.shields.io/clojars/v/speck.svg)](https://clojars.org/speck)\n\nAdd this to your `project.clj`:\n\n    [speck \"1.1.0\"]\n\nAfter that, you'll need to register a [reader tag][] for speck; you can do it by\ncreating a file named `data_readers.clj` in the root of your classpath (i.e. in\nthe `src` directory) with the following content:\n\n    {| speck.v1.core/speck-reader}\n\nAlternatively, you can register it from REPL by executing this code:\n\n    (set! *data-readers* (assoc *data-readers* '| #'speck.v1.core/speck-reader))\n\nIn order to enable :ret and :fn spec checking (**highly recommended**) you'll\nneed to add [orchestra][] too:\n\n    [orchestra \"2018.12.06-2\"] ; check its github page for the latest version info\n\nSpeck will try to automatically use it instead of vanilla instrumentation when\nit's available, but to make sure it's being used you can setup it manually:\n\n\n```clojure \n(ns your.app\n  (:require [speck.v1.core :as speck]\n            [orchestra.spec.test :as orchestra]\n            ...))\n    \n(alter-var-root #'speck/*auto-define-opts* assoc\n  :instrument-fn orchestra/instrument)\n```\n\nThat's it, you're good to go!\n\n[reader tag]: https://clojure.org/reference/reader#tagged_literals\n[orchestra]: https://github.com/jeaye/orchestra\n\n\n\n## Usage\n\nSo how does that work?\n\nJust put a `#|[...]` form inside your defn where the `attr-map` usually goes\n(after the name, but before the argument list) and you're done! Under the hood,\nthis will expand to `{:speck (| [...])}`, where `|` is the macro that generates\nthe fspec and attaches it to your function.\n\n\nThe features included are:\n\n- named and unnamed positional args specs\n- lightweight syntax for args and fn specs\n- arity overloading with separate return and fn specs for each arity\n- varargs, keyword args and optional args are supported too\n- automatic instrumentation\n- specs are automatically redefined on defn's recompilation\n\n\n### Syntax\n\nSkip to the next section if you want examples. The general syntax looks\nsomething like this:\n\n```clojure \n #|[arg-x       =\u003e ret-1  |- args-expr-1  |= fn-expr-1\n    arg-x arg-y =\u003e ret-2  |- args-expr-2  |= fn-expr-2\n    ...\n    opts*] \n```\n\nwhere\n\n- `arg-x` and `arg-y` can be either specs or triplets `name :- spec`; if no\n  names are given, default argument names `%1`, `%2`, etc are used\n\n- `_` is used to indicate zero-argument clause, i.e. `#|[_ =\u003e ret ...]`\n\n- `ret-1` and `ret-2` are ret specs for corresponding arities\n\n- `args-expr`s are boolean expressions used to generate :args specs for\n  corresponding arities; arguments are available by name\n\n- `fn-expr`s are similar to `args-expr`s, except they generate :fn specs and in\n  addition to the arguments the symbol `%` is available which refers to the\n  return value\n  \n- `opts*` are `s/fspec` arguments; `:gen` is passed directly and all other opts\n  are `s/and`ed to the corresponding specs\n\n\n### Examples\n\nYou can find some simple testable examples [here](/test/speck/v1/examples.clj); for a reference of\nall/most possible options check out the [test suite](/test/speck/v1/core_test.clj).\n\n```clojure\n\n;; basic rule is: input =\u003e output\n(defn abs\n  #|[number? =\u003e (s/and number? pos?)]\n  [x] ...)\n\n\n;; if there are no inputs, use `_`:\n(defn pandorandom\n  #|[_ =\u003e any?]\n  [] ...)\n\n\n;; you can add names to the arguments, though it is optional:\n(defn fraction\n  #|[numerator :- int?, denominator :- pos?  =\u003e  ratio?]\n  [num den] ...)\n\n\n;; specs are always matched with args based on their order (think s/cat):\n(defn rotate\n  #|[direction :- ::vec-2d, angle :- ::radians  =\u003e  ::vec-2d]\n  [{:keys [x y]} a]\n  ...)\n\n\n;; different arities can have different ret specs: \n(defn map\n  #|[fn? =\u003e ::transducer, fn? (s/+ seqable?) =\u003e seq?]\n  ([f] ...)\n  ([f coll \u0026 colls] ...))\n\n\n;; note that you can use `s/?` for optional args:\n(defn join\n  #|[(s/? any?) (s/coll-of any?) =\u003e string?]\n  ([coll] ...)\n  ([sep coll] ...))\n  \n\n;; use `s/keys*` for keyword args:\n(defn start-server\n  #|[fn? (s/keys* :opt-un [::host ::port])  =\u003e  ::server]\n  [handler \u0026 {:keys [host port]}]\n  ...)\n\n\n;; to check predicates against several args at once, use |- syntax:\n(defn interval\n  #|[start :- number?, end :- number?  =\u003e  ::interval\n     |- (\u003c start end)]\n  [a b] ...)\n\n\n;; note that unnamed args will get default names (%1, %2, ...)\n;; this is equivalent to the previous example:\n(defn interval\n  #|[number? number? =\u003e ::interval |- (\u003c %1 %2)]\n  [start end] ...)\n\n\n;; use |= to check invariants connecting the arguments and the return value;\n;; the return value is bound to the `%` symbol:\n(defn select-keys\n  #|[m :- map?, ks :- (s/coll-of any?)  =\u003e  map?\n     |=  (= (set ks) (set (keys %)))]\n  [m ks]\n  ...)\n\n\n;; unlike in clojure's anonymous functions, `%` is NOT the same as `%1`!\n;; this is equivalent to the previous example (but much more confusing):\n(defn select-keys\n  #|[map?, (s/coll-of any?)  =\u003e  map?\n     |=  (= (set %2) (set (keys %)))]\n  [m ks]\n  ...)\n\n\n;; finally, you can directly specify fspec opts, such as :gen...\n;; :args, :ret and :fn opts will be added to the corresponding specs via s/and:\n(defn frobnicate\n  #|[x? =\u003e foo?    ;-\u003e (s/and foo? qux?)\n     x? y? =\u003e baz? ;-\u003e (s/and baz? qux?)\n     :ret qux?]\n  ...)\n\n\n;; in fact, you can eschew the speck syntax completely and use vanilla clojure\n;; spec syntax if that's your thing:\n(defn abs\n  #|[:args (s/cat :x int?), :ret nat-int?]\n  ...)\n\n\n;; oh, and by the way, you can entirely bypass using the reader literal; this is\n;; more wordy, but can be useful when you need to attach more meta to your fn:\n;; (:require [speck.v1.core :as speck :refer [|]])\n(defn foo\n  {:speck (| [...])\n   :some-other meta}\n  ...)\n\n\n;; importantly, all of this can be used with any defn-like macro:\n(defn foo #|[...])\n(defn- foo #|[...])\n(defmacro foo #|[...])\n(defmulti foo #|[...])  ; but see https://clojure.atlassian.net/browse/CLJ-2450\n(defun foo #|[...])     ; from https://github.com/killme2008/defun\n(defroutes foo #|[...]) ; not sure why you'd want that... but you get the idea!\n\n```\n\n\n### Automatic redefining and instrumentation\n\nWhen you recompile a *specked* function, speck will detect that and redefine all\n*specks* in the same namespace (including the one you've just recompiled);\nbetter granularity cannot be achieved unfortunately, but it works good enough in\npractice.\n\nTo control this behavior (enable\\disable it, turn debug printing on and off,\netc) alter the var `speck.v1.core/*auto-define-opts*`.\n\nUpon redefining, the affected functions will also be instrumented using the\nfunctions specified under `:instrument-fn` in `*auto-define-opts*`.\n\nYou can manually (re)define the *specks* by calling `define-specs-in-current-ns`:\n\n    (speck/define-specs-in-current-ns {:instrument-fn orchestra/instrument})\n\n\n### Production mode\n\nSet `speck.v1.core/*prod-mode*` to a non-nil value in production. This will\neffectively remove all the `#|[...]` and `(| ...)` calls from your code. On\nload, `*prod-mode*` is set to the value of the environment variable\n`CLJ_SPECK_PROD_MODE`.\n\nAlso, you can change the tagged literal reader from `speck-reader` to\n`speck-reader-bypass` to eliminate all the `#|[...]` forms from your code.\n\n\n## FAQ\n\n#### Is this library stable?\n\nI consider v1 API to be pretty much finished, thus no major breaking changes\nshould happen here. Note however that spec itself is in alpha, so when the\n[next version](https://github.com/clojure/spec-alpha2/) will be released this\nlibrary *might* get a breaking v2 release too.\n\n\n#### Is ClojureScript supported? \n\n[Not yet](https://github.com/j-cr/speck/issues/2). Cljs supports static metadata\non vars, so the port should be pretty straightforward; I just haven't done it\nyet.\n\n\n#### My ret\\fn specs don't work, is that a bug?\n\nDefault clojure's `instrument` [only checks args specs](https://clojure.org/guides/spec#_instrumentation)\n(don't ask me why, I don't know); use [orchestra](https://github.com/jeaye/orchestra) instead.\n\n\n#### Isn't this library abusing the tagged literals feature?\n\nI guess so... but for a worthy cause though! (Right?) You can always use the\nlonger syntax `{:speck (| [...])}` if you so wish.\n\n\n#### [...but is there any advantage to that over a custom defn macro?](https://www.reddit.com/r/Clojure/comments/d7wx0h/new_better_version_of_speck_a_concise_syntax_for/f167hm9/)\n\nMy take on this is as follows: custom defn-wrapping macros don't compose, so as\nclojure ecosystem grows and new feature are developed you might face a situation\nwhere you have to choose between two (or more) incompatible defn\nwrappers. (Also, abstractions that compose poorly are just bad in general.)\n\nOn the other hand speck is compatible with any defn-like macro that produces a\nvar and accepts a metadata map. For example, you can use it with defmacro,\ndefmulti or custom 3rd-party macros like e.g. [defun](https://github.com/killme2008/defun).\n\nAlso, it's compatible with any libraries that extend your definitions in the\nsame way speck does, although admittedly you would have to use the longer syntax\nfor that:\n\n```clojure\n(defn foo\n  {:speck (| [...])\n   :shpec (something (completely different))}\n  ...)\n```\n\nIn the end, clojure does provide this elegant extension point via vars+metadata, so why not use that?\n\n\n#### Should I abuse this library by writing absurdly huge inline specs and never using vanilla `s/fdef` again even where it's more appropriate?\nNo.\n\n\n#### Is this library any good?\n[Yes.](https://news.ycombinator.com/item?id=3067434)\n\n\n\n## License\n\nCopyright © 2018-2019 https://github.com/j-cr\n\nDistributed under the Eclipse Public License either version 1.0 or (at\nyour option) any later version.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fj-cr%2Fspeck","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fj-cr%2Fspeck","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fj-cr%2Fspeck/lists"}