Skip to content

Commit 90beb8e

Browse files
committed
blog: Add 2025-07-25.md
1 parent 51abe63 commit 90beb8e

1 file changed

Lines changed: 326 additions & 0 deletions

File tree

blog/2025-07-25.md

Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
---
2+
title: YS on the Go
3+
date: 2025-07-25
4+
draft: false
5+
authors: [ingydotnet]
6+
categories: [Summer-of-YS]
7+
edit: blog/2025-07-25.md
8+
comments: true
9+
---
10+
11+
[Yesterday](2025-07-24.md#whats-the-problem-with-graalvm) I hinted at the idea
12+
of YS hosted on Go.
13+
14+
This doesn't mean that YS would be rewritten in Go or that it wouldn't compile
15+
to Clojure.
16+
The Lisp is still essential to YS.
17+
18+
Where I was going was the possibility of creating a Clojure hosted on Go, which
19+
I've been thinking about for a while.
20+
21+
Go is the backbone of technologies like Kubernetes, where YS wants to provide
22+
a more powerful YAML experience.
23+
24+
As I was getting ready for bed, the word "Glojure" popped into my head.
25+
26+
What a perfect name for a Go hosted Clojure!
27+
28+
29+
<!-- more -->
30+
31+
32+
## Glojure
33+
34+
I had to see if someone had already thought of this.
35+
36+
A quick search revealed that there were actually 2 projects on GitHub called
37+
Glojure!
38+
39+
[One of them](https://github.com/glojurelang/glojure) looked quite promising, so
40+
I decided to take a look at it after I had slept.
41+
42+
This morning I was able to get a **YS program running on Glojure**!
43+
44+
Let's take a look at it in action, and then I'll explain how it works (and also
45+
how it doesn't).
46+
47+
The program is a YS implementation of the famous [fizzbuzz](
48+
https://rosettacode.org/wiki/FizzBuzz#YAMLScript) algorithm.
49+
50+
In this example we'll just print the first 16 numbers instead of the normal 100.
51+
52+
```bash
53+
$ ys -ce '
54+
say =: println
55+
defn rng(a b): range(a b:inc)
56+
defn or?(a b): a:empty?.if(b a)
57+
58+
doseq x (1 .. 16): !:say
59+
or?:
60+
str:
61+
zero?(x % 3).when("Fizz")
62+
zero?(x % 5).when("Buzz")
63+
=>: x
64+
' | glj
65+
#'user/say
66+
#'user/rng
67+
#'user/or?
68+
1
69+
2
70+
Fizz
71+
4
72+
Buzz
73+
Fizz
74+
7
75+
8
76+
Fizz
77+
Buzz
78+
11
79+
Fizz
80+
13
81+
14
82+
FizzBuzz
83+
16
84+
nil
85+
```
86+
87+
It works!
88+
89+
Right?
90+
91+
92+
## Glojure Basics
93+
94+
If you have a recent version of Go installed, `glj` (the Glojure binary) is
95+
easy to install and run:
96+
97+
```bash
98+
$ go install github.com/glojurelang/glojure/cmd/glj@latest
99+
$ echo '(println "Hello, World!")' | glj
100+
Hello, World!
101+
nil
102+
```
103+
104+
The `nil` was unexpected in this context.
105+
The Clojure command `clj` does this:
106+
107+
```bash
108+
$ clj -M -e '(println "Hello, World!")'
109+
Hello, World!
110+
```
111+
112+
But those are not quite the same.
113+
Glojure doesn't support the `-e` flag currently.
114+
115+
I feel like `glj` is starting a REPL session, and then evaluating the code.
116+
In that case, printing `nil` is expected.
117+
118+
I'm not fully up to speed on how Glojure works, but it doesn't matter for what
119+
I'm showing you today.
120+
121+
A more fair comparison would be:
122+
123+
```bash
124+
$ glj <<<'(println "Hello, World!")'
125+
Hello, World!
126+
nil
127+
$ clj <<<'(println "Hello, World!")'
128+
Clojure 1.12.1
129+
user=> Hello, World!
130+
nil
131+
user=>
132+
$
133+
```
134+
135+
So yep, they are both starting a REPL session, evaluating the code (which prints
136+
`Hello, World!` and returns `nil` (which the REPL correctly shows)), and then
137+
exiting.
138+
139+
140+
## How YS Works with Glojure
141+
142+
With normal YS you'd do this:
143+
144+
```bash
145+
$ ys -e '
146+
each x (1 .. 16): !:say
147+
or?:
148+
str:
149+
zero?(x % 3).when("Fizz")
150+
zero?(x % 5).when("Buzz")
151+
=>: x
152+
'
153+
1
154+
2
155+
Fizz
156+
4
157+
Buzz
158+
Fizz
159+
7
160+
8
161+
Fizz
162+
Buzz
163+
11
164+
Fizz
165+
13
166+
14
167+
FizzBuzz
168+
16
169+
```
170+
171+
The `ys -c` flag compiles the YS code to Clojure, so let's pipe it to `glj`:
172+
173+
```bash
174+
$ ys -c -e '
175+
each x (1 .. 16): !:say
176+
or?:
177+
str:
178+
zero?(x % 3).when("Fizz")
179+
zero?(x % 5).when("Buzz")
180+
=>: x
181+
' | glj
182+
unable to resolve symbol: each
183+
```
184+
185+
Well, Clojure (thus Glojure) doesn't have a `each` function.
186+
True.
187+
`each` is part of the `ys::std` library.
188+
189+
It's really just an alternate form for `doseq`.
190+
Let's try it with `doseq`:
191+
192+
```bash
193+
$ ys -c -e '
194+
doseq x (1 .. 16): !:say
195+
or?:
196+
str:
197+
zero?(x % 3).when("Fizz")
198+
zero?(x % 5).when("Buzz")
199+
=>: x
200+
' | glj
201+
unable to resolve symbol: rng
202+
```
203+
204+
Hmmm, `rng` isn't even used.
205+
206+
It's time to look at the Clojure code that `ys -c` generates:
207+
208+
```bash
209+
$ ys -c -e '
210+
doseq x (1 .. 16): !:say
211+
or?:
212+
str:
213+
zero?(x % 3).when("Fizz")
214+
zero?(x % 5).when("Buzz")
215+
=>: x
216+
'
217+
(doseq
218+
[x (rng 1 16)]
219+
(say
220+
(or?
221+
(str
222+
(when (zero? (rem x 3)) "Fizz")
223+
(when (zero? (rem x 5)) "Buzz"))
224+
x)))
225+
```
226+
227+
OK! There's `rng`. It looks like `..` compiles to `rng`.
228+
229+
!!! note
230+
The `ys::std/rng` is an alternate form for the standard Clojure `range`
231+
function.
232+
It includes the upper bound, which is often what you want.
233+
234+
There's also `or?` and `say`.
235+
Those aren't in Clojure/Glojure either.
236+
237+
It looks like we need a way to include the `ys::std` library in the Glojure
238+
runtime environment.
239+
240+
Well that's gonna take some thought and effort.
241+
242+
But we only need 4 missing functions for this example: `each`, `rng`, `or?`
243+
and `say`.
244+
245+
Let's see if we can just define them quickly in our YS 1-liner:
246+
247+
```bash
248+
$ ys -ce '
249+
each =: doseq
250+
defn rng(a b): range(a b:inc)
251+
defn or?(a b): a:empty?.if(b a)
252+
say =: println
253+
254+
each x (1 .. 16): !:say
255+
or?:
256+
str:
257+
zero?(x % 3).when("Fizz")
258+
zero?(x % 5).when("Buzz")
259+
=>: x
260+
' | glj
261+
can't take value of a macro: #'glojure.core/doseq
262+
#'user/rng
263+
#'user/or?
264+
#'user/say
265+
unable to resolve symbol: x
266+
```
267+
268+
It doesn't like us renaming `doseq` to `each`.
269+
270+
!!! note
271+
I tried for quite a while to get this to work.
272+
That part's not in the cards for today.
273+
We'll just use `doseq` for now.
274+
275+
276+
```bash
277+
$ ys -ce '
278+
defn rng(a b): range(a b:inc)
279+
defn or?(a b): a:empty?.if(b a)
280+
say =: println
281+
282+
doseq x (1 .. 16): !:say
283+
or?:
284+
str:
285+
zero?(x % 3).when("Fizz")
286+
zero?(x % 5).when("Buzz")
287+
=>: x
288+
' | glj
289+
#'user/say
290+
#'user/rng
291+
#'user/or?
292+
1
293+
2
294+
Fizz
295+
4
296+
Buzz
297+
Fizz
298+
7
299+
8
300+
Fizz
301+
Buzz
302+
11
303+
Fizz
304+
13
305+
14
306+
FizzBuzz
307+
16
308+
nil
309+
```
310+
311+
It works!
312+
313+
Just like it did at the start of the blog post. :smile:
314+
315+
We know why it prints `nil` at the end.
316+
The first 3 lines (`#'user/say` etc) are just the return values of the functions
317+
we defined.
318+
319+
320+
## Conclusion
321+
322+
I'm impressed with how well Glojure works even though it's still in its early
323+
days.
324+
325+
It feels very promising and I'm definitely going to push harder on getting it to
326+
be a runtime option for YS!

0 commit comments

Comments
 (0)