{"id":26824026,"url":"https://github.com/willmcpherson2/question","last_synced_at":"2025-04-28T11:45:39.054Z","repository":{"id":207542635,"uuid":"719504202","full_name":"willmcpherson2/question","owner":"willmcpherson2","description":"Pattern matching for Clojure","archived":false,"fork":false,"pushed_at":"2024-06-02T11:29:31.000Z","size":33,"stargazers_count":29,"open_issues_count":2,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-30T09:17:36.692Z","etag":null,"topics":["clojure","library","match"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/willmcpherson2.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-11-16T10:04:29.000Z","updated_at":"2024-12-29T00:23:18.000Z","dependencies_parsed_at":"2023-11-16T11:25:43.676Z","dependency_job_id":"f034aa6f-fe06-40b2-90b9-00ed2380f26a","html_url":"https://github.com/willmcpherson2/question","commit_stats":null,"previous_names":["willmcpherson2/question"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willmcpherson2%2Fquestion","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willmcpherson2%2Fquestion/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willmcpherson2%2Fquestion/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willmcpherson2%2Fquestion/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/willmcpherson2","download_url":"https://codeload.github.com/willmcpherson2/question/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251308702,"owners_count":21568697,"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","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","library","match"],"created_at":"2025-03-30T09:17:40.746Z","updated_at":"2025-04-28T11:45:39.010Z","avatar_url":"https://github.com/willmcpherson2.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# question\n\nQuestion is a pattern matching library for Clojure.\n\nThe primary macro is `?`.\n\n```\nquestion.core/?\n([arg \u0026 clauses])\nMacro\n  Takes an argument and a set of pattern/body pairs.\n\n  A pattern can be any of the following:\n\n  - The symbol _, which just returns the body.\n  - A symbol, which is bound to the argument in the body.\n  - A seqable, where each element will be pattern matched with the\n  corresponding elements in the argument. The seqable types must\n  match, unless the pattern has type Any.\n  - The symbol \u0026 within a seqable, which must be followed by a single\n  pattern which will be pattern matched with the rest of the sequence.\n\n  Any other pattern will be tested for equality with the argument. If\n  false, the next pattern is tested. If no patterns match, nil is\n  returned.\n\n  Patterns are evaluated at compile-time.\n\n  Examples: https://github.com/willmcpherson2/question/blob/main/README.md#examples\n```\n\n## Examples\n\n```clojure\n(ns examples.core\n  (:require [question.core :refer [? _ \u0026 any]]))\n\n;; No patterns, so always nil\n(? 1)\n;=\u003e nil\n\n;; Argument is ignored, so always :something\n(? 1\n   _ :something)\n;=\u003e :something\n\n;; (= 1 1), so :one\n(? 1\n   1 :one)\n;=\u003e :one\n\n;; First branch fails, second succeeds\n(? 2\n   1 :one\n   2 :two)\n;=\u003e :two\n\n(? [1 2]\n   (list 1 2) :list-1-2 ;; Sequence types must match\n   [1] :vec-1           ;; Every element must be present\n   [1 3] :vec-1-3       ;; Every element must be equal\n   [1 2 3] :vec-1-2-3   ;; No excess elements\n   [1 2] :vec-1-2)\n;=\u003e :vec-1-2\n\n;; Use rest syntax if length doesn't matter\n(? [1 2 3]\n   [1 \u0026 _] :starts-1)\n;=\u003e :starts-1\n\n;; The Any type matches any seqable\n(? [1 2 3]\n   (any 1 2 3) :seqable)\n;=\u003e :seqable\n\n;; Symbols are bound in the body\n(? [1 2 3]\n   ['x 'y 3] (+ x y))\n;=\u003e 3\n\n;; Quoting the whole pattern can be easier\n(? [1 2 3]\n   '[x y 3] (+ x y))\n;=\u003e 3\n\n;; Syntax-quote works too\n(? [1 2 3]\n   `[x y 3] (+ x y))\n;=\u003e 3\n\n;; Splitting a sequence\n(? [1 2 3]\n   '[x \u0026 xs] {:first x, :rest xs})\n;=\u003e {:first 1, :rest '(2 3)}\n\n;; Patterns are evaluated at compile-time\n(? [1 2 3]\n   [1 2 (+ 1 2)] :ok)\n;=\u003e :ok\n\n;; def variables are available at compile-time\n(def three 3)\n(? [1 2 3]\n   [1 2 three] :ok)\n;=\u003e :ok\n\n;; You can even apply functions to patterns\n(? '(1 2 3)\n   (reverse '(x 2 1)) x)\n;=\u003e 3\n\n;; If a pattern fails, its body will not be evaluated\n(? 2\n   1 (throw (Exception. \"evaluated!\"))\n   2 :ok\n   3 (throw (Exception. \"evaluated!\")))\n;=\u003e :ok\n```\n\n## Comparison with [`core.match`](https://github.com/clojure/core.match) (version 1.0.1)\n\nThis section compares `match` from `core.match` with `?` from `question`.\n\n### No matching clause\n\nIf no clause matches the argument, `match` will throw an `IllegalArgumentException`. `?` will return `nil`.\n\n### `:else`\n\n`match` uses wildcards `_` and `:else`. `?` just has wildcards.\n\n### Binding\n\n`match` allows binding with `x`. `?` uses the quoted form `'x`.\n\n### Evaluation in patterns\n\n`?` will evaluate patterns at compile time. `x` is evaluated which is why you need `'x` to bind.\n\n### Locals\n\n`match` will actually resolve `x` if it's defined locally. Otherwise, it binds. In this example, `String` is a bind, so it always matches:\n\n```clojure\n(match (type 1)\n       String :string\n       Long :long)\n;=\u003e :string\n```\n\nBut here, `string` is resolved to the value `java.lang.String`, so it doesn't match:\n\n```clojure\n(let [string String\n      long Long]\n  (match (type 1)\n         string :string\n         long :long))\n;=\u003e :long\n```\n\nBut if the definition is not local, it's still a bind:\n\n```clojure\n(def string String)\n(def long Long)\n(match (type 1)\n       string :string\n       long :long)\n;=\u003e :string\n```\n\nThis can be confusing if you intended to bind but accidentally used something in scope.\n\n`?` will simply always evaluate unquoted symbols:\n\n```clojure\n(? (type 1)\n   String :string\n   Long :long)\n;=\u003e :long\n```\n\nWhich will result in an `UnsupportedOperationException` if you try to use a local variable:\n\n```clojure\n(let [string String\n      long Long]\n  (? (type 1)\n     string :string\n     long :long))\n;; Unexpected error (UnsupportedOperationException) macroexpanding ?\n;; Can't eval locals\n```\n\nThis is because locals exist at run time and can't be evaluated at compile time.\n\nhttps://github.com/clojure/core.match/wiki/Basic-usage#locals\n\n### Variable shadowing\n\nVariable shadowing doesn't really work with `match` since locals will be resolved:\n\n```clojure\n(let [x 1]\n  (match 3\n         0 \"zero\"\n         x (str x)))\n;; Execution error (IllegalArgumentException)\n;; No matching clause: 3\n```\n\nVariable shadowing works as expected with `?`:\n\n```clojure\n(let [x 1]\n  (? 3\n     0 \"zero\"\n     'x (str x)))\n;=\u003e \"3\"\n```\n\nhttps://clojure.atlassian.net/browse/MATCH-126\n\n### Qualified names\n\n`?` allows class names and qualified names.\n\n```clojure\n(? (type 1)\n   java.lang.Short :short\n   java.lang.Long :long)\n;=\u003e :long\n\n(? 2147483647\n   Short/MAX_VALUE :short\n   Integer/MAX_VALUE :long)\n;=\u003e :long\n```\n\nhttps://clojure.atlassian.net/browse/MATCH-130\n\n### Vector arguments\n\n`match` will treat a vector as multiple arguments. For example, these are fine:\n\n```clojure\n(match [1]\n       [1] :vector)\n;=\u003e :vector\n\n(match {:a 1}\n       {:a 1} :map)\n;=\u003e :map\n```\n\nBut this is a syntax error:\n\n```clojure\n(match [1]\n       [1] :vector\n       {:a 1} :map)\n;; Unexpected error (AssertionError) macroexpanding match\n;; Pattern row 2: Pattern rows must be wrapped in []. Try changing {:a 1} to [{:a 1}].\n```\n\nBut if the vector is in a variable, it's valid:\n\n```clojure\n(let [x [1]]\n  (match x\n       [1] :vector\n       {:a 1} :map))\n;=\u003e vector\n```\n\n`?` has no special case for vectors:\n\n```clojure\n(? [1]\n   [1] :vector\n   {:a 1} :map)\n;=\u003e vector\n```\n\n### Lists\n\nTo match a list using `match`, you need `:seq` combined with `:guard`:\n\n```clojure\n(match (list 1 2 3)\n  [1 2 3] :vector\n  (([1 2 3] :seq) :guard #(list? %)) :list)\n;=\u003e :list\n```\n\nWith `?`, a list is a valid pattern:\n\n```clojure\n(? (list 1 2 3)\n   [1 2 3] :vector\n   (list 1 2 3) :list)\n;=\u003e :list\n```\n\nQuote syntax is also valid:\n\n```clojure\n(? (list 1 2 3)\n   [1 2 3] :vector\n   '(1 2 3) :list)\n;=\u003e :list\n```\n\nhttps://github.com/clojure/core.match/wiki/Basic-usage#sequential-types\n\nhttps://github.com/clojure/core.match/wiki/Basic-usage#guards\n\nhttps://clojure.atlassian.net/browse/MATCH-103\n\n### Maps\n\n`match` treats map patterns as subsets (unless the `:only` modifier is present):\n\n```clojure\n(match {:a 1, :b 2}\n       {:a 1} :ok)\n;=\u003e :ok\n```\n\n`?` doesn't give maps special treatment:\n\n```clojure\n(? {:a 1, :b 2}\n   {:a 1} :ok)\n;=\u003e nil\n```\n\nhttps://github.com/clojure/core.match/wiki/Basic-usage#map-patterns\n\n### Map keys\n\n`match` can't handle certain key types, including: `Boolean`, `Long`, `PersistentVector`, `Symbol`.\n\n```clojure\n(match {1 :b}\n       {1 :b} :ok)\n;; Unexpected error (ClassCastException) macroexpanding match\n;; class java.lang.Long cannot be cast to class clojure.lang.Named\n```\n\nhttps://stackoverflow.com/questions/72150186/clojure-core-match-on-nested-map\n\nhttps://clojure.atlassian.net/browse/MATCH-107\n\n### Pattern abstraction\n\nBecause patterns are evaluated, `?` lets you abstract over patterns:\n\n```clojure\n(defn pair [x]\n  [x x])\n\n(? [1 1]\n   (pair 1) :ok)\n;=\u003e :ok\n```\n\n`match` can be extended by other means: [Advanced usage](https://github.com/clojure/core.match/wiki/Advanced-usage)\n\n### Optimisations\n\n`match` implements some advanced optimisations, whereas `?` is more basic.\n\nhttps://github.com/clojure/core.match/wiki/Understanding-the-algorithm\n\n### Features\n\n`match` has additional features like `:or`.\n\nhttps://github.com/clojure/core.match/wiki/Basic-usage#or-patterns\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwillmcpherson2%2Fquestion","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwillmcpherson2%2Fquestion","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwillmcpherson2%2Fquestion/lists"}