Vision Pro で盛り上がる最中、Apple からコンフィグレーションファイルを生成するための静的型付言語「Pkl」がオープンソースで公開されたので軽く触ってみました。
github.com
発音は "Pickle" (「ピックル?ピクルゥ?」 )だそうで、 Pickle と聞くと Python の "Pickle化" が頭をよぎりますが、今回はそこには触れずに進めていきましょう!
Installation
公式ドキュメントの Installation からインストールします。今回は M1 MacBook Air から試してみるので、 Native macOS executable for amd64 (tested on macOS 10.15)
を使います。
$ curl -L -o pkl https://github.com/apple/pkl/releases/download/0.25.1/pkl-macos-amd64
$ chmod +x pkl
$ ./pkl --version
Pkl 0.25.1 (macOS, native)
単に実行ファイルが降ってくるだけなので、実用を考えるなら ./.pkl/bin
みたいなディレクトリを作っておいた上でダウンロードして、 .zshrc
などに export PATH=$PATH:$HOME/.pkl/bin
と追加しておくと任意の場所で実行できるようにできます。(Go風味)
触ってみる
コンフィグレーションファイルを生成するための言語として CUE などが比較にあがりますが、 Pkl がユニークなのはコンフィグレーションファイルの生成だけではなくアプリケーションへ埋め込み可能な言語でもあることでしょう。現状 Language Binding はすでに
がサポートされており、今後も増えていくよと語られています。Go が最初からサポートされているのははっきりとターゲットを意識しているな〜と感じました。
github.com
Swift
せっかくなので Swift で触ってみます。 Quickstart に沿ってやっていきましょう。追加でバイナリが必要なので落としてきます。
$ curl -L https://github.com/apple/pkl-swift/releases/download/0.2.1/pkl-gen-swift-macos.bin -o pkl-gen-swift
$ chmod +x pkl-gen-swift
バージョンが表示されていればOKです。
$ pkl-gen-swift --version
0.2.1
Vapor を使って、簡単なWEBアプリケーションサーバーのプロジェクト用意しておき、そこに pkl-swift
を依存として追加します。
import PackageDescription
let package = Package(
name: "hello",
platforms: [
.macOS(.v13)
],
dependencies: [
.package(url: "https://github.com/vapor/vapor.git", from: "4.89.0"),
.package(url: "https://github.com/apple/pkl-swift", from: "0.2.1"),
],
targets: [
.target(
name: "Generated",
dependencies: [
.product(name: "PklSwift", package: "pkl-swift")
]
),
.executableTarget(
name: "App",
dependencies: [
"Generated",
.product(name: "Vapor", package: "vapor"),
]
),
.testTarget(
name: "AppTests",
dependencies: [
"Generated",
.target(name: "App"),
.product(name: "XCTVapor", package: "vapor"),
.product(name: "Vapor", package: "vapor"),
]),
]
)
今回は Example で紹介されているサーバーのホストとポートを環境ごとに用意するユースケースで確認してみましょう。まず AppConfig.pkl
というファイルを作り、以下のように書きます。
// AppConfig.pkl
module AppConfig
host: String
// UInt16 は Int(isBetween(0, 65_535)) の typealias として定義されています
port: UInt16
AppConfig
モジュールからローカル環境向けのコンフィグを作ります。
// pkl/Local/config.pkl
amends "../AppConfig.pkl"
host = "localhost"
port = 8080
pkl
コマンドで正しく設定できているか確認できます。
$ pkl eval ./pkl/Local/config.pkl
host = "localhost"
port = 8080
今回はそのままアプリケーションから読み込んで使いますが、 JSON や YAML にも当然変換できますね。
$ pkl eval -f json ./pkl/Local/config.pkl
{
"host": "localhost",
"port": 8080
}
$ pkl eval -f yaml ./pkl/Local/config.pkl
host: localhost
port: 8080
実際に書き出すなら、-o
オプション*1をつければいい。
$ pkl eval -f json -o config.json ./pkl/Local/config.pkl
実際にSwift アプリケーションから使えるようにするには pkl-gen-swift
で Swift ファイルを生成します。
$ pkl-gen-swift pkl/AppConfig.pkl -o Sources/App/Generated/
すると、以下のようなファイルが生成されます。
import PklSwift
public enum AppConfig {}
extension AppConfig {
public struct Module: PklRegisteredType, Decodable, Hashable {
public static var registeredIdentifier: String = "AppConfig"
public var host: String
public var port: Int
public init(host: String, port: Int) {
self.host = host
self.port = port
}
}
public static func loadFrom(source: ModuleSource) async throws -> AppConfig.Module {
try await PklSwift.withEvaluator { evaluator in
try await loadFrom(evaluator: evaluator, source: source)
}
}
public static func loadFrom(
evaluator: PklSwift.Evaluator,
source: PklSwift.ModuleSource
) async throws -> AppConfig.Module {
try await evaluator.evaluateModule(source: source, as: Module.self)
}
}
AppConfig.Module
構造体とpklモジュールから AppConfig.Module
に評価・変換する関数が含まれていますね。
生成された関数 loadFrom
を使って、先ほど作ったローカル環境向けの設定ファイルを読み込めば、意図した環境の AppConfig.Module
が得ることができます。あとはそれらをアプリケーションに適用するだけです。
import Generated
import PklSwift
import Vapor
public func configure(_ app: Application) async throws {
let config = try await AppConfig.loadFrom(source: ModuleSource.path("pkl/Local/config.pkl"))
app.http.server.configuration.hostname = config.host
app.http.server.configuration.port = config.port
try routes(app)
}
swift run
で動かせば、 Local/config.pkl
で設定した値でサーバーが起動することが確認できるはずです。
[ NOTICE ] Server starting on http://localhost:8080
ざっくり触ってみるところまでやってみましたが、Pkl という言語の表現力にはあまり触れることができませんでした。それはまた別の機会にやってみたいと思います。
言語仕様については紹介しきれない箇所が沢山(throwできたり、Null Value があったりなどなど)あるので、気になる方は公式ドキュメントをご確認ください。
最後に
CUE や toml でいいという声も聞こえてきそうですが、哲学も異なりますし、Framework Integrerations のサポートは Spring (Boot) のみという状況から見ても、Apple の内部のバックエンドサーバー開発のために作ったものを公開したという感じなんですかね(?)。ちなみに公式サイトでは "Incredible IDE Integration" と謳っていますが、 Xcode への統合サポートは現状ありません。Apple 製というのもありしっかり Plist 生成のサポートもあります。今後のアップデートに期待ですね!