yamlscript 2024-05-29

I just finished writing a YAMLScript program for: https://rosettacode.org/wiki/Weird_numbers

$ time ys weird.ys
The first 25 weird numbers:
70 836 4030 5830 7192 7912 9272 10430 10570 10792 10990 11410 11690 12110 12530 12670 13370 13510 13790 13930 14770 15610 15890 16030 16310 

real    0m1.195s
The YS code is: https://gist.github.com/ingydotnet/5ea3299279aeff3abf875df5a605c1f5 There wasn't an existing Clojure entry so I ported my entry from the Ruby one: https://gist.github.com/ingydotnet/246142af10c2a70a421bd24bb220b9d9 This might seem like cheating but my goal was not to solve the problem but to see how well I could express it in YS. This turned out to be a very useful exercise because even though the result looks reasonable, there are several things about it that annoy my adhd brain. I'd like to talk about them in a thread...

🧵 2

The elephant in the room is the 2s runtime. Ruby is 10x faster, and ruby isn't fast. I suspect that most of the time is spent manipulating 17000 entry vector. I wanted to use a Java array but YS has a compile issue currently with the clojure . interop

https://gist.github.com/ingydotnet/5ea3299279aeff3abf875df5a605c1f5#file-weird-ys-L56 that function call is almost all the time, but also calls most of the code...

I'll skip past performance for now (which doesn't bug me because everythihg can be made faster). The first buggy runs took 8s 🙂

I will point out that the clojure version of this run in the repl was even slower than the ys. weird

OK this destructure assignment bugs me because of the escpaing .

Things that you want to be scalars in YS but YAML considers them strings, you escape with a . like a =: ."xxx" + foo

that would be invalid yaml without the .

but on a key it bugs me

👍 1

I had the idea for YS def/let syntax of a =: 1 you could add an optional let in front. so let a =: 1 would could the same

then we can say let [a b c] =: ...

Look ma, no .

The next thing is also minor but also probably easy to address: https://gist.github.com/ingydotnet/5ea3299279aeff3abf875df5a605c1f5#file-weird-ys-L25 Do we need parens around an if condition in the common case?

the general pattern for if is if cond-form: then-form else-form technically you could have: if a b: c or if a b c:

I think if we see a key of if balanced-paren-form: ... we leave alone if we see if some stuff: ... we change it to if (some stuff):

but I can let this one marinate...

The next 2 things are bigger and they both involve if forms

first you see this pattern a lot:

if ...:
  do:
    a: a
    b: b
  =>: c

the do is needed when there are more than one form and the =>: is needed when the form is a scalar (to make YAML happy) (the => evaporates during compilation)

fwiw, I agree on the ."abc" ugliness.

👍 1

I had an idea to make this look friendlier:

if ...:
  then:
    a: a
    b: b
  else: c

that would compile the same.

Naively I'd expect (ah... you just said)

if ...:
  then:
  else:

good to converge

The normal YS for if is:

if (x > 10):
  say: 'x is big'
  say: 'x is small'

with do to have a block of statements

but this could be done now:

if (x > 10):
  =>:
    say: 'x is big'
  =>:
    say: 'x is small'
or maybe later:
if (x > 10):
  then:
    say: 'x is big'
  else:
    say: 'x is small'

I will keep the current form

The 7 stage YS compiler has a stage called "transform" and we can analyze the if forms here and rearrange them

we currently transform cond from

cond:
  a: b
  c: d
to
cond %:
  a: b
  c: d

for example

We probably want to have rules like not allowing else without then etc

people can always use do and => for the else as needed (when there's no then)

the next thing is pretty huge.

in clojure you need to say

(let [x y
      x (if (> a b)
          (inc x) 
          x)]
  (say x))
in python you would
x = y
if (a > b):
  x = x + 1
say(x)
in ys:
x =: y
x =:
  if (a > b):
    inc: x
    =>: x
say: x

this pattern shows up a lot (and a lot in the weird-numbers.ys)

x =:
  if (a > b):
    inc: x
    =>: x

$ ys -ce '
if (a > b):
  x =: inc(x)
'
(if (> a b) (let [x (inc x)]))
is obviously wrong...

but can we make it right?

iow can we reliably compile to something like:

(let [x (if (> a b) (inc x) x)] (say x))

This one probably needs a lot of thought and may very well be a bad idea

but it would make using YS for beginners (like me 🙂 ) so much easier

Is there a better way in clojure to conditionally change a binding without using (let [old (if cond new old)] ...) (needing to repeat old as the else form)?

I think I can move forward on the then and else idea. The others need thought.

hmmm ys has multi-arity fns:

defn add:
  (): 0
  (x): x
  (x y & xs):
    add: (x + y) xs*

not sure how to read that hs (even though I used to know hs a bit)

One could use cond-> instead of if, although the code does not get shorter

(def y 5)
(let [x (if (> 1 0) (inc y) y)] (say x))
(let [x (cond-> y (> 1 0) inc)] (say x))

✅ 1

(was away) The interesting haskell bit is the

x | cond1 = val1
  | cond2 = val2
where conditions are not arities but runtime checks (like if-then cascades or clojure cond) More for syntax inspiration than anything else

OK. Right. This could be a possibly exciting way to add runtime/type/condition polymorphism (with a sexy syntax) to YS. I'll definitely add that to my hammock list 🙂

Reminds me of:

$ ys -ce 'defn foo(x y=42): x + y'
(defn foo ([x y] (+_ x y)) ([x] (foo x 42)))
which provides clean arg defaults by generating a multi-arity result.

Some more things to improve from weird.ys... • need to support w.$n to replace w.nth(n) • need a != operator for a != b instead of not(a == b)