How Does YS Work?
What if I told you that...
- YS is made out of Java
- YS uses no JVM
- YS is a binary Executable
- YS is also a Shared Library
- YS is actually a Lisp
- YS can use S-Expressions
- YS prefers YeS-Expressions
- YS can use modules written in:
- YS
- Any other language
Make sense?
YS is Clojure in Disguise🔗
If you haven't heard of Clojure, it's a Lisp that runs on the JVM. It has a wonderful core library, and loads of external libraries. In fact, it can use any Java library as well.
When you run/load a YS YAML file, all the YAML is compiled to Clojure Lisp code.
That code is then evaluated by a Clojure runtime called SCI
.
If you were "loading" the file, then the last expression evaluated is serialized
and returned (or printed if you're using ys
).
The best part of this is that you can use the entire Clojure core library and other key Clojure libraries in YS.
GraalVM🔗
Normally Java (and thus Clojure) needs to be run on the JVM.
There is a project called GraalVM
that can compile
any jar files (JVM compiled code) to a native binary executable or shared
library.
YS uses this technology to compile to the ys
binary executable and the
libyamlscript.so
shared library.
The shared library is in turn bound to 11 (and counting) languages as a YS
capable YAML loader module.
Using Java in YS🔗
For the most part you never need to use or even know about Java when using YS. You don't need to know about Clojure either; at least at first. As you get better at YS you'll end up learning more about Clojure (and it's a pretty cool language to know). You'll also learn more about Lisp!
There are ways to call Java methods directly from YS, and there are certain times you might want to. Usually for performance reasons. I'll cover that in a future blog post.
At this stage, the only place you'll see Java at all is from error stack traces.
Let me show you an example:
$ ys -Se '1 / 0'
Error: java.lang.ArithmeticException: Divide by zero
at clojure.lang.Numbers.divide (Numbers.java:190)
ys.std$div.invokeStatic (std.clj:270)
ys.std$div.invoke (std.clj:267)
clojure.lang.AFn.applyToHelper (AFn.java:156)
clojure.lang.RestFn.applyTo (RestFn.java:135)
clojure.core$apply.invokeStatic (core.clj:667)
ys.std$div_PLUS_.invokeStatic (std.clj:363)
ys.std$div_PLUS_.doInvoke (std.clj:363)
clojure.lang.RestFn.invoke (RestFn.java:424)
sci.lang.Var.invoke (lang.cljc:202)
sci.impl.analyzer$return_call$reify__5210.eval (analyzer.cljc:1422)
...
sci.impl.interpreter$eval_string_STAR_.invokeStatic (interpreter.cljc:66)
sci.core$eval_string_PLUS_.invokeStatic (core.cljc:276)
yamlscript.runtime$eval_string.invokeStatic (runtime.clj:277)
yamlscript.cli$do_run.invokeStatic (cli.clj:373)
yamlscript.cli$do_main.invokeStatic (cli.clj:529)
yamlscript.cli$_main.invokeStatic (cli.clj:649)
yamlscript.cli$_main.doInvoke (cli.clj:643)
clojure.lang.RestFn.applyTo (RestFn.java:140)
yamlscript.cli.main (:-1)
java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit (LambdaForm$DMH:-1)
The -S
flag is used to show the stack trace on an error.
You can see java.lang...
on the first and last lines.
You also see a lot of Clojure and SCI in there.
The Secret is Out Now🔗
Whenever I give a talk about YS, or do a series on it, I try to hold off on mentioning Clojure or Java for a while.
It's quite unlikely that you took interest in YS as an alternative way to code in those languages (although you certainly could). The main point of YS is to be a better way to work with YAML.
But once I've let the cat out of the bag, it becomes much easier to explain what YS is and how it works. I'm looking forward to being able to explain things to you on a deeper level now.
Before I go, I mentioned that YS can use modules written in any language. It's true! YS can use modules written in Java and Clojure of course. But they can also be written in Python or Ruby!
Stay tuned!!!