yamlscript 2024-06-17

I just wrote this ys code:

preamble =:
  file =: ENV.'YAMLLM_PREAMBLE_FILE'
  when file: file.slurp()
realized that this would be much more elegant:
preamble =: ENV.'YAMLLM_PREAMBLE_FILE'.slurp()
iow slurp returns nil when its file path arg is nil. Wondering why Clojure doesn't do this more. I can easily change the auto-referred ys.std/slurp to do this, and also provide clj/slurp for the original, which is the YS/MO for this kind of thing. Thoughts?

The use case of Yamlscript, where the fulcrum is in a somewhat different spot on the easy/simple teeter totter, might be well served by an additional (I don't know if YS already has this?) defaultly-loaded namespace full of the truly useful obvious I/O. Keep in mind that slurp doesn't even exist in CLJS (slurp is blocking and CLJS can't) so it's already a gray area, and look at how babashka did its own thing (https://cljdoc.org/d/babashka/fs/0.5.20/doc/readme)

The library docs are next up

rather than explain any more I'll write them and take on clarifying questions

Let me know if I can help with docs

I know you invited users to do rosetta code examples, I've been meaning to contribute one

I often want to ys -c file.ys | bb but then error out with something like
>
Could not resolve symbol: say
> 🙂 an idea: implement say as a Clojure macro and, before emitting the Clojure code, do the macro-expansion with the ys-internal SCI. In that way, the emitted Clojure code remains Babashka compatible. The idea of course comes from me using Ferret as the Clojure-to-C compiler, but Ferret does not have native macro-expansion, so I'd like to move my macros one level up to YS.

❤️ 1

I have 2 ideas wrt to those scenarios.

1. Compile with needed (require yamlscript...) 2. ys -c --to=clojure file.ys which swaps out yamlscript.printer with yamlscript.printer.clojure so that symbols like say print as println and +_ prints as +

Both have advantages and disadvantages

I think I can get pretty far with #2. Definitely worth trying as it is easy to do.

💯 1

A higher order way to do #2 is

ys -c --printer=my-printer-spec.yaml file.ys

🤯 1

I just used say as an obvious example, but lots of things change. Some with the same name. The YS AST that comes out of the constructor stage is a Clojure AST (it represents Clojure code as a tree). The printer stage walks it and prints it to code. Just need to change what it generates. We should play with this idea soon.

Can one hook Clojure macros into the "printer stage"?

I can't really fathom what that would mean. I'll answer "No" for now 🙂 There's not really any need for macros to accomplish this unless I'm missing something big.

YS uses Clojure macros internally and also exposes macros in its libraries certainly. But it doesn't try to cross pollinate itself with Clojure macros.

The compiler is and wants to be a series of simple and easier to follow transformations

👍 1

Soon we (YS users) will be able to hook into any of the compilation stages to adjust things.

Honestly that's the clean way to do this is just define a library of printer hooks that get what you want.

💯 1

likely mostly printer hooks and maybe a couple other hooks depending on what we run into.

You might be interested in when-let

I use when-let constantly... I use my own when-lets even more... https://github.com/yaml/yamlscript/blob/main/core/src/yamlscript/transformers.clj#L17-L24 that file is rife with when-lets. when-lets idea was taken from https://clojuredocs.org/clojure.core/when-let#example-5797f908e4b0bafd3e2a04bb can't live without it. But in current YS that would be:

preamble =:
  when-let [file ENV.'YAMLLM_PREAMBLE_FILE']:
    slurp: file
which by comparison is meh.

I really do wish when-let in Clojure would be enhanced to allow multi bindings...

The downside of YS making lots of changes to core functions is interoperability of YS compiled (to Clojure) code with Clojure{,Script}

But this might be solvable by having the compiler write code that uses the necessary yamlscript clojure libraries.

I need to play with that

I often want to ys -c file.ys | bb but then error out with something like

Could not resolve symbol: say
🙂

Oh very cool, yeah my use of when-let has been very limited but I’ve seen it used to read a file that may or may not exist, so I just thought of calling it out. Glad you already knew of it

I actually kind of like your example of how it would look with when-let in ys

One thing I've grown to love about Clojure's when s is their do semantics. As compared to if.

❤️ 1

I don’t know if the average programmer would appreciate having an elegantly-returned nil

They might think that’s mysterious and prone to silent failures

Fair. I should think on it more. A different MO could be calling it slurp+ and that one could also return nil when the file didn't exist

It's a pain when code is full of "this is what I want, and here's all my checks to make sure it's possible"

clojure already uses this to great value with sequences:

$ ys -pe 'nil.take(5 _).reverse()'
()

why not:

$ ys -pe '+"foo".str/split(/.*?/).reverse().apply(str _).slurp()'
Error: oof (No such file or directory)
😄

I'm starting to feel like Clojure is a pretty low level language. I described it to someone yesterday as the ultimate assembly language. Well Lisps in general. Clojure is high level compared to Java. I think Clojure made great choices for performance etc. It had to pick a middle ground betwixt making code elegant but not doing too much for you. My philosophy for ys is to always make sure you can get the clojure you want into the compilation, if you care. But if you don't care I want to take things higher. For example +:

$ ys -ce '(a + b)'
(+_ a b)
$ ys -ce '(+ a b)'
(+ a b)
+_ is very polymorphic. It checks its args at runtime which can be numbers, strings, vectors, maps etc

The nice thing about compiling to a Lisp is that Lisp is meant to be compiled to. (The "P" in "REPL"). So the YS output ends up looking good. (Better with zprint 🙂 )

@danielmgerson going with this for now:

preamble =:
  when-let [file ENV.'YAMLLM_PREAMBLE_FILE']: slurp(file)
thanks for the idea

Hmmm, given:

$ ys -ce '
defn foo():
  preamble =:
    when-let [file ENV."YAMLLM_PREAMBLE_FILE"]: slurp(file)'
(defn
 foo
 []
 (let
  [preamble
   (when-let [file (__ ENV "YAMLLM_PREAMBLE_FILE")] (slurp file))]))
I can imagine this:
$ ys -ce '
defn foo():
  preamble [file ENV."YAMLLM_PREAMBLE_FILE"] =: slurp(file)'
doing the same thing

that is:

foo =: ...
gens a let could have:
foo [x y()] =: ...
gen a when-let

TBD 😄

😀 1

> thanks for the idea Hehe, what idea? 😅

using when-let

👍 1