Skip to content

AI + Clojure Functions in YAML

Yesterday we learned that all YS YAML input compiles to Clojure (Lisp) before being evaluated by a native binary Clojure interpreter runtime.

Does this mean that you could write Lisp functions in your YAML data files? And then call them on your data?

Of course it does!

Dynamic port numbers🔗

Let's imagine we have this config.yaml file:

server:
  host: localhost
  port: 8080
  ssl: true

database:
  driver: postgres
  host: db.example.com
  port: 5432
  name: myapp
  credentials:
    username: admin
    password: s3cr3t

Now let's say we'd rather not need to specify the port values. We'd like the port to be automatically added depending on certain conditions.

Here's a simplistic solution using a YS function:

!YS-v0
defn auto-port(mapping):
  assoc mapping 'port': 1234

--- !data
server::
  auto-port::
    host: localhost
    ssl: true

database::
  auto-port::
    driver: postgres
    host: db.example.com
    name: myapp
    credentials:
      username: admin
      password: s3cr3t

So we wrote a little YS function that takes a mapping and adds a port key with a value of 1234. Not very exciting or useful, but it's a start.

Next we put a function call to auto-port in front of the server and database mappings. This wasn't great because we had to indent those sections in order to call the function.

Let's see if it works:

$ ys -Y config.yaml
server:
  host: localhost
  ssl: true
  port: 1234
database:
  driver: postgres
  host: db.example.com
  name: myapp
  credentials:
    username: admin
    password: s3cr3t
  port: 1234

Nice. It does work. It added the port to the end of each mapping, but that's OK (for now).

Let's keep going on this one.

AI Generated YS🔗

Using a function to generate 1234 was silly.

Let's say we want the auto-port function to return a random port number in the range of 8000-8999 if the host value is localhost. If the driver value is postgres, we'll use port 5432. Otherwise we'll return 0 (for unknown).

Since we are new to YS and super busy, we'll use AI to generate the function.

Here's the prompt:

Write a YS function that takes a mapping argument and returns the mapping with a new pair added: port: <number>. If the mapping has a host key and the value is localhost, use a random port number in the range of 8000-8999. If the mapping has a driver key and the value is postgres, use port 5432. Otherwise use port 0 (for unknown).

Here's the AI generated function:

!YS-v0
defn auto-port(mapping):
  if mapping.get('host') == 'localhost':
    return mapping.assoc('port' rand-int(8000, 8999))
  elif mapping.get('driver') == 'postgres':
    return mapping.assoc('port' 5432)

Let's see if it works:

$ ys -e "
defn auto-port(mapping):
  if mapping.get('host') == 'localhost':
    return mapping.assoc('port' rand-int(8000, 8999))
  elif mapping.get('driver') == 'postgres':
    return mapping.assoc('port' 5432)

autoport::
  foo: 42"
Error: Could not resolve symbol: return

Nope! Amazingly this generated code was actually valid YS syntax, because it compiled. Unfortunately this isn't how you write that in YS.

Note

Use ys -ce in the command above to see the (broken) Clojure/Lisp code that was generated.

The overall problem is that YS is a new language and there isn't enough of it in the wild yet for AI to train on.

Using the !clj tag🔗

Clojure on the other hand is approaching 20 years old and has a lot of code out there.

Let's see what we get with this prompt:

Write a Clojure function that takes a mapping argument and returns the mapping with a new pair added: port: <number>. If the mapping has a host key and the value is localhost, use a random port number in the range of 8000-8999. If the mapping has a driver key and the value is postgres, use port 5432. Otherwise use port 0 (for unknown).

Here's the function:

(defn auto-port [mapping]
  (if (= (get mapping "host") "localhost")
    (assoc mapping "port" (+ 8000 (rand-int 1000)))
    (if (= (get mapping "driver") "postgres")
      (assoc mapping "port" 5432)
      (assoc mapping "port" 0))))

The point of this post was to use Clojure functions in YAML. How do we do that?

YS has a !clj tag that allows us to write raw Clojure code as strings in our YAML files.

