Puppetアドベントカレンダー22日目!! です。あと3日で...埋まるらしいですよ。
今回は、Puppet as a Declarative Language(宣言型言語)としての側面について考えてみます。
宣言型言語とは何か
Wikipediaの 宣言型言語/宣言型プログラミング の項目から引用します。
宣言型プログラミング(英: Declarative programming)は、...2種類の意味がある。第1の意味は、処理方法ではなく対象の性質などを宣言することでプログラミングするパラダイムを意味する。第2の意味は、純粋関数型プログラミング、論理プログラミング、制約プログラミングの総称である。
ここで、「第2の意味」にあたる各種具体的なパラダイムの言語は、結果的に「第1の意味」を実現しやすいパラダイムであるから、しばしば同じように使われるのだと考えられます。
宣言型プログラミングについてもう少し具体的に考えてみると、Wikipediaの項目にある「内部DSL」は、例えばRubyのいわゆるクラスマクロなどに代表されるものでしょう。たとえば以下の宣言は、処理としては「Fooクラスに対して@bar
のゲッターとセッターを定義する」という中身になっていますが、宣言的な側面として、「Fooクラスはbar
と言う名前の属性を持っている」と解釈もできます。
class Foo
attr_accessor :bar
end
ちなみにPuppetは、いわゆる「外部DSL」にあたります。
Prologで数独を解くサンプル
もう一つ、宣言型言語の代表である Prolog(SWI-Prolog version 7.2.2を利用) で数独を解くサンプルを見てみます。以下のような 4x4 の数独を解くための sudoku.pl
を用意します。
% -*- mode: prolog -*-
:- use_module(library(clpfd)).
main( Rows ) :-
sudoku(Rows),
maplist(writeln, Rows).
sudoku(Rows) :-
length(Rows, 4),
append(Rows, Vs), Vs ins 1..4,
maplist(all_distinct, Rows),
transpose(Rows, Columns),
length(Columns, 4),
maplist(all_distinct, Columns),
Rows = [A, B, C, D],
blocks(A, B), blocks(C, D).
blocks([], []).
blocks([A,B|R1], [C,D|R2]) :-
all_distinct([A,B,C,D]),
blocks(R1, R2).
これで数独を解いてみましょう。
$ swipl -qs sudoku.pl
?- main([[_,_,1,2],
| [_,_,3,4],
| [1,_,_,_],
| [_,3,_,_]]).
[3,4,1,2]
[2,1,3,4]
[1,2,4,3]
[4,3,2,1]
true.
Prologの興味深いところは、 sudoku.pl
では、
- 各列、行、2x2のブロックには1 ~ 4の数字が入る
- 各列、行、2x2のブロックにはバラバラの数字が入る
といった、数独の解の「条件」しか記述していないところにあります。
この記述された条件に、解きかけの数独のフィールドを渡すと、全てのマスが埋められた数独の解答に収束していく、というのがPrologのプログラミングです。無論、回答が不可能な数独の場合、「収束しない」という結果もわかります。
Puppetの宣言型の側面
ここでPuppetのコードを振り返ってみます。
user { 'udzura':
ensure => present,
}
file { '/home/udzura/.bashrc':
content => template('sample/home/udzura/.bashrc'),
}
package { 'nginx':
ensure => '1.9.7',
}
このマニフェストは、サーバの状態について、「udzuraというユーザがいる」「/home/udzura/.bashrc
というファイルがあり、こういう中身である」「nginxのバージョン1.9.7のパッケージが入っている」といった宣言を行っているに過ぎないと言えます。
しかしPuppetは、この宣言を読み込んで、 サーバをこれらの宣言を満たす状態に収束してくれる のです。ここが、単にシェルスクリプトでサーバプロビジョニングを行うことと、構成管理システムを利用することの大きな違いです。
この宣言型パラダイムは、構成管理システムにとって重要な要件である「冪等性」とも大きくかかわりがあります。
また、だからこそ、 exec
タイプは少しだけ扱いが難しいのです。 exec
の宣言は、実行するコードが本質なのではなく、実行した結果どういう副作用が起こるか、どういう状態に集約されるか、という点に着目しないと、マニフェストの宣言性、冪等性を維持することが難しくなる場合があります。
また、Defined Typeの定義についても、単に共通のセットアップ処理を切り出すだけではなく、「あるべきサーバーの状態を何かしらを切り出して宣言しているような書き方」を目指すべきだ、ということもお分かりいただけるかと思います。 参考。
Puppet’s Declarative Language: Modeling Instead of Scripting にもある通り、 You tell Puppet what you want the system to look like, not the steps to get there
(Puppetには、システムがどうあるかだけを教え、どのように実現するかは考えない)という意識をもっていくこと、それがPuppetの学習、ひいてはその他の構成管理ツールの使いこなしに影響するんではないかなあ、と考えます。
割と、用語の不正確な感じで恐縮ですが、
SQLなどと同様、 通常のプログラミングとは頭を切り替える必要がある という意識を共有できれば、と思いこんな記事を書きました。
明日は、 @black2rock さんです!