設定ファイル(YAML, JSON)を生成できるApple産Pklについて その1

導入 (Introduction)

  • 概要: 設定ファイルを簡単に、そして楽しく生成できるAppleのオープンソースであるPkl言語について触れてみよう!

  • 目的: 設定ファイルをいかに楽に自動的に生成できるらしいPklについて研究する。煩わしい設定ファイルに静的解析をしてくれるようなツールや言語を用いて、効率を爆上げする。

  • 前提知識: 特にありません。No specific prerequisites! Just enjoy the process!

目次 (Table of Contents)

  1. 自己紹介

  2. 動機

  3. Pklとは?

  4. Pklの始め方

  5. Pklの少し複雑なデータ構造

  6. 参考資料

自己紹介

みなさま、初めまして。4月より24年度新卒として、株式会社インゲージに入社いたしました Jinyang(ジンヤング)と申します。

好きな食べ物はスイカで、好きな飲み物はコーヒーです。社内のドトール缶コーヒーはほぼ私が飲んでいます。よろしくお願いします。

動機

さて、どのようにしてPklにであったのかについて説明しなければなりません。入社したての頃、configuration fileを一生懸命カキカキする時期がありました。

冒頭でも申し上げたようにvscodeのextensionが効けば、これはダメだよ、あれはダメだよと注意してくれたり、補正してくれます。

しかし、自社内で書くYamlはそんなものはないのです。また、yamlを読み込んで解析やら、設定を読み込んだりするバッチタスクは非常に時間を食います。

したがって、しょうもないタイポミス、存在しないプロパティなどを指定してしまうと、10分以上を溶かしてしまうことになるでしょう。

そんな中私はYamlのLinter的なものはないかと調べることにしたのです。そして、最終的にPklに出会ったわけなのです。

Pklとは?

公式ドキュメントより引用

Pklは、プログラマブルスケーラブル、そして安全な設定言語です。複雑な設定ファイルの作成や管理を、より楽しく効率的に行うことができます。

ふむふむ、何やら私が思い描いていたlinter的なものではないが、似たようなことをやってくれそうなものじゃないか?すぐにやってみよう

Pklの始め方

環境構築

まずは、CLI :: Pkl Docs に沿ってインストールを行なっていきましょう。

私はhomebrewを入れているので、以下のようなコマンドで行います。

brew install pkl

次に、私はvscodeを使用しているので、extensionを入れます。

Release 0.17.0 · apple/pkl-vscode · GitHub から、.vsixファイルをダウンロードし、以下のコマンドを実行し手動でインストールする必要があります。

まだ、拡張機能マーケットには出ていないんですね〜。ちなみに、この拡張機能を入れた後、シンタックスハイライトが効くようになり、ちょっとしたスニペットが使えるようになります。

code --install-extension :your_path_to_the_extension/pkl-vscode-0.17.0.vsix

Pklの基本データ構造

さて、まずは基本的な値の設定について紹介しなればなりません。

name = "Pkl: Configure your Systems in New Ways"
attendants = 100
isInteractive = true
amountLearned = 13.37

この例では、name、attendants、isInteractive、amountLearnedという4つのプロパティが定義されています。それぞれ文字列、整数、ブール値、そして浮動小数点数の値を持っています。

pkl evalを使い、評価を行うことができます。

pkl eval /Users/me/tutorial/intro.pkl
name = "Pkl: Configure your Systems in New Ways"
attendants = 100
isInteractive = true
amountLearned = 13.37

また、異なるフォーマットに簡単に変換できるので、やって見ましょう

jsonに変換する場合

pkl eval -f json /Users/me/tutorial/intro.pkl
{
  "name": "Pkl: Configure your Systems in New Ways",
  "attendants": 100,
  "isInteractive": true,
  "amountLearned": 13.37
}

Property Listsに変換する場合

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>
yamlに変換する場合
pkl eval -f yaml /Users/me/tutorial/intro.pkl
name: 'Pkl: Configure your Systems in New Ways'
attendants: 100
isInteractive: true
amountLearned: 13.37

