Pkl: Configuration As Code by Apple

Shubham Thakur
10 min readFeb 11, 2024

--

On Feb 24,2024, The apple open sourced the pkl (pronounced pickle): A Configuration As Code by Apple.

pkl is a programming language for producing configuration. pkl allows you to write configurations as code, validate them convert to existing static formats, write custom logic etc.

Introduction and First steps

A simple config, Lets see how it will look into a .pkl format

First we define a template for configuration blogTemplate , Note defining as template is not required, you can directly write config, but for our use case we will first define template

module blogTemplate //module name

blogName: String
// define type string
website: String

// this is how you give comments
article: Article

class Article {
author: String //attribute of class article

link: String

views: UInt16 =0 // this is how you provide default value

topic: String
published: "yes"|"no" // only valid values are yes or no
}

Here we define a template, and give it a name under module blogTemplate and them define its parameters with there data type

Notice we define article as data type Article which is like a class and has sub types. We can reuse this component

Now we can define our simple config as

amends "blogTemplate.pkl"                 // get defination from blogtemplate.pkl

blogName = "A simple blog"

website = "example.com"


article {
author = "gopher"
views = 1000
link = "blog by gopher"
topic = "Go Java Python"

}

To test run

./pkl eval — format yaml ./blog/blog.pkl

./pkl eval — format json ./blog/blog.pkl

and we can get output in yaml and json

output yaml
output json

This is a simple example of how we can use pkl. Lets dive a little deeper. Lets first install pkl-cli

Installation: pkl-cli

Java

The Java executable works on multiple platforms and has a smaller binary size than the native executables. However, it requires a Java 8 (or higher) runtime on the system path, has a noticeable startup delay, and runs complex Pkl code slower than the native executables.

Download java at https://www.oracle.com/in/java/technologies/downloads/

curl -L -o jpkl https://repo1.maven.org/maven2/org/pkl-lang/pkl-cli-java/0.25.2/pkl-cli-java-0.25.2.jar
chmod +x jpkl
./jpkl --version

Linux

On AMD 64

curl -L -o pkl https://github.com/apple/pkl/releases/download/0.25.2/pkl-linux-amd64
chmod +x pkl
./pkl --version

On aarch64:

curl -L -o pkl https://github.com/apple/pkl/releases/download/0.25.2/pkl-linux-aarch64
chmod +x pkl
./pkl --versio

Mac

On aarch64:

curl -L -o pkl https://github.com/apple/pkl/releases/download/0.25.2/pkl-macos-aarch64
chmod +x pkl
./pkl --version

On amd64:

curl -L -o pkl https://github.com/apple/pkl/releases/download/0.25.2/pkl-macos-amd64
chmod +x pkl
./pkl --version

Checkout official docs at https://pkl-lang.org/main/current/pkl-cli/index.html

Also you can install extension for your editor

I am using vs code , heres guide to use it in vs code

or

curl https://github.com/apple/pkl-vscode/releases/download/0.15.0/pkl-vscode-0.15.0.vsix -o pkl-vscode-0.15.0.vsix
code --install-extension pkl-vscode-0.15.0.vsix

Working with Pkl

In above example lets say now we want to extend it such that we have multiple authors, also we want to append all links with our main website.

Lets start without a template, so our blog.pkl looks like


blogName = "A simple blog"
website = "example.com"
article {
author = "gopher"
views = 1000
link = "gopher"
topic = "Go Java Python"

}

Output ./pkl eval — format yaml ./blog/blog.pkl

output

Now lets nest it for 2 bloggers name gopher_1 and gopher_2 .

gopher_1 {
blogName = "A simple blog"
website = "example.com"

article {
author = "gopher_1"
views = 1000
link = "gopher"
topic{"Go" "Java" "Python"}
}
}

gopher_2 {
blogName = "A simpler blog"
website = "example.com"
article {
author = "gopher_2"
views = 100
}
}
output

Well you can see there is lot of overlap in gopher_1 and gopher_2.

You may have also noticed in gopher_1 we have a list. Note: You can define mixed object in pkl, but they may not be valid in other formats

Now there are few similar values in gopher_1 and gopher_2, lets address that

Amending

you can “build” one configuration on top of another using a feature called “amending.” This lets you reuse existing configurations and keep your overall config organized.

Consider


blog{
website = "example.com"
mode="website"
}



gopher_1 = (blog){
blogName = "A simple blog"
article {
author = "gopher_1"
views = 1000
link = "gopher"
topic{"Go" "Java" "Python"}
}
}

gopher_2 = (blog){
blogName = "A simpler blog"
website = "example_2.com"
article {
author = "gopher_2"
views = 100
}
}

Here gopher_1 inherits all values from blog (website,mode) whereas gopher_2 modifies value of website

output

This allows us to reuse values in components.

So while amending you can

  1. inherit existing properties
  2. modify existing properties
  3. add new properties

Amending does not allow us to add properties to the (typed) object we are amending. So we cant change type of an object.

You can also modify by index.

Importing

pkl also allows you to import and share the code

lets segregate blog.pkl and blogUser.pkl as follows

blog.pkl

blogName = " blog"
article {
author = "gopher_null"
views = 1000
link = ""
topic{"Go" "Java" "Python" "pkl"}
}

website="example.com"

blogUser.pkl

import "blog.pkl"

gopher_1 = (blog){
blogName = "A simple blog"
article {
author = "gopher_1"
views = 1000
link = "gopher"
topic{"Go" "Java" "Python"}
}
}

gopher_2 = (blog){
blogName = "A simpler blog"
website = "example_2.com"
article {
author = "gopher_2"
views = 100
}
}

Now lets compile to yaml

output

Notice topic was appended to form new list. As pkl do not know what to do it just appends the list while importing or amending

