• Stars
    star
    167
  • Rank 226,635 (Top 5 %)
  • Language
    Clojure
  • License
    Eclipse Public Li...
  • Created about 8 years ago
  • Updated about 5 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Programming with shapes

tracks

Example based coding

Converging Tracks Build Status

We become what we behold. We shape our tools, and thereafter our tools shape us.

― Marshall McLuhan

Usage

Add the following line to your leiningen dependencies:

Clojars Project

Require tracks in your namespace header:

(:require [tracks.core :as t :refer [track]])

Rationale

This is a library to handle shapes. What's a shape?

shape n.
    - the correct or original form or contours of something.
    - an example of something that has a particular form.

shape v.
    - to give definite form, organization, or character to.

It'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.

Examples

deftrack example:

Instead 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.

Let'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.

(def buyer-information-map
  {:buyer-info
   {:is-guest true
    :primary-contact {:name {:first-name "Bob" :last-name "Ross"}
                      :phone {:complete-number "123123123"}
                      :email {:email-address "[email protected]"}}}})

Next, Let's create a function that takes this particular shape and returns another representing a notification for a customer.

(require '[tracks.core :as t :refer [deftrack]])

(deftrack notify-buyer
  {:buyer-info {:is-guest guest?                                    ;; 1
                :primary-contact {:name {:first-name firstname
                                         :last-name lastname}
                                  :phone {:complete-number phone}
                                  :email {:email-address email}}}}
  (when guest?                                                      ;; 2
    {:command :send-notification
     :address email
     :phone phone
     :text (str "Hi, " firstname " " lastname)}))
;; => #function[user/notify-buyer]

(notify-buyer buyer-information-map)
;; => {:command :send-notification
;;     :address "[email protected]"
;;     :phone "123123123"
;;     :text "Hi, Bob Ross"}
  1. deftrack expects data of this shape
  2. deftrack returns this value

What is going on here?

For 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.

Destructuring

You may be thinking to yourself: Clojure already has destructuring! That's true, let's compare using deftrack against defn style destructuring:

(deftrack notify-buyer
  {:buyer-info {:is-guest guest?
                :primary-contact {:name {:first-name firstname
                                         :last-name lastname}
                                  :phone {:complete-number phone}
                                  :email {:email-address email}}}}
  (when guest?
    {:command :send-notification
     :address email
     :phone phone
     :text (str "Hi, " firstname " " lastname)}))

(defn notify-buyer-2 [{{guest? :is-guest,
                        {{firstname :first-name, lastname :last-name} :name,
                         {phone :complete-number} :phone,
                         {email :email-address} :email}
                        :primary-contact}
                       :buyer-info}]
  (when guest?
    {:command :send-notification
     :address email
     :phone phone
     :text (str "Hi, " firstname " " lastname)}))

I think you'd agree which of those is easier to read.

deftrack metadata

deftrack plays nice with arglists metadata, enabling your editor to explain what sort of shape a function created with deftrack takes.

(deftrack move-some-keys
  {:a a :b b :c c :d {:e e}}
  {:a b :b c :c e :d {:e a}})

(move-some-keys {:a 1 :b 2 :c 3 :d {:e 4}})
;; => {:a 2, :b 3, :c 4, :d {:e 1}}

(:arglists (meta #'move-some-keys))
;; => ([{a :a, b :b, c :c, {e :e} :d}])

Since 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).

(:tracks/expects (meta #'move-some-keys))
;; => {:a a, :b b, :c c, :d {:e e}}

let example

For more flexible flowing of data, here's tracks/let, which allows for the same data-oriented style but with multiple arguments, etc.

(require '[tracks.core :as t :refer [deftrack]])

;; Please Notice: you usually don't get to see what some-data looks like! :)
(def some-data
  {:more-info {:price-for-this-order 10}
   :order-info {:amount-bought-from-my-company 3}})

;;in another part of your program:


;; you can use t/let:
(t/let [{:more-info {:price-for-this-order price}
         :order-info {:amount-bought-from-my-company quantity}} some-data]
  (* price quantity))
;;=> 30

;; or you can use deftrack:
(deftrack calculate-price-for-order
  {:more-info {:price-for-this-order price}
   :order-info {:amount-bought-from-my-company quantity}}
  (* price quantity))

(calculate-price-for-order some-data)
;;=> 30

Arbitrary nesting levels

Deep contemplation about deeply nested shapes is the old way.

(deftrack deeptx
  {0 zero
   1 one
   2 two
   3 three} ;; <- deeptx takes a map with this shape
  {:a zero
   :b {:c one
       :d {:e two
           :f {:g three}}}} ;; <- deeptx then returns one with this shape
  )

(deeptx {0 "first" 1 "second" 2 "third" 3 "fourth"})
;;=> {:a "first", :b {:c "second", :d {:e "third", :f {:g "fourth"}}}}

Complex leaf values

Let'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.

;;; Setup the function that moves around players,
;;; no matter what datastructure the players are
;;; represented as:

(deftrack move-players
  {:active-player p1 :players [p2 p3 p4]}
  {:active-player p2 :players [p3 p4 p1]})

;;; Here's the datastructure that represents the state of the game.
;;; Notice that the players are more than scalar values!

(defonce game (atom {:active-player {:name "A"}
                     :players [{:name "B"}
                               {:name "C"}
                               {:name "D"}]}))

(swap! game move-players)
;;=>  {:active-player {:name "B"}
;;     :players [{:name "C"}
;;               {:name "D"}
;;               {:name "A"}]}

(swap! game move-players)
;;=>  {:active-player {:name "C"}
;;     :players [{:name "D"}
;;               {:name "A"}
;;               {:name "B"}]}


(swap! game move-players)
;;=>  {:active-player {:name "D"}
;;     :players [{:name "A"}
;;               {:name "B"}
;;               {:name "C"}]}

Multiple endpoints

Like a train track, sometimes one track can split into many. With track the values can be duplicated.

(deftrack one-to-many {:clone-me x} {:a x :b {:c [x x]}})

(one-to-many {:clone-me "?"})

;;=> {:a "?", :b {:c ["?" "?"]}}

Want more examples?

Check the test namespace!

More Repositories

1

huff

Juicy hiccup in pure Clojure
Clojure
87
star
2

bengine

A Simple Data-Driven static site generator
Clojure
35
star
3

bask

Get user input on the cli
Clojure
25
star
4

staplegun

Single file clipboard-manager
Clojure
13
star
5

restructure

function based restructuring of clj{s} data
Clojure
11
star
6

img_sql

Running sql update commands on images
Python
11
star
7

crajure

Clojure Craigslist client - please use responsibly
Clojure
8
star
8

defrag

Clojure
7
star
9

seagent-ui

Semantic UI wrapper for Reagent (hiccup?)
JavaScript
6
star
10

quick-question

enquirer.js bridge for babashka scripts
Clojure
6
star
11

random-avatar

for creating memorable(ish) random avatars
Clojure
6
star
12

data-desk

general datastructure building ui
Clojure
6
star
13

dredd

Automatically enforce judgements (tests)
Clojure
4
star
14

prelude-dotemacs

personal dotfiles to go with prelude.
YASnippet
3
star
15

scraping-talk

Code to go along with a talk about Enlive and Skyscraper
Clojure
3
star
16

historia

SQLite to remember what data functions expect to see
Clojure
3
star
17

streetlight

Tools to throw light onto deep and dark datastructures
Clojure
3
star
18

p-frame

Clojure
2
star
19

drytomic

The dry way to generate datomic entity relationship diagrams from edn schema files.
Clojure
2
star
20

malli-descriptions

Describe malli schemas in plain english
Clojure
2
star
21

memory-whole

Automatically document the data a functions recieve and return
Clojure
2
star
22

clj

Clojure
1
star
23

ngram

exploratory ngram anlysis
Clojure
1
star
24

rf-eight-cljc

some prototype thing
Clojure
1
star
25

cat_camp

catan boot-camp
JavaScript
1
star
26

rethinkdb_overview

Code for slides on clojure + rethinkdb
Clojure
1
star
27

weighter

Clojure
1
star
28

one-love-sample

example of error using one.love for clojure+rethinkdb
Clojure
1
star
29

convo

Clojure
1
star
30

janet-survivors

sort of a vampire survivor clone, in janet
Janet
1
star
31

elemental

The bare basics needed for smooth reagent based video game development.
JavaScript
1
star
32

tiny_runner

Runs a clojure(ish) file, and prints its output.
Clojure
1
star
33

personal_website

JavaScript
1
star
34

keelung

Clojure
1
star
35

dot-hammerspoon

hammerspoon conifg
Lua
1
star
36

cbmethod

You know, for coffee.
HTML
1
star
37

escherize.github.io

JavaScript
1
star
38

cljs-quiz

JavaScript
1
star
39

naughty-string-generator

Crash your programs in style.
Clojure
1
star
40

dotemacs

A customized emacs using el-get for clojure production code.
Emacs Lisp
1
star
41

wordless-fe

simple frontend for accessing and displaying the wordless project API
JavaScript
1
star
42

blackjack-api

its a toy
Clojure
1
star
43

blindfold

Clojure
1
star
44

python_crawler

A graph-y code analyzer
Python
1
star
45

conquire

A web server for realtime Q & A during a talk or lecture.
Clojure
1
star
46

el-get-hub

A Central Place to compare notes on El-Get.el recipes
CSS
1
star
47

save-time

Calculate how much time Fetchh saves you!
CSS
1
star
48

reddit-babble

Give a username, get back a markov chain. aka: What would /u/___ say?
Clojure
1
star
49

force-directed-graph

A custom force directed graph written in python, using pygame. The physics calculations were all
Python
1
star
50

clip_mon

Python
1
star
51

wordless

backend for finding the words you're looking for.
Clojure
1
star
52

awbc

implementation of advance wars in clojure
Clojure
1
star