#bullshitlanguage
#bullshitlanguage
AnnotationScript
Java annotations with a LISP
Demo time!
│ EqualsVerifier │ jqno.nl │ jqno
#bullshitlanguage
Read more at jqno.nl/climate
Let’s do the opposite!
no annotations → only annotations
The weirdest yet!
Why not?
@Autowired
@Bean
@Column(name = "wat")
@Deprecated
@JsonAlias("whynot")
@PostMapping("/endpoint/wtf")
@Test
public void waitwhat() {}
I got excited with this one!
No ChatGPT
Except for the final demo
AnnotationScript
is
a LISP
LISP
(LISP)
((LISP))
(((LISP)))
(define (fizzbuzz x y)
(cond ((eq? (remainder x 15) 0) (display "FizzBuzz\n"))
((eq? (remainder x 3) 0) (display "Fizz\n"))
((eq? (remainder x 5) 0) (display "Buzz\n"))
(else (display x) (display "\n")))
(cond ((< x y) (fizzbuzz (+ x 1) y))
(else ())))
(fizzbuzz 1 100)
(+ x 10)
(+ x 10)
(+ x (* 2 5))
(+ x 10)
(+ x (* 2 5))
((if #t + -) x 10)
if (this.getId().equals(that.getId()))
(if (= (getId this) (getId that)))
public int sum(int n) {
int result = 0;
for (int i = 0; i < n; i++) {
result += i;
}
return result;
}
public int sum(int n) {
if (n == 0) {
return 0;
}
else {
return n + sum(n - 1);
}
}
(define (sum n)
(cond ((eq? n 0) 0)
(else (+ n (sum (- n 1))))))
java.lang.StackOverflowError
at java.base/java.util.Objects.hashCode(Objects.java:103)
at io.vavr.collection.HashArrayMappedTrieModule$AbstractNode.get(HashArrayMappedTrie.java:235)
at io.vavr.collection.HashMap.get(HashMap.java:615)
at nl.jqno.annotationscript.language.Environment.lookupOption(Environment.java:16)
at nl.jqno.annotationscript.language.Environment.lookup(Environment.java:20)
at nl.jqno.annotationscript.language.Evaluator.evaluateSymbol(Evaluator.java:53)
at nl.jqno.annotationscript.language.Evaluator.evaluate(Evaluator.java:21)
at nl.jqno.annotationscript.language.Evaluator.evaluateProc(Evaluator.java:117)
at nl.jqno.annotationscript.language.Evaluator.evaluate(Evaluator.java:45)
at nl.jqno.annotationscript.language.Evaluator.lambda$4(Evaluator.java:121)
at io.vavr.collection.Traversable.foldLeft(Traversable.java:493)
at nl.jqno.annotationscript.language.Evaluator.evaluateProc(Evaluator.java:120)
at nl.jqno.annotationscript.language.Evaluator.evaluate(Evaluator.java:45)
at nl.jqno.annotationscript.language.Evaluator.lambda$0(Evaluator.java:76)
at io.vavr.collection.Traversable.find(Traversable.java:458)
at nl.jqno.annotationscript.language.Evaluator.evaluateCond(Evaluator.java:76)
at nl.jqno.annotationscript.language.Evaluator.evaluate(Evaluator.java:33)
(sum 4)
(+ 4 (sum 3))
(+ 4 (+ 3 (sum 2)))
(+ 4 (+ 3 (+ 2 (sum 1))))
(+ 4 (+ 3 (+ 2 (+ 1 (sum 0)))))
(+ 4 (+ 3 (+ 2 (+ 1 0))))
(+ 4 (+ 3 (+ 2 1)))
(+ 4 (+ 3 3))
(+ 4 6)
10
Let’s re-write
(define (sum n)
(cond ((eq? n 0) 0)
(else (+ n (sum (- n 1))))))
(sum 4)
↓
(define (sum n acc)
(cond ((eq? n 0) acc)
(else (sum (- n 1) (+ n acc)))))
(sum 4 0)
(sum 4)
(+ 4 (sum 3))
(+ 4 (+ 3 (sum 2)))
(+ 4 (+ 3 (+ 2 (sum 1))))
(+ 4 (+ 3 (+ 2 (+ 1 (sum 0)))))
(+ 4 (+ 3 (+ 2 (+ 1 0))))
(+ 4 (+ 3 (+ 2 1)))
(+ 4 (+ 3 3))
(+ 4 6)
10
(sum 4 0)
(sum (- 4 1) (+ 4 0))
(sum 3 4)
(sum (- 3 1) (+ 3 4))
(sum 2 7)
(sum (- 2 1) (+ 2 7))
(sum 1 9)
(sum (- 1 1) (+ 1 9))
(sum 0 10)
10
What if…
recursion is not supported?
(define Y
(lambda (le)
((lambda (f) (f f))
(lambda (f)
(le (lambda (x) ((f f) x)))))))
No reasonable person can understand this
But it works
Demo time!
Annotations
Checked at runtime
You have a nice, compiled, strongly-typed language underneath:
Why not use it!
Weakly typed
@Autowired
@Bean
@Column(name = "wat")
@Deprecated
@JsonAlias("whynot")
@PostMapping("/endpoint/wtf")
@Test
public void waitwhat() {}
Stringly typed
@PreAuthorize("isFullyAuthenticated")
@PreAuthorize("isFullyAuthenticated()")
Hard to discover
Hard to debug
public @interface GetMapping {
/**
* Alias for {@link RequestMapping#name}.
*/
@AliasFor(annotation = RequestMapping.class)
String name() default "";
/**
* Alias for {@link RequestMapping#value}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] value() default {};
// ...
}
Slow
List<Class<?>> allTheClasses = scanTheClasspath(); // 👈 expensive!
for (Class<?> cls : allTheClasses) {
for (Annotation a : cls.getAnnotations()) {
Class<?> ann = a.getAnnotationType();
if (ann.getName().equals("SpringBootApplication")) {
// Start the container
}
}
}
Anyway
@Parenthesis("if", @Parenthesis("<", "x", "0"), "'a'", "'b'")
No nesting annotations!
@Open
@Symbol("if")
@Open@Symbol("<")@Symbol("x")@Symbol("0")@Close
@Symbol("'a'")
@Symbol("'b'")
@Close
Sure! I’ll group them for you
Open[] opens = { @Open, @Open };
Symbol[] symbols = { @Symbol("if"), @Symbol("x"), @Symbol("0"),
@Symbol("'a'"), @Symbol("'b'") };
Close[] closes = { @Close, @Close };
public @interface Syntax {} // 👈🏻 smart-ass!
public @interface Open extends Syntax {}
public @interface Symbol extends Syntax {}
public @interface Close extends Syntax {}
Syntax[] code = { @Open, @Symbol("if"), @Open, @Symbol("<"),
@Symbol("x"), @Symbol("0"), @Close, @Symbol("'a'"),
@Symbol("'b'"), @Close };
No extending annotations!
@Zero("if")
@Zero(list={@One("<"), @One("x"), @One("0")})
@Zero("'a'")
@Zero("'b'")
I see no issue with that!
Make as many as you like!
🥱
@Zero
to @Eleven
(if (< x 0) "a" "b")
(
if
(
<
x
0)
"a"
"b"
)
(
if
(
<
x
0 )
"a"
"b"
)
@Zero("if")
@Zero(list={
@One("<"),
@One("x"),
@One("0")})
@Zero("'a'")
@Zero("'b'")
public static class First {}
Implementation
Using Peter Norvig’s blog post:
@Zero("if")
@Zero(list={
@One("<"),
@One("x"),
@One("0")
})
@Zero("'a'")
@Zero("'b'")
@Zero("if") // 'if'
@Zero(list={ // '('
@One("<"), // '<'
@One("x"), // 'x'
@One("0") // '0'
}) // ')'
@Zero("'a'") // 'a'
@Zero("'b'") // 'b'
DONE! 🥳
0
, 'a'
) remain the samedefine
, if
, <
)
are wrapped in Symbol
class(
starts a sub-list)
ends a sub-listList<Object> ast = List.of(
Symbol("if"),
List.of(Symbol("<"), Symbol("x"), 0),
"a",
"b");
(define x 10)
(define x 10)
Is it an Atom?0, 'a'
↓
Return it
Is it a Symbol?if
, <
↓
Look up in Environment
Return it
Is it a List?(< x 0)
↓
Evaluate all elements
Call function
Return result
Evaluator works recursively
stack overflow after ~60
Use Peter Norvig’s second blog post:
Instead of this:
public Object evaluate(Object expression) {
// ...
if (isAtom(expression)) {
return evaluateAtom(expression);
}
if (isProc(expression)) {
var evaluated = evaluateProc(expression);
return evaluate(evaluated); // 👈 recursive call
}
}
We do this:
public Object evaluate(Object expression) {
var exp = expression;
while (true) {
// ...
if (isAtom(exp)) {
return evaluateAtom(expression);
}
if (isProc(exp)) {
exp = evaluateProc(exp); // 👈 loop
}
}
}
No more stack overflow 🥳
Interlude
@Zero("begin")
@Zero(list={@One("define"), @One("fizz-buzz"), @One(list={@Two("lambda"),
@Two(list=@Three("n")), @Two(list={@Three("cond"),
@Three(list={@Four("="), @Four(list={@Five("%"), @Five("n"),
@Five("15")}), @Four("0")}), @Three("'fizzbuzz'"),
@Three(list={@Four("="), @Four(list={@Five("%"), @Five("n"),
@Five("3")}), @Four("0")}), @Three("'fizz'"),
@Three(list={@Four("="), @Four(list={@Five("%"), @Five("n"),
@Five("5")}), @Four("0")}), @Three("'buzz'"),
@Three("else"), @Three("n")})})})
@Zero(list={@One("map"), @One("println"), @One(list={@Two("map"),
@Two("fizz-buzz"), @Two(list={@Three("range"),
@Three("1"), @Three("101")})})})
public class FizzBuzz {}
(begin
(define fizz-buzz (lambda (n)
(cond (= (% n 15) 0) 'fizzbuzz')
(cond (= (% n 3) 0) 'fizz')
(cond (= (% n 5) 0) 'buzz')
(else n))
(map println (map fizz-buzz (range 1 101))))
String code = """
(begin
(define fizz-buzz (lambda (n)
(cond (= (% n 15) 0) 'fizzbuzz')
(cond (= (% n 3) 0) 'fizz')
(cond (= (% n 5) 0) 'buzz')
(else n))
(map println (map fizz-buzz (range 1 101))))""";
String code = """
(begin
(define fizz-buzz (lambda (n)
(cond (= (% n 15) 0) 'fizzbuzz')
(cond (= (% n 3) 0) 'fizz')
(cond (= (% n 5) 0) 'buzz')
(else n))
(map println (map fizz-buzz (range 1 101))))""";
return code
.replace("(", " ( ")
.replace(")", " ) ")
String code = """
(begin
(define fizz-buzz (lambda (n)
(cond (= (% n 15) 0) 'fizzbuzz')
(cond (= (% n 3) 0) 'fizz')
(cond (= (% n 5) 0) 'buzz')
(else n))
(map println (map fizz-buzz (range 1 101))))""";
return code
.replace("(", " ( ")
.replace(")", " ) ")
.split(" ");
(recurse
Demo time!
Unit tests written in Java, not AnnotationScript
AnnotationScript evaluates to regular Java objects
Unit tests: identical!
Unit tests: identical!
(define (sum
(lambda (i)
(cond
((eq? i 0) 0)
(else
(+ i (sum (sub1 i)))))))
(sum 4))
(define (sum
(lambda (i)
(cond
((eq? i 0) 0)
(else
(+ i (sum (sub1 i)))))))
(sum 4))
cond has no true branch
(define (sum
(lambda (i)
(cond
((eq? i 0) 0)
(else
(+ i (sum (sub1 i)))))))
(sum 4))
Invalid identifier: sum
(define (sum
(lambda (i)
(cond
((eq? i 0) 0)
(else
(+ i (sum (sub1 i)))))))
(sum 4))
Y Combinator ???
(define (sum
(lambda (i recurse)
(cond
((eq? i 0) 0)
(else
(+ i (recurse (sub1 i) recurse))))))
(sum 4 sum))
Solution: function is parameter to itself
(recurse
I promised
But how to prove it?
Implement one!
…
Brainfuck!
Demo time!
))
What’s next
Error handling
cond has no true branch
“[It] expects the programmer to be perfect”
String.split
"'Hello world'" → "'Hello" + "world'"
Spring integration
🤪
Conclusion
Learn about annotations
Learn about LISP
It was fun
Greenspun’s Tenth Rule:
Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.
#bullshitlanguage
Fin
Image credits
annotationscript | by moinart on Fiverr |
architecture-1 | by Jan Ouwens |
architecture-2 | by Jan Ouwens |
background | by Jan Ouwens |
bored | by DreamStudio (Stable Diffusion) with prompt “Nerd behind laptop looking bored. Many Salvador Dali melting clocks.” |
climate-change | by DreamStudio (Stable Diffusion) with prompt “Laptop with image of a coal plant emitting thick black smoke. It’s on a table next to a pile of books and a coffee cup. In the background lush springtime forest” |
code-generator | by DreamStudio (Stable Diffusion) with prompt “Monkey typing at a computer” |
comics-0 | by Jan Ouwens - picture of “Asterix de Galliër” by René Goscinny & Albert Uderzo |
comics-1 | by Jan Ouwens - picture of a stack of comic books with on top: “Asterix de Galliër” by René Goscinny & Albert Uderzo |
comics-2 | by Jan Ouwens - picture of a stack of comic books with on top: “Gilles de Geus - De Spaanse furie” by Hanco Kolk |
comics-3 | by Jan Ouwens |
comics-4 | by Jan Ouwens - picture of two comic books: “Astérix Légionnaire” by René Goscinny & Albert Uderzo, and “Gilles de Geus - De revue” by Hanco Kolk & Peter de Wit |
discoverability | Screenshot from IntelliJ IDEA, taken by Jan Ouwens |
duke-says-no | by Jeff Dinkins for Sun/Oracle - source |
duke-says-yes & duke-says-yes-background | by Jeff Dinkins for Sun/Oracle - source |
environment-1 | by Jan Ouwens |
environment-2 | by Jan Ouwens |
escalation | by DreamStudio (Stable Diffusion) with prompt “A tsunami of snow coming from a mountain, a person running away from it, powdered snow in the air” |
evaluator | by Jan Ouwens |
fun | by dreamstudio (stable diffusion) with prompt “childrens drawing of stick fligure children playing in a school playground with a swing, a slide and a tree” |
hackernews | Screenshot from Hacker News, taken by Jan Ouwens on 2023-09-11 |
jan-ouwens | by Riemke Ouwens |
lego-0 | by Jan Ouwens |
lego-1 | by Jan Ouwens |
lego-2 | by Jan Ouwens |
lego-3 | by Jan Ouwens |
lego-4 | Screenshot from my website, taken by Jan Ouwens on 2023-09-30 |
lego-5 | Screenshot from Rebrickable, taken by Jan Ouwens on 2023-09-29 |
lego-6 | by Jan Ouwens |
lockdown | by DreamStudio (Stable Diffusion) with prompt “fat nerdy person at computer. man cave. messy room with parcels, stacks of books, empty pizza boxes, gadgets” |
meta-evaluator & meta-parser & meta-tokenizer | by Jan Ouwens |
mind-blown-1 | by DreamStudio (Stable Diffusion) with prompt “Person looking at a computer screen, very surprised, wide eyes, open mouth” |
mind-blown-2 | by DreamStudio (Stable Diffusion) with prompt “Person looking at a computer screen, very surprised, wide eyes, open mouth” |
nerd-cred | by DreamStudio (Stable Diffusion) with prompt “Rap music album cover in the style of Eminem, 50ct and notorious BIG. Photograph one face of fat, white, nerdy guy with large beard, messy hair, jewelry and colored glasses. Fish eye lens. Album title”nerd cred” appears next to face” |
paralleljava-0 | by Jan Ouwens |
paralleljava-1 | by Jan Ouwens |
paralleljava-2 | Screenshot from GitHub taken by Jan Ouwens on 2023-09-29 |
paralleljava-3 | Screenshot from Todo-Backend taken by Jan Ouwens |
paralleljava-4 | by J-Fall - source |
parser | by Jan Ouwens |
rite-of-passage | by DreamStudio (Stable Diffusion) with prompt “Graduation ceremony. Fat nerdy man with glasses and unkempt beard on stage.” |
stackoverflow | Screenshot from StackOverflow, taken by Jan Ouwens on 2023-09-11 |
the-little-schemer | by MIT Press - source |
tokenizer | by Jan Ouwens |
turing-machine | by DreamStudio (Stable Diffusion) with prompt “A sewing machine sewing a long, straight, white ribbon that must not be bundled up” |
vim | by Jan Ouwens |
Enterprise Lego model | designed by MiniTrekMocs |
No animals were harmed during the making of this slide
deck.
Except bugs. Bugs were squashed without
hesitation.