Last active
December 15, 2015 15:49
-
-
Save headius/5285216 to your computer and use it in GitHub Desktop.
Alternate Clojure syntax (work in progress...and NOT an April Fool's joke)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://groups.google.com/group/clojure/msg/698cbdc7dc2f1ea4