Let's put this all together in our config.yaml file:

!YS-v0:

=>: !clj |
  (defn auto-port [mapping]
    (if (= (get mapping "host") "localhost")
      (assoc mapping "port" (+ 8000 (rand-int 1000)))
      (if (= (get mapping "driver") "postgres")
        (assoc mapping "port" 5432)
        (assoc mapping "port" 0))))

server: !:auto-port
  host: localhost
  port:
  ssl: true

database: !:auto-port
  driver: postgres
  host: db.example.com
  port:
  name: myapp
  credentials:
    username: admin
    password: s3cr3t

Before we try this out, let's look at what we did here.

  • We start in data mode with the !YS-v0 tag.
  • We use the !clj tag to define some code in raw Clojure.
  • Since we need a YAML pair here we use the dummy => key.
  • We use a YAML | literal style scalar for the multi-line string.
  • We use the !:<fn> YS calling syntax to call the auto-port function.
    • This saves us from indenting the mapping data.
  • We put an empty port pair to keep the desired position for the port pair.

Now, let's load this:

$ ys -Y config.yaml
server:
  host: localhost
  port: 8643
  ssl: true
database:
  driver: postgres
  host: db.example.com
  port: 5432
  name: myapp
  credentials:
    username: admin
    password: s3cr3t

Perfect!

We were able to use AI to get something we wanted with minimal effort.

Back to the Future🔗

Assuming Clojure is the past and YS is the future... 😂

Let's go Back to the Future!

Can we convert this Clojure code into YS?

If you have the time in your busy day, then absolutely!

Later on I'll show you a lot about how to go from Clojure back to YS, but for today I'll just do it for you:

Here's a direct translation:

!YS-v0

defn auto-port(mapping):
  if mapping.host == 'localhost':
   assoc mapping "port": 8000 + rand-int(1000)
   if mapping.driver == 'postgres':
     assoc mapping 'port': 5432
     assoc mapping 'port': 0

--- !data
server: !:auto-port
  host: localhost
  port:
  ssl: true

database: !:auto-port
  driver: postgres
  host: db.example.com
  port:
  name: myapp
  credentials:
    username: admin
    password: s3cr3t

and:

$ ys -Y config.yaml
server:
  host: localhost
  port: 8151
  ssl: true
database:
  driver: postgres
  host: db.example.com
  port: 5432
  name: myapp
  credentials:
    username: admin
    password: s3cr3t

Voila!

Going all the way🔗

I know this post is already long, but the OCD in me wants to go all the way to how I'd probably do this for real.

Let's move this helper function into a separate YS module file called helpers.ys.

Also the AI didn't write the best idiomatic Clojure code. Let's tighten up the YS ported code a bit.

Here's config.yaml:

!YS-v0:
:use helpers: :all

server: !:auto-port
  host: localhost
  port:
  ssl: true

database: !:auto-port
  driver: postgres
  host: db.example.com
  port:
  name: myapp
  credentials:
    username: admin
    password: s3cr3t

and helpers.ys:

!YS-v0
ns: helpers

defn auto-port(mapping):
  assoc mapping 'port':
    cond:
      mapping.host == 'localhost':
        8000 + rand-int(1000)
      mapping.driver == 'postgres': 5432
      else: 0

and:

$ ys -Y config.yaml
server:
  host: localhost
  port: 8913
  ssl: true
database:
  driver: postgres
  host: db.example.com
  port: 5432
  name: myapp
  credentials:
    username: admin
    password: s3cr3t

Excellent!

Conclusion🔗

That was probably the longest post of the Summer of YS series so far.

But like I was telling you yesterday... Now that you know that YS is Clojure in disguise, the sky is the limit for the cool stuff I can show you.


If you are enjoying these posts, the please add a "Reaction" below.

And if you have comments or questions, just ask in the comment section there. I hope this post raised a lot of interesting possibilities in your mind. If that's the case, you must surely have questions!

Don't be shy!

YS is a new language. Don't be afraid to ask questions. If you don't get something, then it's almost certain that others don't get it either.

Just ask!!!

Comments