{"id":31595268,"url":"https://github.com/escherize/tracks","last_synced_at":"2025-10-06T03:59:03.316Z","repository":{"id":57714079,"uuid":"69973480","full_name":"escherize/tracks","owner":"escherize","description":"Programming with shapes","archived":false,"fork":false,"pushed_at":"2019-09-30T16:23:17.000Z","size":147,"stargazers_count":167,"open_issues_count":1,"forks_count":3,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-09-10T16:46:37.067Z","etag":null,"topics":["clojure","clojurescript","shape","transformations"],"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/escherize.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":"2016-10-04T14:32:21.000Z","updated_at":"2025-01-27T07:19:14.000Z","dependencies_parsed_at":"2022-09-26T21:31:13.137Z","dependency_job_id":null,"html_url":"https://github.com/escherize/tracks","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/escherize/tracks","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/escherize%2Ftracks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/escherize%2Ftracks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/escherize%2Ftracks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/escherize%2Ftracks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/escherize","download_url":"https://codeload.github.com/escherize/tracks/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/escherize%2Ftracks/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278556181,"owners_count":26006081,"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-10-06T02:00:05.630Z","response_time":65,"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","clojurescript","shape","transformations"],"created_at":"2025-10-06T03:58:51.970Z","updated_at":"2025-10-06T03:59:03.311Z","avatar_url":"https://github.com/escherize.png","language":"Clojure","readme":"# tracks\n\n## Example based coding\n\n![Converging Tracks](https://raw.githubusercontent.com/escherize/tracks/master/tracks.jpg)\n[![Build Status](https://travis-ci.org/escherize/tracks.svg?branch=master)](https://travis-ci.org/escherize/tracks)\n\n\u003e We become what we behold. We shape our tools, and thereafter our tools shape us.\n\n\u003e ― Marshall McLuhan\n\n## Usage\n\nAdd the following line to your leiningen dependencies:\n\n[![Clojars Project](https://img.shields.io/clojars/v/tracks.svg)](https://clojars.org/tracks)\n\nRequire tracks in your namespace header:\n\n    (:require [tracks.core :as t :refer [track]])\n\n## Rationale\n\nThis is a library to handle _shapes_. What's a shape?\n\n    shape n.\n        - the correct or original form or contours of something.\n        - an example of something that has a particular form.\n\n    shape v.\n        - to give definite form, organization, or character to.\n\nIt's common to grapple with deeply nested arguments whose shapes are difficult to know without running the code. The data we love, tho pure and immutable can be nested and complex. This approach removes the cognitive burden needed to understand our datastructures.\n\n## Examples\n\n#### deftrack example:\n\nInstead of describing _how to do a transformation_, tracks allows the user to _create those transformations declaratively_.  This makes writing code that takes one shape and transforms them to another *dead simple*.\n\nLet's consider this data as our input. Typically this shape needs to be 'reverse-engineered' by reading and understanding code with `get-in`, destructuring, and other such operations.\n\n``` clojure\n(def buyer-information-map\n  {:buyer-info\n   {:is-guest true\n    :primary-contact {:name {:first-name \"Bob\" :last-name \"Ross\"}\n                      :phone {:complete-number \"123123123\"}\n                      :email {:email-address \"thebobguy@rossinator.com\"}}}})\n```\n\n\nNext, Let's create a function that takes this particular _shape_ and returns another representing a notification for a customer.\n\n\n```clojure\n(require '[tracks.core :as t :refer [deftrack]])\n\n(deftrack notify-buyer\n  {:buyer-info {:is-guest guest?                                    ;; 1\n                :primary-contact {:name {:first-name firstname\n                                         :last-name lastname}\n                                  :phone {:complete-number phone}\n                                  :email {:email-address email}}}}\n  (when guest?                                                      ;; 2\n    {:command :send-notification\n     :address email\n     :phone phone\n     :text (str \"Hi, \" firstname \" \" lastname)}))\n;; =\u003e #function[user/notify-buyer]\n\n(notify-buyer buyer-information-map)\n;; =\u003e {:command :send-notification\n;;     :address \"thebobguy@rossinator.com\"\n;;     :phone \"123123123\"\n;;     :text \"Hi, Bob Ross\"}\n```\n\n1. `deftrack` expects data of this shape\n2. `deftrack` returns this value\n\n### What is going on here?\n\nFor every symbol in the binding form to `deftrack` (1 above), `deftrack` generates a program to seamlessly write the get / get-in / assoc-in / assoc / etc. sort of accessing code and allows you to focus on __your data__.\n\n## Destructuring\n\nYou may be thinking to yourself: Clojure already has destructuring! That's true, let's compare using `deftrack` against `defn` style destructuring:\n\n``` clojure\n(deftrack notify-buyer\n  {:buyer-info {:is-guest guest?\n                :primary-contact {:name {:first-name firstname\n                                         :last-name lastname}\n                                  :phone {:complete-number phone}\n                                  :email {:email-address email}}}}\n  (when guest?\n    {:command :send-notification\n     :address email\n     :phone phone\n     :text (str \"Hi, \" firstname \" \" lastname)}))\n\n(defn notify-buyer-2 [{{guest? :is-guest,\n                        {{firstname :first-name, lastname :last-name} :name,\n                         {phone :complete-number} :phone,\n                         {email :email-address} :email}\n                        :primary-contact}\n                       :buyer-info}]\n  (when guest?\n    {:command :send-notification\n     :address email\n     :phone phone\n     :text (str \"Hi, \" firstname \" \" lastname)}))\n```\n\nI think you'd agree which of those is easier to read.\n\n### deftrack metadata\n\ndeftrack plays nice with arglists metadata, enabling your editor to explain what sort of shape a function created with `deftrack` takes.\n\n``` clojure\n(deftrack move-some-keys\n  {:a a :b b :c c :d {:e e}}\n  {:a b :b c :c e :d {:e a}})\n\n(move-some-keys {:a 1 :b 2 :c 3 :d {:e 4}})\n;; =\u003e {:a 2, :b 3, :c 4, :d {:e 1}}\n\n(:arglists (meta #'move-some-keys))\n;; =\u003e ([{a :a, b :b, c :c, {e :e} :d}])\n```\n\nSince we don't like to read deeply destructured arglists, `deftracks` also goes one step further, and includes what shape your function _expects_. (Todo: make this work with editors).\n\n``` clojure\n(:tracks/expects (meta #'move-some-keys))\n;; =\u003e {:a a, :b b, :c c, :d {:e e}}\n```\n\n#### let example\n\nFor more flexible flowing of data, here's __tracks/let__, which allows for the same data-oriented style but with multiple arguments, etc.\n\n``` clojure\n(require '[tracks.core :as t :refer [deftrack]])\n\n;; Please Notice: you usually don't get to see what some-data looks like! :)\n(def some-data\n  {:more-info {:price-for-this-order 10}\n   :order-info {:amount-bought-from-my-company 3}})\n\n;;in another part of your program:\n\n\n;; you can use t/let:\n(t/let [{:more-info {:price-for-this-order price}\n         :order-info {:amount-bought-from-my-company quantity}} some-data]\n  (* price quantity))\n;;=\u003e 30\n\n;; or you can use deftrack:\n(deftrack calculate-price-for-order\n  {:more-info {:price-for-this-order price}\n   :order-info {:amount-bought-from-my-company quantity}}\n  (* price quantity))\n\n(calculate-price-for-order some-data)\n;;=\u003e 30\n```\n\n### Arbitrary nesting levels\n\nDeep contemplation about deeply nested shapes is the old way.\n\n``` clojure\n(deftrack deeptx\n  {0 zero\n   1 one\n   2 two\n   3 three} ;; \u003c- deeptx takes a map with this shape\n  {:a zero\n   :b {:c one\n       :d {:e two\n           :f {:g three}}}} ;; \u003c- deeptx then returns one with this shape\n  )\n\n(deeptx {0 \"first\" 1 \"second\" 2 \"third\" 3 \"fourth\"})\n;;=\u003e {:a \"first\", :b {:c \"second\", :d {:e \"third\", :f {:g \"fourth\"}}}}\n```\n\n### Complex leaf values\n\nLet's simulate a game where there's an active player, and all other players wait in a queue to become the active one. Once a player has played their turn, they naturally go to the back of the queue.\n\n```clojure\n\n;;; Setup the function that moves around players,\n;;; no matter what datastructure the players are\n;;; represented as:\n\n(deftrack move-players\n  {:active-player p1 :players [p2 p3 p4]}\n  {:active-player p2 :players [p3 p4 p1]})\n\n;;; Here's the datastructure that represents the state of the game.\n;;; Notice that the players are more than scalar values!\n\n(defonce game (atom {:active-player {:name \"A\"}\n                     :players [{:name \"B\"}\n                               {:name \"C\"}\n                               {:name \"D\"}]}))\n\n(swap! game move-players)\n;;=\u003e  {:active-player {:name \"B\"}\n;;     :players [{:name \"C\"}\n;;               {:name \"D\"}\n;;               {:name \"A\"}]}\n\n(swap! game move-players)\n;;=\u003e  {:active-player {:name \"C\"}\n;;     :players [{:name \"D\"}\n;;               {:name \"A\"}\n;;               {:name \"B\"}]}\n\n\n(swap! game move-players)\n;;=\u003e  {:active-player {:name \"D\"}\n;;     :players [{:name \"A\"}\n;;               {:name \"B\"}\n;;               {:name \"C\"}]}\n\n```\n\n### Multiple endpoints\n\nLike a train track, sometimes one track can split into many. With `track` the values can be duplicated.\n\n``` clojure\n(deftrack one-to-many {:clone-me x} {:a x :b {:c [x x]}})\n\n(one-to-many {:clone-me \"?\"})\n\n;;=\u003e {:a \"?\", :b {:c [\"?\" \"?\"]}}\n```\n\n## Want more examples?\n\nCheck the test namespace!\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fescherize%2Ftracks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fescherize%2Ftracks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fescherize%2Ftracks/lists"}