Pklの少し複雑なデータ構造

Pklでは「不変オブジェクト」という概念を使用して、オブジェクトやクラス、モジュールなどを扱います。 そして、オブジェクトには、プロパティ要素エントリの三種類のメンバーが含まれます。それぞれについて説明を行いましょう。心配しないでください!名前はなんとなく威圧感を感じますが、見てみると普段使っているものと同じようなものです。

プロパティ

この例では、オブジェクトhumanは3つのプロパティを持っています。hobbyもまたオブジェクトであり、ネストさせることができます。

human {
  name = "jinyang"
  age = 2024 - 1997
  hobby {
    outDoor = "Camp"
  }
}

要素

オブジェクトの中には名前を持たないメンバー、つまり要素も含めることができます。要素は、順序付きリストのように扱われます。 例えば、趣味であれば上で示した例よりも下のように記述した方が良いでしょうかね?。

hobby {
  "Camp"
  "Fishing"
  "Hiking"
}

エントリ

エントリは、キーと値のペアでオブジェクトを定義します。エントリーのキーは文字列以外でも使用できます。

Ingage {
  ["jinyang"] {
    name = "jinyang"
    age = 27
    department = "Engineering"
  }
  ["taro"] {
    name = "taro"
    age = 30
    department = "Sales"
  }
}

📝 練習問題

ここで、公式ドキュメントで出題されている問題を解いて見ましょう!

以下のJSONを出力できるように、Pklファイルを作成してください。

{
  "name": "Common wood pigeon",
  "lifespan": 8,
  "friends": {
    "bird1": "Parrot",
    "bird2": "Albatross",
    "bird3": "Falcon"
  }
}

解答例は以下の通りです。

上で述べたプロパティ、エントリを使って解答することができます。

name="Common wood pigeon"
lifespan=8
friends {
  ["bird1"] = "Parrot"
  ["bird2"] = "Albatross"
  ["bird3"] = "Falcon"
}

もう一問いきましょう!

{
  "name": "Common wood pigeon",
  "lifespan": 8,
  "birds": ["Parrot", "Barn owl", "Falcon"]
}

回答例は以下の通りです。今回は要素を使って解答することができます。

name="Common wood pigeon"
lifespan=8
birds {
  "Parrot"
  "Barn owl"
  "Falcon"
}

皆さんできましたでしょうか?

しかし、ここまでやっているとこう思ったのかもしれません。pklを使うメリットなんてないじゃないか!と。ymlを書くのと何が違うのか?と。

長くなると良くないので、次回に続きます。 しかし、ここで少しだけ次回の内容をお話ししましょう。下のコードを眺めて見てください。

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 {}
}

cicd.pkl

amends "AcmeCICD.pkl"

timeout = 1

cicd.pklをyamlに変換すると、以下のようにエラーを吐きます。 いよいよ、pklの真骨頂が見えてきますね。静的解析をしてくれるおかげで、設定ファイルのミスを未然に防ぐことができるのです。

–– Pkl Error ––
Type constraint `this >= 3` violated.
Value: 1

14 | timeout: Int(this >= 3)
                  ^^^^^^^^^
at AcmeCICD#timeout (file:///Users/k.you/Workspace/pkl/AcmeCICD.pkl, line 14)

3 | timeout = 1
              ^
at cicd#timeout (file:///Users/k.you/Workspace/pkl/cicd.pkl, line 3)

106 | text = renderer.renderDocument(value)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (https://github.com/apple/pkl/blob/0.26.3/stdlib/base.pkl#L106)

逆に、しっかり条件に合ったコードをかけば、いろいろなformatのconfiguration fileに変換してくれます

cicd.pkl

amends "AcmeCICD.pkl"

timeout = 10
pipelines {
  new {
    name = "prb"
    branchName = "main"
  }
}
timeout: 10
pipelines:
- name: prb
  branchName: main

ということで、次回のテーマはより複雑な「template」について書きたいと思いますので、もうしばらくお待ちください!

参考資料