Note: While trying to import only some attributes, i faced a issue where i was not able to add extra fields, so i modified this example. not sure if its possible or not

Output for blog.pkl now is

But blog should be an object, this can be resolved by using amending instead of import directive

Amending is like filling out a form, lets modify our blog.pkl as

blog {blogName = " blog"
article {
author = "gopher_null"
views = 1000
link = ""
topic{"Go" "Java" "Python" "pkl"}
}

website="example.com"
}

Notice we have wrapped blog as object , now we can ammend it in gopher_1.pkl

amends  "blog.pkl"
blog {
blogName = "A simple blog"
article {
author = "gopher_1"
views = 1000
link = "gopher"
topic{"Go" "Java" "Python"}
}
}

Output:

Output for blog

Note values not specified in gopher_1.pkl is auto filled from blog.pkl

So while importing you create a new object , while amending you fill out the current object.

Also lets try to modify type, view is an int, lets try to add 100 as string

amends  "blog.pkl"
blog {
blogName = "A simple blog"
article {
author = "gopher_1"
views = "1000"
link = "gopher"
topic{"Go" "Java" "Python"}
}
}

It worked !!

should not happen

Note: according to documentation during ammending, type cannot be changed but looks like version iam using it works, it may be just a bug (also iam not defining type explicity).

anyways with templating we can resolve this

So we have discussed how to define config without a template, but you may have noticed in above example blog.pkl has actually become a template, lets create a discuss template.

Templates

Pkl always checks the syntax of its input. As it evaluates your configuration, it also checks types. You’ve seen objects, listings, and mappings already. These provide ways to write structured configuration. (examples are taken from official docs)

Pkl basic types

name: String = "Writing a Template"

part: Int = 3

hasExercises: Boolean = true

amountLearned: Float = 13.37

duration: Duration = 30.min

bandwidthRequirementPerSecond: DataSize = 50.mb

You can nest objects like

class Language { 
name: String
}

bestForConfig: Language = new {
name = "Pkl"
}

Lets get back to our example

module blogTemplate

blogName: String

website: String


article: Article
// this is how you give comments
class Article {
author: String

link: String

views: UInt16 =0

topic: String
}
amends "blogTemplate.pkl"

blogName = "A simple blog"

website = "example.com"


article {
author = "gopher"
views = 1000
link = "blog by gopher"
topic = "Go Java Python"

}

So here we defined type in template and then config in blog.pkl , also notice i defined default value=0 in template.

Output:

But lets say now we want to create articles config, and in this config we can reuse the class defined in blogTemplate.pkl , create a new file called article.pkl

import "blogTemplate.pkl"

hidden blogs: blogTemplate.Article=new{
topic="Go"
link=""

}

authors=List("gopher_1","gopher_2")

articlesWithTopic{
for(a in authors){
(blogs){
author=a
}
}
}

Output

output

So we successfully added author to as a list, but notice the authors list we created as sample? we can hide this by using keyword hidden

import "blogTemplate.pkl"

hidden blogs: blogTemplate.Article=new{
topic="Go"
link=""

}

hidden authors=List("gopher_1","gopher_2")

articlesWithTopic{
for(a in authors){
(blogs){
author=a
}
}
}
output

Well great

Now if you try adding views as string instead of int

import "blogTemplate.pkl"

hidden blogs: blogTemplate.Article=new{
topic="Go"
link=""
views="100"

}

hidden authors=List("gopher_1","gopher_2")

articlesWithTopic{
for(a in authors){
(blogs){
author=a
}
}
}

It throws a validation error

error output

Lets do a little more validation,

module blogTemplate

blogName: String

website: String


article: Article

class Article {
author: String(!isEmpty)

link: String
published: "yes"|"no"
views: UInt16(isBetween(0, 100)) =0

topic: String
}

Here we are telling that

  1. author should not be empty
  2. views can be only between 0 to 100
  3. published can be yes or no only

Now lets see what happens when we remove author from defination

import "blogTemplate.pkl"

hidden blogs: blogTemplate.Article=new{
topic="Go"
link=""

}

hidden authors=List("gopher_1","gopher_2")

articlesWithTopic{
for(a in authors){
(blogs){

published="yes"
}
}
}

We get an error

Similar behavior we can observe if views>100

error constrain

You can checkout all features of pkl language at https://pkl-lang.org/main/current/language-reference/index.html

PklDocs:

Pkl also provides tool for documentation of config using pkl docs

Pkldoc: Pkldoc is a documentation website generator that produces navigable and searchable API documentation for Pkl modules.

Wrap Up

Great, The articles use case is very simple, lets consider few more use cases

  1. You want to create a config , with slight variations.

example: you want to launch a redis cluster with slight variation in ports and other config

2. you want to support multiple config (like json and yaml etc)

3. You want to add logic to config generation, like generate list of servers from the host names provided in config.

4. Add constrain into your config, like limit resources required by k8s pod etc.

You can checkout more example at https://pkl-lang.org/main/current/examples.html

This article serves as basic intro to pkl, where we go through and try out pkl. whereas we did not go into all features and language specific things, I tried to cover few interesting features.

I have also tried to provide references along side articles for further read and deep dive

it looks like a great tool to maintain multiple similar configs and good thing is we can also convert them to static formats like json,yaml. You can also generate code for supported languages from the single config.

Whereas the pkl is just released so there is lot to look forward.

if you are interested to learn more you can check out references for detailed deep dive

pkl as language has other feature, you can check them here

Checkout how you can bind the pkl module in various languages at https://pkl-lang.org/go/current/quickstart.html

So this is new way to write configurations by apple, and is also opensource. Lets see what this project brings in future

Hope you liked the article, If there are any suggestions or mistakes please let me know. Have a great day ahead and keep learning.

--

--