Pkl: Configuration As Code by Apple
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
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
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
}
}
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
This allows us to reuse values in components.
So while amending you can
- inherit existing properties
- modify existing properties
- 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
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 !!
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
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
}
}
}
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
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
- author should not be empty
- views can be only between 0 to 100
- 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
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
- 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
References And Further read:
Hope you liked the article, If there are any suggestions or mistakes please let me know. Have a great day ahead and keep learning.