public
Last active

Alternate Clojure syntax (work in progress...and NOT an April Fool's joke)

  • Download Gist
gistfile1.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
The majority of parens in Clojure are redundant if we just say that all
n > 1 tokens in a line are arguments to the first token. Parens can be
added in a more C-style to disambiguate within a line. Newline plus
indent expands argument list over multiple lines and each of those
lines gets the original "one-line" treatment. Introducing an "ignore
indent" sigil allows sugaring up the structure. The majority of calls
can be expressed in one line with no parens at all.
 
Hello world:
 
defn hello fn([] "Hello world")
hello
 
Partially expanded:
 
defn hello
fn [] "Hello world"
hello
 
Completely expanded (1ish node per line:
 
defn
hello
fn
[]
"Hello world"
hello
 
Immutable data structure example (mostly deleting parens):
 
let [my-vector [1 2 3 4]
my-map {:fred "ethel"}
my-list list(4 3 2 1)]
list
conj my-vector 5
assoc my-map :ricky "lucy"
conj my-list 5
;the originals are intact
my-vector
my-map
my-list
 
Completely expanded (showing indent and expansion of arrays)
 
let
[
my-vector
[
1
2
3
4
my-map
{
:fred
"ethel"
my-list
list
4
3
2
1
list
conj
my-vector
5
assoc
my-map
:ricky
"lucy"
conj
my-list
5
;the originals are intact
my-vector
my-map
my-list
 
Part of a bowling game kata:
 
0. original code
 
(defn score-game [rolls]
(loop [[frame left-rolls] (next-frame (parse-game rolls))
score 0]
(cond
(not (seq frame)) score
(spare? frame) (recur (next-frame left-rolls)
(+ score 10 (first left-rolls)))
(strike? frame) (recur (next-frame left-rolls)
(+ score 10 (first left-rolls) (second left-rolls)))
:else (recur (next-frame left-rolls)
(apply + score frame)))))
 
1. medium version using indent
 
defn score-game [rolls]
loop [[frame left rolls] next-frame(parse-game(rolls)) score 0]
cond
not seq(frame)
score
spare? frame
recur next-frame(left-rolls) +(score 10 first(left-rolls))
strike? frame
recur next-frame(left-rolls) +(score 10 first(left-rolls) second(left-rolls))
:else
recur next-frame(left-rolls) apply(+ score frame)
 
2. long, fewer parens
 
defn score-game [rolls]
loop
[[frame left rolls] next-frame(parse-game(rolls)) score 0]
cond
not seq(frame)
score
spare? frame
recur
next-frame left-rolls
+ score 10 first(left-rolls)
strike? frame
recur
next-frame left-rolls
+ score 10 first(left-rolls) second(left-rolls)
:else
recur
next-frame left-rolls
apply + score frame
 
3. short, more parens
 
defn score-game [rolls]
loop [[frame left rolls] next-frame(parse-game(rolls)) score 0]
cond
not(seq(frame)) score
spare?(frame) recur(next-frame(left-rolls) +(score 10 first(left-rolls)))
strike?(frame) recur(next-frame(left-rolls) +(score 10 first(left-rolls) second(left-rolls)))
:else recur(next-frame(left-rolls) apply(+ score frame))
 
4. one long line, most parens
 
defn score-game [rolls] loop([[frame left rolls] next-frame(parse-game(rolls)) score 0] cond(not(seq(frame)) score spare?(frame) recur(next-frame(left-rolls) +(score 10 first(left-rolls))) strike?(frame) recur(next-frame(left-rolls) +(score 10 first(left-rolls) second(left-rolls))) :else recur(next-frame(left-rolls) apply(+ score frame))))
 
5. medium, closer to original; => means "ignore indent", pure sugar
 
defn score-game [rolls]
loop [[frame left rolls] next-frame(parse-game(rolls)) score 0]
cond
not(seq(frame))
=> score
spare?(frame)
=> recur next-frame(left-rolls) +(score 10 first(left-rolls))
strike?(frame)
=> recur next-frame(left-rolls) +(score 10 first(left-rolls) second(left-rolls))
:else
=> recur next-frame(left-rolls) apply(+ score frame)
 
6. super long, 1ish node per line, minimum parens
 
defn
score-game
[
rolls
loop
[
[
frame
left
rolls
next-frame
parse-game
rolls
score
0
cond
not
seq
frame
score
spare?
frame
recur
next-frame
left-rolls
+
score
10
first
left-rolls
strike?
frame
recur
next-frame
left-rolls
+
score
10
first
left-rolls
second
left-rolls
:else
recur
next-frame
left-rolls
apply
+
score
frame
 
A simple macro. Nothing about this syntax loses homoiconity. It
it simply a different way to delimit sexps.
 
defmacro on-debug [& body]
`when DEBUG
do ~@body

On line 101 for example, why doesn't the sugar expand to a call to score with zero arguments?

@technomancy Consider it a thought exercise. All I'm doing here is delimiting sexps a different way; they're still there, line by line.

@tomjack score is defined in the loop preamble. It got lost in my initial translation, but I've fixed that now. You may have found an ambiguity, though, between a simple value and applying a zero-arg function; latter might need () to apply.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.