Initial commit

This commit is contained in:
Peter Niederwieser
2016-01-19 14:51:19 +01:00
committed by Dan Chao
commit ecad035dca
2972 changed files with 211653 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

View File

@@ -0,0 +1,317 @@
= Basic Configuration
include::ROOT:partial$component-attributes.adoc[]
In this first part of xref:index.adoc[the Pkl tutorial], you build familiarity with Pkl syntax and basic structure.
You also learn different ways to invoke Pkl to produce different formats.
== Basic values
Consider the following example Pkl file.
[source,{pkl}]
.intro.pkl
----
name = "Pkl: Configure your Systems in New Ways"
attendants = 100
isInteractive = true
amountLearned = 13.37
----
Running Pkl on this file gives
[source,shell]
----
$ pkl eval /Users/me/tutorial/intro.pkl
name = "Pkl: Configure your Systems in New Ways"
attendants = 100
isInteractive = true
amountLearned = 13.37
----
It may seem nothing happened.
However, Pkl tells you that it _accepts the input_.
In other words, you now know that `intro.pkl` does not contain any errors.
You can ask Pkl to print this configuration in a different format, using the `-f` option.
For example, JSON:
[source,shell]
----
$ pkl eval -f json /Users/me/tutorial/intro.pkl
{
"name": "Pkl: Configure your Systems in New Ways",
"attendants": 100,
"isInteractive": true,
"amountLearned": 13.37
}
----
Or _PropertyList_ format:
[source,shell]
----
$ pkl eval -f plist /Users/me/tutorial/intro.pkl
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>name</key>
<string>Pkl: Configure your Systems in New Ways</string>
<key>attendants</key>
<integer>100</integer>
<key>isInteractive</key>
<true/>
<key>amountLearned</key>
<real>13.37</real>
</dict>
</plist>
----
Notice that Pkl generated `<string>`, `<integer>`, `<true/>` and `<real>` for the values in your configuration.
This means it has _both_ correctly derived the types of the literal values _and_ translated those types to the corresponding elements in the PropertyList.
xref:03_writing_a_template.adoc[Part III] goes into types in more detail.
== Structure: Classes, objects, modules
A configuration often requires more than just basic values.
Typically, you need some kind of (hierarchical) structure.
Pkl provides _immutable objects_ for this.
Objects have three kinds of members: properties, elements and entries.
First, look at the syntax for objects and their members.
=== Properties
[source,{pkl}]
.simpleObjectWithProperties.pkl
----
bird { // <1>
name = "Common wood pigeon" // <2>
diet = "Seeds"
taxonomy { // <3>
species = "Columba palumbus"
}
}
----
<1> This _defines_ `bird` to be an object
<2> For primitive values, Pkl has the `=` syntax (more on this later).
<3> Just like `bird {`, but to show that objects can be nested.
This defines an object called `bird` with three _named properties_: `name`, `diet`, and `taxonomy`.
The first two of these are strings, but `taxonomy` is another object.
This means properties in an object can have different types and objects can be nested.
=== Elements
Of course, you don't always have names for every individual structure in your configuration.
What if you want "just a bunch of things" without knowing how many?
Pkl offers _elements_ for this purpose.
Elements are object members, just like properties.
Where you index properties by their name, you index elements by an integer.
You can think of an object that only contains elements as _array_.
Much like arrays in many languages, you can use square brackets to access an element, for example, `myObject[42]`.
You write an element, by writing only an expression.
Pkl derives the index from the number of elements already in the object.
For example:
[source,{pkl}]
.simpleObjectsWithElements.pkl
----
exampleObjectWithJustIntElements {
100 // <1>
42
}
exampleObjectWithMixedElements {
"Bird Breeder Conference"
(2000 + 23) // <2>
exampleObjectWithJustIntElements // <3>
}
----
<1> When you write only the value (without a name), you describe an _element_.
<2> Elements don't have to be literal values; they can be arbitrary _expressions_.
<3> Elements can really be _any_ value, not just primitive values.
=== Entries
Objects can have one more kind of member; _entries_.
Like a _property_, an _entry_ is "named" (technically _keyed_).
Unlike a property, the name does not need to be known at declaration time.
Of course, we need a syntax to tell entries apart from properties.
You write entry "names" by enclosing them in square brackets ("names" is quoted, because the names do not need to be strings; any value can index entries).
[source,{pkl}]
.simpleObjectsWithEntries.pkl
----
pigeonShelter {
["bird"] { // <1>
name = "Common wood pigeon"
diet = "Seeds"
taxonomy {
species = "Columba palumbus"
}
}
["address"] = "355 Bird St." // <2>
}
birdCount {
[pigeonShelter] = 42 // <3>
}
----
<1> The difference with properties is the notation of the key: `[<expression>]`.
<2> As with properties, entries can be primitive values or objects.
<3> Any object can be used as a key for an entry.
=== Mixed members
In the examples so far, you have seen objects with properties, object with elements and object with entries.
These object members can be freely mixed.
[source,{pkl}]
.mixedObject.pkl
----
mixedObject {
name = "Pigeon"
lifespan = 8
"wing"
"claw"
["wing"] = "Not related to the _element_ \"wing\""
42
extinct = false
[false] {
description = "Construed object example"
}
}
----
Notice, how properties (`name`, `lifespan` and `extinct`), elements (`"wing"`, `"claw"`, `42`) and entries (`"wing"`, `false`) are mixed together in this one object.
You don't have to order them by kind, and you don't require (other) special syntax.
=== Collections
This free-for-all mixing of object members can become confusing.
Also, target formats are often considerably more restrictive.
In the following example, you see what happens when you try to produce JSON from `mixedObject`:
[source,shell]
----
$ pkl eval -f json /Users/me/tutorial/mixedObject.pkl
Pkl Error
Cannot render object with both properties/entries and elements as JSON.
Object: "Pigeon"
89 | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (https://github.com/apple/pkl/blob/0.24.0/stdlib/base.pkl#L90)
----
This is why Pkl has two special types of object, namely _listings_, which contain _exclusively_ elements, and _mappings_, which contain _exclusively_ entries.
Both listings and mappings _are_ "just objects," so, they don't require syntax besides that of objects:
[source,{pkl}]
.collections.pkl
----
birds { // <1>
"Pigeon"
"Parrot"
"Barn owl"
"Falcon"
}
habitats { // <2>
["Pigeon"] = "Streets"
["Parrot"] = "Parks"
["Barn owl"] = "Forests"
["Falcon"] = "Mountains"
}
----
<1> A listing containing four elements.
<2> A mapping containing four entries.
[NOTE]
====
_Technically_, the correct way to define `birds` and `habitats` is by using `new Listing {...}` and `new Mapping {...}` explicitly.
You will see what these mean in part xref:03_writing_a_template.adoc[three] of this tutorial.
====
When you render _this_ configuration as JSON, everything works:
[source,json]
----
{
"birds": [
"Pigeon",
"Parrot",
"Barn owl",
"Falcon"
],
"habitats": {
"Pigeon": "Streets",
"Parrot": "Parks",
"Barn owl": "Forests",
"Falcon": "Mountains"
}
}
----
Notice particularly, that you rendered the listing as a JSON _array_.
When you index the listing with an integer, you're referring to the element inside the listing at the corresponding position (starting from `0`).
For example:
[source,{pkl}]
.indexedListing.pkl
----
birds {
"Pigeon"
"Parrot"
"Barn owl"
"Falcon"
}
relatedToSnowOwl = birds[2]
----
results in
[source,{pkl}]
----
birds {
"Pigeon"
"Parrot"
"Barn owl"
"Falcon"
}
relatedToSnowOwl = "Barn owl"
----
== Exercises
1. Given the following JSON snippet (taken from W3C examples), write the `.pkl` file that produces this JSON:
+
[source,json]
----
{
"name": "Common wood pigeon",
"lifespan": 8,
"friends": {
"bird1": "Parrot",
"bird2": "Albatross",
"bird3": "Falcon"
}
}
----
2. For some reason, we decide we no longer need the birdX names of the different birds; we just need them as an array.
Change your solution to the previous question to produce the following JSON result:
+
[source,json]
----
{
"name": "Common wood pigeon",
"lifespan": 8,
"birds": ["Parrot", "Barn owl", "Falcon"]
}
----

View File

@@ -0,0 +1,438 @@
= Filling out a Template
include::ROOT:partial$component-attributes.adoc[]
In this second part of xref:index.adoc[the Pkl tutorial], you will learn how to write one (part of a) configuration in terms of another.
You will also find and fill out an existing _template_.
== Composing configurations
=== Amending
The central mechanism in Pkl for expressing one (part of a) configuration in terms of another is _amending_.
Consider the following example.
[source,{pkl}]
.amendingObjects.pkl
----
bird {
name = "Pigeon"
diet = "Seeds"
taxonomy {
kingdom = "Animalia"
clade = "Dinosauria"
order = "Columbiformes"
}
}
parrot = (bird) {
name = "Parrot"
diet = "Berries"
taxonomy {
order = "Psittaciformes"
}
}
----
Parrot and Pigeon have nearly identical properties.
They only differ in their name and taxonomy, so if you have already written out `bird`, you can say that `parrot` is just like `pigeon` except `name` is `"Parrot"`, diet is `"Berries"` the `taxonomy.order` is `"Psittaciformes"`.
When you run this, Pkl expands everything fully.
[source,{pkl}]
----
bird {
name = "Common wood pigeon"
diet = "Seeds"
taxonomy {
kingdom = "Animalia"
clade = "Dinosauria"
order = "Columbiformes"
}
}
parrot {
name = "Parrot"
diet = "Berries"
taxonomy {
kingdom = "Animalia"
clade = "Dinosauria"
order = "Psittaciformes"
}
}
----
[IMPORTANT]
====
_Amending_ does not allow us to _add_ properties to the (typed) object we are amending.
The xref:03_writing_a_template.adoc[next part of the tutorial] discusses types in more detail.
There, you see that amending _never changes the type_ of the object.
====
You can also amend nested objects.
This allows you to only describe the difference with the outermost object for arbitrarily deeply nested structures.
Consider the following example.
[source,{pkl}]
.nestedAmends.pkl
----
woodPigeon {
name = "Common wood pigeon"
diet = "Seeds"
taxonomy {
species = "Columba palumbus"
}
}
stockPigeon = (woodPigeon) {
name = "Stock pigeon"
taxonomy { // <1>
species = "Columba oenas"
}
}
dodo = (stockPigeon) { // <2>
name = "Dodo"
extinct = true // <3>
taxonomy {
species = "Raphus cucullatus"
}
}
----
<1> This amends `species`, _as it occurs in_ `stockPigeon`.
<2> Amended objects can, themselves, be amended.
<3> New fields can be added to objects when amending.
Notice how you only have to change `taxonomy.species`.
In this example, `bird.taxonomy` has `kingdom`, `clade`, `order` and `species`.
You are amending `stockPigeon`, to define `woodPigeon`.
They have the same `taxonomy`, except for `species`.
This notation says that everything in `taxonomy` should be what it is in the object you are amending (`stockPigeon`), except for `species`, which should be `"Columba palumbus"` .
For the input above, Pkl produces the following output.
[source,{pkl}]
----
woodPigeon {
name = "Common wood pigeon"
diet = "Seeds"
taxonomy {
species = "Columba palumbus"
}
}
stockPigeon {
name = "Stock pigeon"
diet = "Seeds"
taxonomy {
species = "Columba oenas"
}
}
dodo {
name = "Dodo"
diet = "Seeds"
extinct = true
taxonomy {
species = "Raphus cucullatus"
}
}
----
So far, you have only amended _properties_.
Since you refer to them by name, it makes sense that you "overwrite" the value from the object you're amending.
What if you include _elements_ or _entries_ in an amends expression?
[source,{pkl}]
.amendElementsAndEntries.pkl
----
favoriteFoods {
"red berries"
"blue berries"
["Barn owl"] {
"mice"
}
}
adultBirdFoods = (favoriteFoods) {
[1] = "pebbles" // <1>
"worms" // <2>
["Falcon"] { // <3>
"insects"
"amphibians"
}
["Barn owl"] { // <4>
"fish"
}
}
----
<1> Explicitly amending _by index_ replaces the element at that index.
<2> Without explicit indices, Pkl can't know which element to overwrite, so, instead, it _adds_ an element to the object you're amending.
<3> When you write "new" entries (using a key that does not occur in the object you're amending), Pkl also _adds_ them.
<4> When you write an entry using a key that exists, this notation amends its value.
Pkl can't know which of the `favoriteFoods` to overwrite only by their _value_.
When you want to _replace_ an element, you have to explicitly amend the element at a specific index.
This is why a "plain" element in an amends expression is _added_ to the object being amended.
Result:
[source,{pkl}]
----
favoriteFoods {
["Barn owl"] {
"mice"
}
"red berries"
"blue berries"
}
adultBirdFoods {
["Barn owl"] {
"mice"
"fish"
}
"red berries"
"pebbles"
["Falcon"] {
"insects"
"amphibians"
}
"worms"
}
----
=== Modules
A `.pkl` file describes a _module_.
Modules are objects that can be referred to from other modules.
Going back to the example above, you can write `parrot` as a separate module.
[source,{pkl}]
.pigeon.pkl
----
name = "Common wood pigeon"
diet = "Seeds"
taxonomy {
species = "Columba palumbus"
}
----
You can `import` this module and express `parrot` like you did before.
[source,{pkl}]
.parrot.pkl
----
import "pigeon.pkl" // <1>
parrot = (pigeon) {
name = "Great green macaw"
diet = "Berries"
species {
species = "Ara ambiguus"
}
}
----
<1> Importing `foo.pkl` creates the object `foo`, so you can refer to `pigeon` in this code, like you did before.
If you run Pkl on both, you will see that it works.
Looking at the result, however, you see a (possibly) unexpected difference.
[source,{pkl}]
----
$ pkl eval /Users/me/tutorial/pigeon.pkl
name = "Common wood pigeon""
diet = "Seeds"
taxonomy {
species = "Columba palumbus"
}
$ pkl eval /Users/me/tutorial/parrot.pkl
parrot {
name = "Great green macaw"
diet = "Berries"
taxonomy {
species = "Ara ambiguus"
}
}
----
The object `pigeon` is "spread" in the top-level, while `parrot` is a nested and named object.
This is because writing `parrot {...}` defines an object property _in_ the "current" module.
In order to say that "this module is an object, amended from the `pigeon` module," you use an _amends clause_.
[source,{pkl}]
.parrot.pkl
----
amends "pigeon.pkl" // <1>
name = "Great green macaw"
----
<1> "This" module is the same as `"pigeon.pkl"`, except for what is in the remainder of the file.
[NOTE]
====
As a first intuition, think of "amending a module" as "filling out a form."
====
== Amending templates
A Pkl file can be either a _template_ or a _"normal" module_.
This terminology describes the _intended use_ of the module and doesn't imply anything about its structure.
In other words: just by looking at Pkl code, you can't tell whether it is a template or a "normal" module.
[source,{pkl}]
.acmecicd.pkl
----
module acmecicd
class Pipeline {
name: String(nameRequiresBranchName)?
hidden nameRequiresBranchName = (_) ->
if (branchName == null)
throw("Pipelines that set a 'name' must also set a 'branchName'.")
else true
branchName: String?
}
timeout: Int(this >= 3)
pipelines: Listing<Pipeline>
output {
renderer = new YamlRenderer {}
}
----
Remember that amending is like filling out a form.
That's exactly what you're doing here; you're filling out "work order forms".
Next, add a time-out of one minute for your job.
[source,{pkl}]
.cicd.pkl
----
amends "acmecicd.pkl"
timeout = 1
----
Unfortunately, Pkl does not accept this configuration and provides a rather elaborate error message:
[source,plain]
----
Pkl Error // <1>
Type constraint `this >= 3` violated. // <2>
Value: 1 // <3>
225 | timeout: Int(this >= 3)? // <4>
^^^^^^^^^
at acmecicd#timeout (file:///Users/me/tutorial/acmecicd.pkl, line 8)
3 | timeout = 1 // <5>
^
at cicd#timeout (file:///Users/me/tutorial/cicd.pkl, line 3)
90 | text = renderer.renderDocument(value) // <6>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (https://github.com/apple/pkl/blob/e4d8c882d/stdlib/base.pkl#L90)
----
<1> Pkl found an error.
<2> Which error Pkl found.
<3> What the offending value is.
<4> Where Pkl found its expectation (line 8 of the amended module).
<5> Where Pkl found the offending value (line 3 of the input module).
<6> What Pkl evaluated to discover the error.
When Pkl prints source locations, it also prints clickable links for easy access.
For local files, it generates a link for your development environment (https://pkl-lang.org/main/current/pkl-cli/index.html#settings-file[configurable in `+~/.pkl/settings.pkl+`]).
For packages imported from elsewhere, if available, Pkl produces `https://` links to their repository.
Pkl complains about a _type constraint_.
Pkl's type system doesn't just protect you from providing a `String` where you expected an `Int`, it even checks which _values_ are allowed.
In this case, the minimum time-out is _three_ minutes.
If you change the value to `3`, Pkl accepts your configuration.
[source, shell]
----
$ pkl eval cicd.pkl
timeout: 3
pipelines: []
----
You can now define a pipeline.
Start off by specifying the name of the pipeline and nothing else.
[source,{pkl}]
.cicd.pkl
----
amends "acmecicd.pkl"
timeout = 3
pipelines {
new { // <1>
name = "prb"
}
}
----
<1> There is no pipeline object to amend. The `new` keyword gives you an object to amend.
So far, you've defined objects the same way you amended them.
When the name `foo` didn't occur before, `foo { ... }` _creates_ a property called `foo` and assigns to it the object specified on the `...`.
If `foo` is an existing object, this notation is an _amend expression_; resulting in a new _object_ (value), but _not_ a new (named) property.
Since `pipelines` is a listing, you can _add_ elements by writing expressions in an amend expression.
In this case, though, there is no object to amend. Writing `myNewPipeline { ... }` defines a _property_, but listings may only include _elements_.
This is where you can use the keyword `new`.
`new` gives you an object to amend.
Pkl derives from the context in which `new` is used and what the object to amend should look like.
This is called the _default value_ for the context.
xref:03_writing_a_template.adoc[The next part] goes into detail about how Pkl does this.
Running Pkl on your new configuration produces a verbose error.
[source,plain]
.cicd.pkl
----
Pkl Error
Pipelines that set a 'name' must also set a 'branchName'.
8 | throw("Pipelines that set a 'name' must also set a 'branchName'.")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at acmecicd#Pipeline.nameRequiresBranchName.<function#1> (file:///Users/me/tutorial/acmecicd.pkl, line 8)
6 | name = "prb"
^^^^^
at cicd#pipelines[#1].name (file:///Users/me/tutorial/cicd.pkl, line 6)
90 | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (https://github.com/apple/pkl/blob/e4d8c882d/stdlib/base.pkl#L90)
----
You have hit another type constraint, like `timeout: Int(this >= 3)` before.
In this case, the error message consists of an English language sentence, instead of Pkl code.
When constraints are complicated or very application specific, template authors can `throw` a more descriptive error message like this.
The message is quite instructive, so you can fix the error by adding a `branchName`.
[source,{pkl}]
.cicd.pkl
----
amends "acmecicd.pkl"
timeout = 3
pipelines {
new {
name = "prb"
branchName = "main"
}
}
----
and indeed
[source,{pkl}]
----
$ pkl eval -f yml /Users/me/tutorial/cicd.pkl
timeout: 3
pipelines:
- name: prb
branchName: main
----

View File

@@ -0,0 +1,441 @@
= Writing a Template
include::ROOT:partial$component-attributes.adoc[]
In parts xref:01_basic_config.adoc[one] and xref:02_filling_out_a_template.adoc[two], you saw that Pkl provides _validation_ of our configurations.
It checks syntax, types and constraints.
As you saw in the `acmecicd` example xref:02_filling_out_a_template.adoc#amending-templates[here], the template can provide informative error messages when an amending module violates a type constraint.
In this final part, you will see some of Pkl's techniques that are particularly relevant for writing a template.
== Basic types
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.
Before you can write types for them, you need to know how to write the types for the simplest (unstructured) values.
These are all Pkl's _basic_ types:
[source,{pkl}]
.pklTutorialPart3.pkl
----
name: String = "Writing a Template"
part: Int = 3
hasExercises: Boolean = true
amountLearned: Float = 13.37
duration: Duration = 30.min
bandwidthRequirementPerSecond: DataSize = 50.mb
----
In the above, you've explicitly annotated the code with type signatures.
The default output of Pkl is actually `pcf`, which is a subset of Pkl.
Since `pcf` does not have type signatures, running Pkl on this example removes them.
[source,shell]
----
$ pkl eval pklTutorialPart3.pkl
name = "Writing a Template"
part = 3
hasExercises = true
amountLearned = 13.37
duration = 30.min
bandwidthRequirementPerSecond = 50.mb
----
Note how `Duration` and `DataSize` help you prevent https://en.wikipedia.org/wiki/Mars_Climate_Orbiter[unit errors] in these common (for configuration) domains.
== Typed objects, properties and amending
Having a notation for basic types, you can now write _typed objects_.
[source,{pkl}]
.simpleClass.pkl
----
class Language { // <1>
name: String
}
bestForConfig: Language = new { // <2>
name = "Pkl"
}
----
<1> A class definition.
<2> A property definition, using the `Language` class.
[NOTE]
====
Although not required (or enforced), it's customary to name properties starting with a lower-case letter. Class names, by that same convention, start with an upper-case letter.
====
You can type objects with _classes_.
In this example, you define a class called `Language`.
You can now be certain that every instance of `Language` has a property `name` with type `String`.
Types and values are different things in Pkl.
Pkl does not render types in its output,footnote:[Although, some output formats can contain their own form of type annotation. This may be derived from the Pkl type. Type definitions (`class` and `typealias`) themselves are never rendered.] so when you run Pkl on this, you don't see the class _definition_ at all.
[source,{pkl}]
----
$ pkl eval simpleClass.pkl
bestForConfig {
name = "Pkl"
}
----
Did you notice that the output doesn't just omit the type signature, but also the `= new`?
We will discuss this further in the next section.
When your configuration describes a few different parts like this, you can define one instance and amend it for every other instance.
For example:
[source,{pkl}]
.pklTutorialParts.pkl
----
class TutorialPart {
name: String
part: Int
hasExercises: Boolean
amountLearned: Float
duration: Duration
bandwidthRequirementPerSecond: DataSize
}
pklTutorialPart1: TutorialPart = new {
name = "Basic Configuration"
part = 1
hasExercises = true
amountLearned = 13.37
duration = 30.min
bandwidthRequirementPerSecond = 50.mib.toUnit("mb")
}
pklTutorialPart2: TutorialPart = (pklTutorialPart1) {
name = "Filling out a Template"
part = 2
}
pklTutorialPart3: TutorialPart = (pklTutorialPart1) {
name = "Writing a Template"
part = 3
}
----
You can read this as saying "``pklTutorialPart2`` & `pklTutorialPart3` are exactly like `pklTutorialPart1`, except for their `name` and `part`."
Running Pkl confirms this:
[source,shell]
----
$ pkl eval pklTutorialParts.pkl
pklTutorialPart1 {
name = "Basic Configuration"
part = 1
hasExercises = true
amountLearned = 13.37
duration = 30.min
bandwidthRequirementPerSecond = 50.mb
}
pklTutorialPart2 {
name = "Filling out a Template"
part = 2
hasExercises = true
amountLearned = 13.37
duration = 30.min
bandwidthRequirementPerSecond = 50.mb
}
pklTutorialPart3 {
name = "Writing a Template"
part = 3
hasExercises = true
amountLearned = 13.37
duration = 30.min
bandwidthRequirementPerSecond = 50.mb
}
----
Sadly, `pklTutorialParts.pkl` is a _rewrite_ of `pklTutorial.pkl`.
It creates a separate `class TutorialPart` and instantiates three properties with it (`pklTutorialPart1`, `pklTutorialPart2` and `pklTutorialPart3`).
In doing so, it implicitly moves everything "down" one level (`pklTutorialPart3` is now a property in the module `pklTutorialParts`, whereas above, in `pklTutorialPart3.pkl` it was its own module).
This is not very DRY.
As a matter of fact, you don't need this rewrite.
Any `.pkl` file defines a _module_ in Pkl.
Any module is represented by a _module class_, which is an actual Pkl `class`.
A module is not quite the same as any other class, because Pkl never renders class definitions on the output.
However, when you ran Pkl on `pklTutorialPart3.pkl`, it _did_ produce an output.
This is because a module also defines an _instance_ of the module class.
The values given to properties in a module (or in any "normal" class) are called _default values_.
When you instantiate a class, all the properties for which you _don't_ provide a value are populated from the class' default values.
In our examples of tutorial parts, only the `name` and `part` varied across instances.
You can express this by adding default values to the (module) class definition.
Instead of starting from a particular tutorial part, you can define the module `tutorialPart` as follows:
[source,{pkl}]
.TutorialPart.pkl
----
name: String // <1>
part: Int // <1>
hasExercises: Boolean = true // <2>
amountLearned: Float = 13.37 // <2>
duration: Duration = 30.min // <2>
bandwidthRequirementPerSecond: DataSize = 50.mb // <2>
----
<1> No default value given.
<2> Default value given.
Running this through Pkl gives an error, or course, because of the missing values:
[source, shell]
----
$ pkl eval TutorialPart.pkl
Pkl Error
Tried to read property `name` but its value is undefined.
1 | name: String
^^^^
...
----
An individual part now only has to fill in the missing fields, so you can change `pklTutorialPart3.pkl` to amend this:
[source,{pkl}]
.pklTutorialPart3.pkl
----
amends "TutorialPart.pkl"
name = "Writing a Template"
part = 3
----
This results in
[source, shell]
----
$ pkl eval pklTutorialPart3.pkl
name = "Writing a Template"
part = 3
hasExercises = true
amountLearned = 13.37
duration = 30.min
bandwidthRequirementPerSecond = 50.mb
----
This now behaves exactly like our `pklTutorialPart3: TutorialPart = (pklTutorialPart1) {...` before.
`pklTutorialPart3` is now defined as the value we get by amending `tutorialPart` and giving it a `name` and a `part`.
[IMPORTANT]
====
Amending anything _never changes its type_.
When we amend an object of type `Foo`, the result will always be precisely of type `Foo`.
By "precisely" we mean, that amending an object also can't "turn it into" an instance of a sub-class of the class of the object being amended.
====
== A new template
Now that you know about types, you can start writing your first template.
So far, you've written configurations with Pkl, either without a template, or using a template on Pkl Hub.
It is often easiest to first write a (typical) configuration for which you want to create a template.
Suppose you want to define what a live workshop for this tutorial looks like.
Consider this example:
[source,{pkl}]
.workshop2023.pkl
----
title = "Pkl: Configure your Systems in New Ways"
interactive = true
seats = 100
occupancy = 0.85
duration = 1.5.h
`abstract` = """
With more systems to configure, the software industry is drowning in repetitive and brittle configuration files.
YAML and other configuration formats have been turned into programming languages against their will.
Unsurprisingly, they dont live up to the task.
Pkl puts you back in control.
"""
event {
name = "Migrating Birds between hemispheres"
year = 2023
}
instructors {
"Kate Sparrow"
"Jerome Owl"
}
sessions {
new {
date = "8/14/2023"
time = 30.min
}
new {
date = "8/15/2023"
time = 30.min
}
}
assistants {
["kevin"] = "Kevin Parrot"
["betty"] = "Betty Harrier"
}
agenda {
["beginners"] {
title = "Basic Configuration"
part = 1
duration = 45.min
}
["intermediates"] {
title = "Filling out a Template"
part = 2
duration = 45.min
}
["experts"] {
title = "Writing a Template"
part = 3
duration = 45.min
}
}
----
Call your new template `Workshop.pkl`.
Although not required, it's good practice to always name your template with a `module`-clause.
Defining the first few properties are like you saw in the previous section:
[source,{pkl}]
----
module Workshop
title: String
interactive: Boolean
seats: Int
occupancy: Float
duration: Duration
`abstract`: String
----
Unlike these first few properties, `event` is an object with multiple properties.
To be able to type `event`, you need a `class`.
You've seen before how to define this:
[source,{pkl}]
----
class Event {
name: String
year: Int
}
event: Event
----
Next, `instructors` isn't an object with properties, but a list of unnamed values.
Pkl offers the `Listing` type for this:
[source,{pkl}]
----
instructors: Listing<String>
----
`sessions` is a `Listing` of objects, so you need a `Session` class.
[source,{pkl}]
----
class Session {
time: Duration
date: String
}
sessions: Listing<Session>
----
`assistants` has a structure like an object, in that all the values are named, but the set of names is not fixed for all possible workshops (and some workshops may have more assistants than others). The Pkl type for this is a `Mapping`:
[source,{pkl}]
----
assistants: Mapping<String, String>
----
Finally, for every workshop session, there is an `agenda`, which describes which ``TutorialPart``s are covered.
You already defined `TutorialPart.pkl` as its own module, so you should not define a separate class, but rather `import` that module and reuse it here:
[source,{pkl}]
----
import "TutorialPart.pkl" // <1>
agenda: Mapping<String, TutorialPart>
----
<1> This `import` clause brings the name `TutorialPart` into scope, which is the module class as discussed above. Note that import clauses must appear before property definitions.
Putting it all together, your `Workshop.pkl` template looks like this:
[source,{pkl}]
.Workshop.pkl
----
module Workshop
import "TutorialPart.pkl"
title: String
interactive: Boolean
seats: Int
occupancy: Float
duration: Duration
`abstract`: String
class Event {
name: String
year: Int
}
event: Event
instructors: Listing<String>
class Session {
time: Duration
date: String
}
sessions: Listing<Session>
assistants: Mapping<String, String>
agenda: Mapping<String, TutorialPart>
----

View File

@@ -0,0 +1,17 @@
= Tutorial
include::ROOT:partial$component-attributes.adoc[]
Welcome to the Pkl tutorial. We will get you up and running quickly!
If you are new to Pkl, we recommend that you follow along with the code examples.
This tutorial describes interactions with the xref:pkl-cli:index.adoc#repl[REPL].
For an even more interactive experience, follow along using a xref:main:ROOT:tools.adoc[supported editor].
For more comprehensive documentation, see xref:language-reference:index.adoc[Language Reference].
For ready-to-go examples with full source code, see xref:ROOT:examples.adoc[].
For API documentation, see xref:ROOT:standard-library.adoc[Standard Library].
Pick a tutorial by topic:
1. xref:01_basic_config.adoc[Basic Configuration]
2. xref:02_filling_out_a_template.adoc[Filling out a Template]
3. xref:03_writing_a_template.adoc[Writing a Template]