Factor 座談会
(2009年12月5日開催)
Factorは連鎖性言語です
しおや
今日取り上げるプログラミング言語Factorというのはタイトルのとおりプログラム言語なんですけれども、
どういう言語かと言いますと、彼らのホームページなんかをチラチラと見ると、表現力が高くて速くて
インタラクティブな開発に向いていて、フル機能のライブラリがあって、スタンドアローンのプログラムが作れますよ、
というようなことが最初の方に出てくるんですけれども、一番大きな特徴は連鎖性言語あるいはスタック指向の言語
であるというところです。
私も今回これをやるまで、それは一体どんな言語なんですかというか、全然馴染みがなかったので、まず最初にスタック指向の
プログラミングはこんな感じですというのを紹介して、後半ではFactorならではの特徴というのを簡単に触れたいと思います。
しおや
連鎖性言語というのはどういうことか言いますと、今普通のプログラム言語、こんな感じのシートを書きますけれども、
っていうのは引数でa=10, b=20とかあって、定義しておいたものを、この例だとbarという関数にbar(a,b)という風に変数名を
渡すことで関数に適用するっていう形でプログラムを書いていきます。
でも、連鎖性言語では、このbarという処理の前にスタックに同じような10と20というデータを用意しておいて、
それをbarに渡して、そうすると結果がスタックに戻ってくるというような流れになります。
上の例と違って、aとかbとか値に名前が付いていないところが特徴です。
このときにデータの固まりを大体はスタックの形で表現しているので、連鎖性言語と言うとスタック指向言語と
呼ぶ場合も多いです。厳密には違う概念です。
実際のコード例
しおや
具体的にはどんな感じでプログラムを書くかというと、Factorの実行環境Windows版があってこれが大変便利なんですが、
こういうインタクラクティブな画面で大体の操作ができます。もちろん、普通のプログラム言語みたいに
テキストエディタで書いてっていうのもできるんですけれど、今日はインタクラティブなFactor環境で紹介を
していきたいと思います。
10 20 +
スタック言語の文法は非常にシンプルで、こうやって項目を並べていくとだんだん入力した言語がスタックに積まれていきます。
10を入れると10がスタックに積まれる。次に20と入れると10の上に20というスタックが積み重ねられていきます。
ここで10と20を足すってやると10と20というスタックに対して“足す”という演算が適用されて、
またデータスタックに30という数字が戻ってくる。続けて書くと、こういう感じです。
10 20 +
おもしろいのは、「10と20を足せ」と日本語と同じ順番でプログラムが書ける。
プログラム言語では必ずやる"Hello World"は、こうHello Worldとスタックに積んでおいて、
それをprintってやるとHello Worldと出てきます。
"Hello World" print
Cの関数、Javaのメソッドに相当するものはFactorではWordと言います。コロンを使って定義します。
:add (x y -- z) + ;
この定義の例ですとaddというWordはxとyという2つの引数をとってzという結果を返しますよという宣言です。
で、処理内容は+(プラス)です。
addって書いてx,yで2つの引数を使います、と書いておいて+です。ここで+ってありましたけれども、
1と2をaddせよとやると3という結果が返ってくると。さらに足したいと思ったら、例えば3という結果に今度は4を
足しなさいって入力すると、まず4によって3と4というのがデータに積まれてaddによって7という結果が返る、
という風にWordを定義していきます。
え〜と、今、x,yって書きましたけれど、Factorの世界ではスタックからいくつ引数をとるかっていうのだけが
意味があって、この名前というのは何となく付いているだけです。この、最後のzを返すというのも、
やっぱり何となくzって付けているだけで、この定義で大事なのは、スタックから2つ値をとって1つ値を
返しますっていうそれだけが意味があって、あとはドキュメンテーションのためというか、
見た人がパッと見てわかりやすいように、こうやって2つの引数をとるんですねっていうのが
すぐにわかるように、っていう程度で使います。
いぬい printはスタックに積まないんですよね?
しおや printは積まないです。
いぬい その場合のzはスタックに積まれるんですか?
しおや そうです。この場合は3と4が引数でとられて、演算したの結果の7がスタックに積まれる。
いぬい それはどうして違うんですか?
しおや printの定義はこんな風になっているんですけれども、文字列を1つとって標準出力に出しちゃうんで、 もう出て行かないんですよ。この画面だとスタックとprintが混ざっちゃってあれなんですけれども。
しおや スタックのおもしろいところは、必ずスタックに入れてから演算するんですけれど、何もスタックのないときや、 1つしか値がないのに足し算をしようとすると、スタックアンダーフローというCとかでは絶対にお目にかかれない エラーが出ます。普通はスタックオーバーフローですからね。
制御構造...if文、while文
しおや
制御コードはどうやって書くかというと、こういう風に条件を入れて、真のときの処理、偽のときの処理で、
最後にifを付けるというような書き方をします。で、このカッコ[]っていうのをクォーテーションと言うんですけれども、
このカッコの中身をまとめてスタックに置く働きがあります。
0 > [ "positive" print ] [ "negative" print ] if
何がどう違うかというと、さっきの例だと1,2,+っていうのは、まず1が乗って2が乗って+って乗っちゃうわけですけれど、
カッコで括ってやるとこれはこのまままとめてスタックに乗るようになります。なので、この機能を使って、例えば
" 3 0 >"は「3が0より大きい」って読むんですけど、これだと3は0より大きいんでtrueが返ってくる。trueかfalseが
返ってくるんですけれど、ここまで入れるとデータスタックには「3が0より大きい」でtrueが入っていて、trueのときの
実行文とfalseのときの実行文がスタックに積まれた状態になっていて、そこでifを入れてやるとそれが評価されてポジティブ、
でこっちの文が実行される。こういう制御構造を取ります。
いぬい ifは3つのパラメータがあるっていうような感じなんですね。
しおや そうです。
かさい データスタックというところに積まれる、データ型っていうんですか一般的に、そういう部分は何でも入れちゃえる?
しおや 入れちゃって大丈夫。
かさい それは自動的にFactorの方で判定してっていうことですか?
しおや その単純な項目、ひとつだけの項目を「リテラル」って言って、このカッコ[]で括ったのを「クォーテーション」って 言うんですけれど、その2種類ですね。どっちでも大丈夫です。
(注: ここではこういう説明をしてますが、ほかにも色々あります)
しおや
while文も似たような感じです。条件文があってloop…ここでdupっていうのはスタックにあるものを
いろいろと操作できるようになっています。dupってやると、今、123があって、もう1回dupってやると一番上の3が1つ増えて、
っていう風にスタックを操作できるんですけれど、while文はここのloop条件でスタックの数字を1つ拾って0より大きかったら
loopを続ける、で、loopの中はスタックの数字から1を引いてHelloと表示する、とまぁ、そういうloopです。
[ dup 0 > ] [ 1 - "Hello" print ] while
これもクォーテーションを並べて書く。whileっていうのもさっきのif文と同じように2つの処理をとって実行する
というような流れになります。スタックの上から1つ取って0より大きかったら1を引いてHelloと表示して、
というのを繰り返せってやるとその通り繰り返してくれると。ちょっとクセがあるんですけれど、
まぁこういう感じで普通のプログラム言語と同じように制御も書けると。
ループと配列
しおや ループもこのクォーテーションを使うんですけれども、10回繰り返すみたいなコマンドは、10回で、 やっぱりクォーテーションで繰り返したい範囲を括ってやって繰り返すと。10 times ですね。
10 [ "Factor rocks!" print ] times
こうやって繰り返してくれる。今ここで10っていうのを渡しましたけれども、例えば配列を渡すこともできます。
ここの例の通り、中カッコ{}で括ると配列になります。
{ 1 2 3 } [ "the number is " write . ] each
これは配列{1,2,3}について、それぞれこの中の文章を実行します。で、まぁ1,2,3みたいなそういう感じで
ループを書いていけます。おもしろいのはですね、配列をフィルタリングするというような操作とかマップをするとか、
そういう操作もできるようになっています。これはさっきのif文が2つの数をとって、というような話をしたと
思うんですけれども、ifとかも関数のように定義がされていて、この辺の様々な処理がプログラム的に
書きやすいようになっております。
: negative? ( n -- ? ) 0 < ; ! 負かどうかを返すword
{ -12 10 16 0 -1 -3 -9 } [ negative? ] filter . ! negativeで判定してフィルタ
この例は先にまずネガティブかどうかという判定するWordを作っておきます。これは引数を一つ取って
trueかfalseを返す関数。で、判定文は0よりちっちゃいかどうかですね。さっきちょっと説明しましたけれども、
ここの?とかnっていうのは何でもいいですし、この?の後に付いてるっていうのもPerlとかやっていると何か意味があるのかなとか
ついつい思っちゃうんですけれども、これはYesかNoかを返すんで?が付いているだけで、純粋にわかりやすくするだけのためのものです。
で、このフィルターっていうWordは、この配列の要素の中から、このnegativeって式に当てはまるものだけを残す、
そういう処理です。この配列からnegativeで選ばれたマイナスのものだけがデータスタックに積まれる、とまぁそういう感じです。
これもCなんかで書いてみようとすると、もうちょっと不器用な感じになる思うんですけれど、
Factorはかなりこういう風にシンプルにコードが書けるのが特徴です。
{ 5 7 9 } [ sq ] map . ! 全部二乗する
これも、一応せっかくあるんで実行だけします。これは配列の各要素をsqっていうWordに渡す。
5,7,9っていうのがあって、それぞれ2乗して25, 49, 81というように出てきます。
引数はないが変数は使える
しおや でですね、今までスタックベースに処理してきたんですけれども、あくまでも引数はないんですけれども 変数というのを使うことができます。さすがにこのデータスタックだけでデータのやりとりをするのは しんどい時があるので、こういう風にシンボルで宣言してやると変数が使えるようになります。 nameっていう文字列をシンボルで教えてやるんですけれども、教える前だとnameって入れても 「そんな単語は知りません」ってエラーになるんですが、「nameというのはシンボルだよ」 と教えた後だとnameっていうのがスタックに積めるようになります。
名前空間もある
しおや nameって変数の値を設定するのも、スタック型言語なのでスタックに入れる形でやります。
"Alice" name set ! 文字列"Alice"を変数nameに代入 name get print ! 変数nameの値を取得して、出力
Alice name set「Aliceをnameにsetしなさい」とすると、スタックには何も反映されていないんですけれども、nameっていう変数に
Aliceが代入されたことになります。そこでnameっていう変数を入れておいてgetっていうのをやるとさっき入れたAliceっていうのが出てくる。
こういう形で変数を作って、そこに値を入れるようなことができます。
で、これだといかにも実行環境でやっているんでグローバル変数に見えるんですけれども、
名前空間のようなものもあって、名前空間を使う場合には、この名前空間で隔離したいところをクォーテーションで
くくっておいてwith-scopeというので「ここだけ特別だよ」というような宣言をしてやります。
"Alice" name set [ "Bob" name set ] with-scope ! ここだけ別の名前空間となる name get print
今、Aliceっていう文字列をnameにsetしてnameをgetするとAliceっていう風に出てくるわけですけれど、
ここでカッコを使って別の名前空間をスタックしてやってBobという名前を入れてやる。
で、このwith-scopeという空間の中だけで操作しますっていうことにしてgetすると、ここでBobっていう文字列を
nameに入れて取り出せたんですけれど、この名前空間の処理が終わってからもう一回名前を取り出してみると、
Aliceになっている。このように、関数のスコープのようなことができます。
と言っても、最初のとおり、普通は処理と処理の間の引き渡しのためのパラメータはないので、変数の使いどころも
普通のプログラムとはちょっと違ってくるイメージですね。
オブジェクト指向もサポート
しおや またオブジェクト指向をサポートしています。オブジェクトの定義はデータベースで言うタプルっていう キーワードで宣言するようになっていて、これはemployeeというよく出てくるタプルの例ですけれども、employeeというクラスを定義する。 で、メンバ変数はnameとpositionとsalaryですよってやると、この瞬間にemployeeというクラスができあがります。
TUPLE: employee name position salary ; ! employeeクラスの定義 employee new ! インスタンスの生成
で、employee, newってやると、新しいemployeeクラスのインスタンスが、このようにスタックに積まれます。
いぬい T{ employee f f f f }の、f f f f というのは?
しおや これはnewをしたばっかりで値が入っていないので、各メンバ変数にはfalseが入っているということです。
いぬい メンバ変数は3つですよね?
しおや もう1つは別の用途で必ずひとつ増える。
しおや 値をセットするときには>>、これはリダイレクションじゃないけど、Taro >>nameでnameっていうパラメータが"Taro"に設定される。 取り出すときには、今もうスタックにemployeeのインスタンスが入っている状態なのでこの操作ができるんですね。 で、今これでうっかり操作しちゃったんですけれど…
いぬい それでインスタンスが消えちゃってる?
しおや 消えちゃってます。だから、これは油断してるとこうやって消しちゃうんですけれど(^o^;) ゲッター, セッターはこんな感じでデフォルトで入ってます。演算を適用するっていうのも、 例えばお給料を1.2倍にしてほしいなぁというときには、1.2倍にするっていう式をchangeを付けて入力してやると。 そうすると4万が4万8千になっております。というような操作ができます。 あと、サブクラスとかメソッド定義、こんな値を変えるだけじゃなくてちゃんとしたメソッドの定義もできるんですけれど、 ちょっと今日はパスさせてください。
ボキャブラリー (Java でいうパッケージ)
しおや でですね、いろいろ書いたプログラムはWordの集合になるんですけれど、ボキャブラリーっていう単位で モジュール管理します。Javaで言うパッケージですね。で、どんなWordもどれかのボキャブラリーに所属する というルールになってまして、今どのボキャブラリーを使ってるかっていうのをIN:で切り替える。 今この実行環境ではscratchpadっていうボキャブラリーを使っていて、 適当に定義したものがこのscratchpadっていうボキャブラリーの中にどんどん投入されていきます。 いろいろモジュールを使いたいなぁというときには出来合いのモジュールがあるので、何とかのモジュールを使いますっていう ようなのをこういう形(USING: IO ;)で宣言してやると、I/OならI/Oのボキャブラリーに所属する関数が使えるようになります。
Show me some code!
しおや
というところを踏まえて、具体的にはどんな例があるかというと、Factorの公式サイトに行くと、
こういう風にShow me some codeっていってサンプルのコードが出てきます。これは毎回変わるんですけれど、
(ここで説明しているのはhttpでXMLを取得するサンプル)ここのUSING:っていうところで、
こういうパッケージを使いますよと、I/Oのパッケージを使います、
カーネルのパッケージを使います、シーケンスのパッケージを使います、httpクライアントを使います、で、
プログラムをこういう風に書いていくわけですね。これはちなみにどういうコードかというと、
factorcode.orgへhttpでアクセスして、ヘッダとボディが返ってくる、nipでスタックの上にあるヘッダを取り除く。
残ったボディをXMLとして解釈して、Aタグを取って、HREFを取り、HREFの属性を取り出してそれを表現する。
要するにこれだけのコードで、元ページにあるリンクが取り出せる。
USING: io kernel sequences http.client xml xml.data xml.traversal ; "http://factorcode.org" http-get nip string>xml "a" deep-tags-named [ "href" attr ] map [ print ] each
(実行環境にコードを入力中・USING: キーワードの処理に時間がかかる)今、httpを使いたいと言ったんで、
factorがいろいろな関連するボキャブラリーを必死に読み込んでいるところです。
(操作中・画面にインデントされたXMLツリーが表示される)えーと、これはプログラムがここまで実行された状態なんで…。
これはhttpで取ったものがXMLとして展開されたものです。さっきから操作しているので、みなさんも薄々お気づきかと
思うんですけれども、スタック型言語は何がいいかというと、こうやって一行一行実行するのが非常に易しい。
まず、URLをデータスタックに積んでhttp-getというのをやると、このようにスタックが2行入ります。
上の行はレスポンスとか200とか入っているところからだいたい想像できると思いますけれども、httpのヘッダが入っています。
で、下の方にDOCTYPEなんちゃらとあるとおり、httpのデータがストリングでそのまま入っています。
で、今回は上のヘッダはいらないのでこのnipというスタックの変数を操作するコマンドなんですけれど、まず一旦、
上の余計な使わないヘッダを消しちゃいます。で、残った方をstring>xmlでXML化すると。
で、そうすると、ただのストリングだったのがXMLっぽい構造になります。
ここでこれをprintするとさっきのような、きれいなインデントした形で出てくるんですけれど、
今はせっかくスタックに乗っているのでそれはやらないで、"a" deep-tags-namedを実行すると、これはAのタグを取ってくるよと。
で、AタグはリンクですからHREF要素があるわけで、それを[ "href" attr ] mapと取り出して、[ print ] each(http://factorcode.orgからリンクされているURLのリストが表示される)。
Factorのプログラムというのはこんな感じですね。httpとかXMLとかいとも簡単にできちゃいましたけど、
これは当然ライブラリが充実しているのでこういうことができると。このfactorcode.orgのサンプルコードはリロードするたびに変わっていきますので、
そのときの気分によっていろいろと遊べると思います。
スタック型言語は自転車に乗るようなもの
しおや
でですね、スタック型の言語によるプログラミング、今ここまでで半分くらい話してきたんですけれど、
作者らのメモによると、これは自転車に乗るようなもので、ちょっと最初は難しいけれど慣れると非常におもしろい。
で、一節ずつ実行できるっていうのは今ご覧になったとおりです。あと、おもしろいのは、
プログラムは並べられているので加工がしやすいです。
100 200 + 1.05 * ! 小計を出して税率を掛ける :tax ( x -- z ) 1.05 *; ! 税率部分をカットしてWord化 100 200 + tax ! カットしたところにペーストするだけ
例えば、このプログラムは100と200を足したものに1.05を掛ける、つまり100円と200円の買い物をして、
それに税率を掛けるというプログラムなんですけれど、ここの税率を切り出したいなぁと思ったら、
いったんWordって形で1.05 * を切り出してやると。で、切り出したところを今つくったWordで置き換えて
やるという風にカット&ペーストするだけでプログラムの整理ができていく。
これが例えば普通の言語だと、taxを定義した後、tax ( 100 + 200 )と()に入れてやって処理ができる。taxの中でもパラメータ名で式を書きなおさなきゃいけなくて、ちょっと冗長。
int x = ( 100 + 200 ) * 1.05; // 元の関数
int tax ( int v ) { return v * 1.05; } // 書換え後
int x = tax ( 100 + 200 ); // 関数名が先になる
一時的に値を渡すっていう処理ではスタックを使うと、値を渡すためだけの名前を付ける必要がないので、非常に整理がしやすいです。 そのかわり、スタックを常に意識しなきゃいけないんで、 いろいろスタックを操作するための命令を駆使しなきゃいけないシチュエーションっていうのも結構あります。 もう何回か出てきましたけど、スタックをduplicateで増やすですとか (dup)、2つめの値を消しちゃうですとか (nip)、 スワップですとか (swap) ですね。プログラム言語とかって必ずスワップって話題になるんですけれど、 Factorではおもしろくもなんともなくて、10 20 swap 「10と20が積んであってスワップ」ってやると「はい、20と10になりました」と。 あと、たまに引数に名前がないので、ちょっとデータフローを意識するというか、素直に置き換えるときに、 あれっ?というような処理をするときがあります。
x [foo] [bar] bi ! ←int x; foo(x); bar(x)
例えば、このプログラムのXっていう変数をfooという処理とbarという処理と2つの処理に任せている、
よくプログラムではあるシチュエーションだと思うんですけれども、普通のプログラム言語ではただ単に並べて書けばいいところを、
スタック型言語ではfooとbarという2つの処理をいったんスタックに積んでおいて、この2つの処理にこのXの処理を渡して
くださいっていう、このbiっていう命令を使って渡すようにします。データの流れを意識して考えてやらなきゃいけない。
ここではfooとbarっていう処理が変数xでつながっているんですけれども、それをbiっていうキーワードでつなげているところが、スタック型言語のスタイル。
スタック型言語の仲間
しおや
でですね、こういう変わった書き方をするスタック言語なんですけれど、歴史そのものは結構古いんですね。
前回、Javaバーチャルマシンが、みたいな話も出ましたけれど、元祖はやっぱりForthっていう言語っていう
ことになると思います。それが出てきたのは1968年ぐらい。で80年代ぐらいにはもうすっかり廃れて
しまっているんですけれども、PostScript(1985)はスタック型言語の代表で、今でも活躍中です。
で、Macでも Open Firmware (1994)っていうBIOSを操作するプログラムがあるんですけれども、
ここでもスタック型言語でプログラムを書いています。JavaでもForthのような仮想マシンがあって、
これはForth言語じゃなくてForthの仮想マシンを元にしてるのでちょっと違うんですけれども。
プリンタ用の言語ですとかファームウェア用の言語ですとか仮想マシンですとかって、見ればわかると思うんですけれど、
組み込み系によく使われる言語なんです。っていうのは構文がシンプルで実装もシンプルなんで、
非常にそういうところに向いている。
で、逆にそれ故にあまり最近ではメジャーな言語ではなくなりつつあったんですけれども、2001年頃にですね、
Joyっていう言語が出ました。これはForthをベースに関数型言語の特徴をいろいろと導入しています。
ただ、それはあまりにも理論に寄りすぎて(=純関数型すぎて)使いにくいらしいので、FactorはForthの流れを
受け継ぎつつJoyの、この関数型言語の特徴というのを生かして発展、開発されています。
じゃぁ、その関数型言語の特徴っていうのはどんなんでしょうっていうのが後半の話になります。
関数型言語としての機能:[]...処理そのものをデータと同等に扱える
しおや
関数型言語としての機能の一つ目はカッコ[]というのがあります。カッコで囲むことによって、
ここの処理ひとつっていうのをデータとして扱えるようになります。つまり、さっき既に何回かお見せしてますけれど、
こうやってデータスタックに値を積むのと同じように、足し算をするという処理そのものを一緒にスタックに乗せることができる。
(factor実行環境で[ 10 40 + ]と入力→計算されず、[ 10 40 + ]とそのままスタックに積まれる)
これはこの画面でやっているから、これはこういう機能があるんですよってサラッと見えるんですけれど、
Cとかだとこういうのは、Cは関数ポインタがあるんで、あるにはあるんですけれども、なかなかやりづらい。
で、今、この[ + ](プラス)っていうのがスタックの一番上に乗っているんですけれど、さっきもちょっと出たcall、
この命令は何かというと、このスタックに乗っている命令を呼び出しなさいという命令です。(ここでcallと入力)
これで+(プラス)という命令が呼び出されるので、+というのは本来の役割を果たして10と40を足しますよ、と。
おもしろいのは、単にcallするだけじゃなくて、こういう風に1.05と掛けるっていう演算が乗っかっていると思いますけれども、
これをcurry化っていうんですが、こうやってスタックにあるデータを元に新しい関数を作り出すことができる。
30 ! スタックに値30を積んでおく 1.05 [ * ] curry ! 1.05と*が合成され、[ 1.05 * ] というcurry化された関数がスタックに積まれる call ! スタック上の30に [ 1.05 * ]が適用される
ここで30ってスタックに積んでおいてやって、curry化した[]にcallってやると30っていう値に1.05*(掛ける)が適用されて
31.5円、端数を切り捨てで31円ですというような計算ができます。
なんで、この機能をうまく使うと、例えば消費税を計算する関数みたいなものを定義することができて、
これは値をひとつ取って関数を返すという関数です、っていうのを定義してやって、
消費税のかけ算は1.05ですよっていうのを教えてやると、100円に対して1.05の税率を適用してください
みたいなプログラムが割と素直に書けるというようになります。
これも、この関数を動的に作るっていうのはやっぱりCではなかなかない。構造体を別の引数で渡すとか、
スタティック変数を用意しておいてそこから渡すとか、そういうことをしなきゃいけない。Javaだと、これ一つをクラスとして
定義するとか無名関数っていうのを普通はやります。こういう風に関数、まぁ処理ですね、
そのものをデータと平等に同じように扱えるっていうのが関数型言語の特徴の一つです。
データとプログラムの文法が一体(Homoiconic)
しおや
またFactorにはhomoiconicっていう性質があって、データとプログラムの文法が一体。
データはこのようにスタックに積まれていますし、プログラムはスタックに積むのを順番に並べていく
だけというような形なので、つまりどういうことかというと、プログラムをスタックに積んでおいて、
それをプログラム自身で加工するということが非常にやりやすい。自分で自分を記述するみたいなことができるわけです。
有名なLisp(カッコのかたまり言語!)なんかは同じ、そういう性質を持っていますけれど、
Factorの場合はカッコもいらない、スペースで区切るだけ、その代わり構造はスタックしか
ありませんっていう作りですね。
で、これらの特徴によって非常にFactor自身が拡張性が高い構造になっています。具体的にどれくらい拡張性が
高いかというと、さっきのifとかはすでに何回かお見せしましたけれど、if自体が既に、普通Cとかだとifっていうのは
あくまでも文法の一部だと思うんですけれども、Factorでは関数として定義されているんですね。
ifというのは引数を1,2,3、3つとってtrueだったらcallしますっていう風に、そういう基本的なものでさえ、
こうやって関数として書けてしまう。whileも確かそう。
もちろん、こういう構文はですね、Factorプログラムのホームページに行ってドキュメントを見ていくといろいろ
あるんですけれど、例えばこの辺で例外の処理なんていうのも、いろいろやり方が揃っているんですけれども、
ここに定義が書いてあるんですね。こんな感じで。これが例外処理の、ここで説明している関数のプログラムコード
そのものなんです。これだけなんです。こういうのを使って例外処理ですとか、オブジェクトのシステムですとか、
オブジェクトの定義みたいなものも…これは組み込みだからこんな感じでですね、これは最初から定義されているやつ
ですからあんまり詳しくあれなんですけれども、まぁそんな感じでFactorの仕組みそのもののかなりの部分が
Factorで書かれてしまうというくらい柔軟性の高い言語システムになっています。
関数型言語としての機能:参照透過性...入力も出力もスタック
しおや
で、もう一つ、関数型言語っていう話をするときに必ず出てくるのが参照透過性、別の言葉で言うと副作用がないっていう
話なんですけれども、Factorはですね、入力がスタックで記録がスタックなので、ということは、Factorの、こういう
文章を書いたときに、このfooっていう関数が何をするかっていうのは、それまでに今データスタックに何が積んであるかで
決まってしまうと。例えばJavaですと、このfooの中はオブジェクトになっていて、メンバ変数があってメンバ変数の状態が
どうのこうのによって動作が変わるっていうことになると思うんですけれども、普通にさっきのsetとかgetを使わない
限りでは、この処理がスタックが受け取ってスタックに返すだけですから、スタックだけでこの関数の動作が決まると。
そうすると、この関数の機能っていうのは実行してる状況に関係なく、「入力と実際のコードだけ」(!)で関数の動作が決まるし、
他の関数同士が相互作用することも基本的にはないということになります。
で、こういうのを副作用のない、あるいは参照透過性を持つって言って、関数型言語でプログラムを書くときには
非常に重要な考え方になります。で、全部これで書ける、これだけでプログラムを書くような言語っていうのも
Haskellとかあるんですけれども、Factorはさすがにそれだとしんどいでしょということでさっき見せた
set, getみたいな方法で変数を保存することができるようになっています。そうすると、関数の内部状態はないので、
これのおかげで非常にユニットテストを書きやすいという特徴もあって、Factorはそれを生かしてユニットテストを
ちゃんと書きましょうという書く機能を最初から備えています。
それはどうやって書けばいいかというと、実行結果が100と200っていうのが…実行文がこうなるはずだというのを
書いておいて、実際に実行するコードを書いてunit-testってやるとテストを実行してくれます。
[ 100 200 ] [100 100 100 + ] unit-test
これは実行結果として [ 100 200 ]っていう状態を期待する。 テスト対象として 100 100 100 に+(プラス)っていう演算を実行する。実行結果が期待通りならOK、そういう風にテストします。
ここで [ 100 100 + ]って書いてたら、100 200 と出るはずのところが200が出まして、みたいなエラーが
出る。このように言語のネイティブな機能としてユニットテストが書きやすくなってます。もちろん、書くからには
こういう風に参照透過性を生かした関数を書かなければいけないんですけれども、作者としてはコードを細かく切って
テストしやすい形で書きましょうと、そういう書きやすい環境を用意しておりますと、そういう趣旨の設計です。
実用性に重点を置いた機能が豊富
しおや
その他の特徴としましてはですね、実用性については重点が置かれています。標準Cライブラリはもちろん、
オープンGLですとかX11、Cocoa、V.32、API、Windows32 API、全部対応しています。でも、Window版はなんか結構、
GUI系は少し動かしにくいんだそうです。で、その他のライブラリもunicode、正規表現、データベース、HTTP/SMTP、
FTPサーバ、アプリケーション系みたいなのも。XMLの処理、暗号化並列処理みたいなライブラリも取り揃っていると。
で、今時の言語っぽくガーベジコレクションですとか型システムですとかデバッガですとか、そういう最近の言語ならないと
困るよねというものは一通り揃っている、非常に実用性の高い環境です。
動作環境なんですけれど、ホームページを見るとこんな感じでバイナリが配布されています。Windows, Mac, LinuxとBSD系が3種類。
ソースコードはC++とFactor自身で書いてある。最初はJavaだったんだそうですけど、作者があんまりJavaでイライラするんで
Cに置き換えたんだそうです。なので、それぞれの環境でスタンドアローンのアプリが作れる。配布するときもランタイムとかは
特になくてもよくて、単体のアプリケーションとして配れる。
で、長々と話してきましたけれども、全体のまとめとしてはスタック指向の言語、特に簡潔なコードが書けて、
切り貼りしながらリファクタリングが非常にしやすい環境になっています。しかも、昔の言語と違って関数型の言語の特徴を
いろいろ取り入れているので、非常に表現力が高い。
で、実行速度もなかなか速いんだそうですね。私もちゃんと調べていないんですけれども、ギョームに聞いたところによると
Javaと結構いい勝負。処理によってはむしろ速いぐらいのスピードが今は出るようになっている。ライブラリもかなり、
実際にこういう風に、ちょっとさっきWebの例とかデモンストレーションしましたけれども、今時の環境を踏まえた
ライブラリというのが整っている、非常に実用性の高い環境です。
プログラマのツールボックスとしては非常におもしろい言語だと思いました。駆け足でしたが、以上、Factorのさわりを紹介しました。
業務への応用例は?
かわの どうもありがとうございました。座談会というより先生の講義が終わったというような感じで^^;。質問したい人はいますか?
ともじ 例えばビジネスアプリていうか業務アプリに使うというのは業務例としてあるんですか?
しおや 業務アプリというのかどうかはあれなんですけれど、Factorの公式サイトのWikiはもちろんFactorで書かれています。 concatenative.orgっていうのがあるんですけれど、ここはWikiになっていまして、これは全部Factorで 書いてあるんだそうです。で、これをホスティングしているサーバもやっぱりFactor。
ギョーム Build スクリプト、IRC、Web site、もちろんさっきのマニュアル、ドキュメンテーションを管理してるウェブサイトはすべてそうです。
あなたがFactorに惹かれるわけ
かわの 私の質問はちょっと切り口を変えるというか…塩谷さんがFactorに惹かれる理由は?
しおや そうですね、最初に話を聞いたときには、スタック指向の言語があるよと、しかし一体、逆ポーランドでプログラムを書いて 何がおもしろいんだろうと(笑)。まぁ、それも確かに頭の体操というか。で、おもしろいのかなと思って興味を 持ったんですけれど、実際に持ってみるといくつか出てきたとおり、まず実行しながらいろいろ見られるっていうのが おもしろいですし、いらないものを書かなくていい。さっきの、値を渡すためだけに変数名を付けなくていいっていうのが ある。それから、ちゃんと勉強してみて初めてわかったのが、こういう柔軟なプログラムの書き方がいろいろできるっていうのが 非常におもしろいですね。その分、使いこなすのが大変なんですけれども。
かわの データとプログラムの文法が一体って、工夫によってはものすごく柔軟というか。プログラムを書き換えていけるわけですよね。
しおや そうですね。プログラムとしては一応昔からLispっていうのはそうでしたし、最近だとRubyは松本さんがLispの ファンなだけあって、その辺を非常に意識されている。メソッドですとかクラスですとかをデータと同じように扱える。
いぬい あれもそうじゃないですか、Meta-Language(ML)。
しおや ありますあります。
かわの 同じ質問をギョームさんにしたいんですけど、Factorに惹かれる理由、魅了を感じる理由というのは何ですか?
ギョーム 今までやった言語とは非常に違うような文法、コンセプトを勉強することでいろいろ頭の刺激というか、 考えさせられる。C言語で書いているプログラムよりも楽しくて、C言語を使いますと、いつも同じような作業で いろんなコードの部分を何回も何回も同じようなものを書かなきゃいけないことがあって...こういうような言語だと 大きなものを小さなブロックでリファクタリングができて、非常に小さいプログラムでパワフルな処理ができます。
かわの Cで書いていたものをFactorで書くと小さくなるという印象を持っていますか?
ギョーム もちろん。必要のないコードは書かなくて大丈夫です。
いぬい 関数をデータとして扱えるというのが大きいんじゃないですか?
ギョーム そう、関数型言語のすべてにある、関数を別の関数に渡せるという特徴。
いぬい
関数型言語は関数をどんどん変化させていけるんですよ。それを駆使すると簡単に高度なやつが書けるんですけど、
C言語のような手続き型言語に馴染んでいる頭だと、なかなか頭が付いていかないんですよね。
SOBAの前にMLを使ってやろうとしたんですけど、うちにいるフランス人の彼はCamlっていうMLの変形型をずっと
メンテナンスしてるんですけど、それでやろうとしたんですけど僕はなかなか馴染めなかったですね。
でも、やっぱりすごいと思いますよ。強力な言語ですよね。深いところまで理解できていないですけど。
これはやってみないとなかなか頭が付いていかないところですね。
かわの 僕はプログラミングができないからわからないんですけど、やっぱり言語によって頭の使い方が違うというか…
いぬい 全然違うと思いますよ。パラダイムが違うと思いますけどね。僕はMLをやったときにパラダイムが違うと思いましたけど、 オブジェクト指向とまたパラダムが違うと言われますけど、MLもなんかよくわからないというか、パラダイムが 違っておもしろいとは思いますけど。
しおや 日本では七色だけど海外では六色、文化圏によっては三色とか二色しかないという話があって、そういう文化圏の人は 本当に二色しか見えていないんだよみたいな話が昔あったんですけれども(注・実際にはそんなことはないそうです)、それに近いです。 Cだと二色にしか見えない虹が、こういう関数型言語だと六色とか七色に見えると、そういうような。
かわの 僕、言語学とかちょっと興味があって勉強してるんですけど、言語の文節機能というのがあって、 使っている言語によって対象を文節を六色にわけるか三色にわけるかみたいなことが変わってくると。 そういうことがプログラミング言語の選択においてもある?
しおや ある。
かわの なるほど。いぬいさんが言ってるML? 関数型言語?
いぬい Meta Laguageって言うんですけど、関数型言語。僕もあんまり詳しくは知らないんですけど、 詳しくはというか…がんばって勉強しましたけど、強力ですよね。そういうのに本当に慣れると そういう思考で考えられるので、最適な書き方が思い浮かぶわけですよ、きっと。 で、僕らは手続き型言語のJavaとかCに慣れてしまっているので、そういう思考で行ってしまうんですけど、 そういう思考ではだめなんですよね。
かわの プログラマの人っていうのは与えられた仕事、この処理をしないといけないっていうのがありますよね。 それに対して言語の選択が許される場合はCを選択するかJavaを選択するか、あまりよく知られていないけど Factorを選択するかを選べるとしたら、Factorを選択すると自分にとってハッピーになれるときがあるかも しれないということですね。
しおや 大いにあります。自分の趣味のプログラムを書く分には特にありますけれど。
かわの そんなにありそう。
しおや プログラマの仕事ってああしたいっていう曖昧なイメージを実際のプログラムの言語に合わせるところにあるんですけれど、 合わせ方って二通りあって、こうしたいっていうイメージをプログラム言語のルールに乗せる方のルールと、 逆にプログラムの方を自分の考えに近いのはどれかなぁというのを近づけてきても書きたいことは書けるようになるです。
かわの やりたいことのイメージはまだ言語化されていないんですね?
しおや
例えば、繰り返しの処理をやりますと。そうすると、CでもJavaでもそうなんですけど(板書)10回処理を繰り返す
プログラムを書いてくださいって言ったら、普通のプログラマは大体こう書くんですよ。while文というのを用意しますと。
で、10回繰り返すので10回繰り返すカウンタを用意してそれを10回分、ひとつずつ数えながらfooというのを実行しますよと。
我々プログラマの仕事としては、10回繰り返しますっていう処理を「じゃぁ、繰り返しならwhile文だから、このwhile文の形で書けます」。
こう繰り返すっていう要件をこのwhileっていうカウンタを使う構文に書き直すのがプログラマの仕事なんですけれど、
でも我々がやりたいのはあくまでも繰り返すという部分であって、この意図をパッと表現してくれる手段があれば、それはそれで構わない。
例えば、それだったらじゃぁ、繰り返すというのは10個の何かものがあるんでしょ、と。その10個のリストっていうのが
既にあるんでしょ、と。そしたら、そのリストの次にfooっていう処理をサラっと書きたい。
10個について繰り返すっていう意図をどれだけ表現できるかっていうのが言語の力なんです。
Cだと、別にCが悪いわけじゃないんですけれど、CPUの世界により近い形でしか言えない。でも、関数型言語だと、
豊かな表現力があって、自分のイメージにより近いことが素直に書けるようになる。
Factorだと困る場合は?
かわの その話を伺っていると、最初からFactorみたいな言語があればよかったんじゃないかと思うんですけど、 他の言語にいろいろ行かないといけなかったのは言語に対して別の要請もあるからですか?
しおや やっぱり(手続き型言語は)「じゃぁ、どんな動作をするの?」っていうときに説明がしやすいんだと思います。
いぬい スタック型言語のForthとかは複雑なプログラムを書くと、スタックの構造を頭の中で解釈しないといけないので、 頭がついて行かなくなるみたいですけどね。
かわの パッと見たときに何をやりたい処理か読めないというか…。
いぬい スタックにこういうデータがあるから、じゃぁこういう処理を積んでいって、それをこうしてというのが、 複雑になりすぎると頭が追いつかなくなる。そこがスタック型言語の欠点という風には言われてますよね。 ただ、そこをちゃんと理解できれば逆に強力だっていうのもありで、更に今回のやつは関数も扱えるというところが。
かわの いぬいさんが理解できないというところは、他の人間が読むときの話ですよね?
いぬい 他の人間が読むときも大変らしいんですけど、自分が複雑なものを書いていくときも大変。
かわの 要するに人間が読んだときに大変。
いぬい そうそうそう。そうらしいですね、僕も詳しくは知らないけど。
しおや 最近はいろいろ言語のバランスとか設計思想とか…
いぬい 長所短所があって、ただ、やっぱりさっきのギョームさんがおっしゃったような“強力な”っていうところが 関数型言語の強力なところだと思います。
かわの 逆に言うと、使いこなせる人が使えば非常にプロダクティビティが高い。
いぬい そうそう。うちのSOBAプロジェクトにいたフランス人が使っているMLはフランス版なんですよ。彼、フランス大好きだから。 で、フランスのCamlをメンテナンスしているコアメンバーだったんですけど、 「これが最強だ!プロジェクトはこれでやるんだ!」みたいな。
かわの 大勢教えてパッとやらせるというのには…
いぬい いや、そんなことはないと思いますよ。勉強して慣れればできると思いますけどね。Camlもグラフィック関係の ライブラリとかが充実してたんで、SOBAプロジェクトの前身はビジュアル的なウィンドウとかを使うやつを作っていたんで、 そういうのをぱぱぱぱ〜と作っていましたけどね。
かわの そうか、わかった。JavaとかCみたいに世間でよく使われている言語だとそれを知っている人も既に世間にいるから、 知っているかどうか聞いて雇えるけど、Factorみたいに世間にあまり知られていないと自分のところで…
いぬい それもあるんでしょうね。本当に考え方違うんでなかなか馴染みにくいとは思いますよ、最初は。でも、 何で流行らなかったんですかね。なぜ流行らなかったのか。
かわの ていうか、今流行らせようとしているんじゃないですか! これから流行るプロジェクトなんだから、これは。
ギョーム 6〜7年開発中ですかね。
しおや そうですね、2003年に最初のバージョンが出て、その頃はゲーム用のスクリプト言語でした。2004年くらいからでしたっけ、 さっきのCを使ったスタイルになったのは。もう結構長いことやってる言語ですよね。
ギョーム 開発スケジュールとしては、まだ安定版というのではなくて0.9*…
しおや バージョン番号調べようと思って、ちょっと見たらよくわかんない日付しか打ってない。
ギョーム いろいろ足りない部分、ネイティブスレッドなど、言語を意識したエディタ、編集が簡単にできて、 現在はFactorのそういうエディタはなかなかなくて、1年後か2年後に安定版を出す予定らしい。
かわの でも、いいことばっかりというか。
いぬい
僕が思うに、言語が流行るとか流行らないとか使われるとか使われないとか、もちろん言語仕様は
重要かもしれないですけど、環境とかもあるじゃないですか。僕らはJavaでプログラミングして
製品を出しているんですよ。それは当然ユーザインタフェースもあって作るんですけど、
ユーザインタフェースの表現力が落ちるんですよね、Windowsとかの表現力に比べると。
それは、そのJavaで用意されているライブラリというのが僕らから言うとまだまだ。
どちらかというとFlashとかの方が表現力豊かに作れるんですよ。それは、ちょっと言語仕様とは
違う次元の話になるじゃないですか。
だから、今日のFactorも、もちろん言語仕様も要素としてあるんでしょうけど、その周辺の環境、
さっきおっしゃったような安定版が出る出ないとか、そういう要素もすごく大きいので、
これが流行るかどうかというのは言語仕様だけではなかなか判断できない。
かわの 結局、流行るかどうかは世間のことだけど、自分にとって役立つかどうかというのは条件があって、今おっしゃったように 自分が必要なライブラリが用意されているか、さもなきゃ自分で作らないといけないのかとか、 自分がやっている業務がビジュアルなUIを必要とするかしないかとか、いくつかの条件があるけど、 全部クリアできたらFactorが機能する領域があって、そこで使う分にはいいわけですね…。
いぬい
そうそう、そうだと思いますよ。でもね、ほんとその環境というのは重要で、Javaアプレットってあるじゃないですか、
ブラウザの中で動く。あれもFlashとかに比べるとやっぱり落ちるんですよね。Sunマイクロシステムズでは
力を入れると言いながらもまだまだダメだと僕は思うんですよ。そうするとどうしてもFlashで書きたくなる
じゃないですか。そこはホント、その環境というのが重要だと思いますよ。
自分だけ満足できればいいって、それは自分だけ満足できればいいですけど、それはすごい狭い世界に入っていきますよね。
そういう人は少ないじゃないですか。
かわの 自分だけ満足できればいいというか、処理の分野というか内容によるというか。ユーザインタフェースが からまないところだったらいらないわけですよね、Flashは。
いぬい そうですね。
プログラマは自転車に乗って...
しおや 関数型言語の元祖ってやっぱりLispなんですけれど、Lispってプログラム言語の中でもすごく歴史が古い部類。 だから、さっきの「関数型言語で書けばこんなに便利なプログラムが書ける」っていうのは何十年も前に Lispは通ってきた道なんですよ。ベイジアンのスパムフィルタを書いた人、Paul Grahamっていう人がいて、 この人はLisp大好きなんですけれど、「Lispを使えば普通のプログラマのずっと上を行けるんだから、 これをやれば勝ったも同然」っていうようなことを何年か前に言ったんですけれど、誰も業務用システムをLispで書いたりなんか、 いやしてる人はしてるかもしれないですけれど、全然メジャーにはならない。
いぬい Lisp使う人はLisperって言われてますからね。
かわの 僕が学生の頃もあったよ、Lisp。
いぬい Lispはありますよ、Prolog、emacsはLispですよね。…確かにLispの商品というのはあんまり思い浮かばない。
しおや 聞かないですよね。
かわの でも、ギヨムさんとか塩谷さんがやっぱりFactorとかに魅力を感じる部分っていうのがあるっていうのは 生産性だけじゃないですよね。その可能性っていうか…
しおや もちろん書いてて楽しいっていうのはありますよね。
かわの 書いてて楽しいんですよねぇ。
しおや こう、自転車に乗っているような感じというか。
かわの 頭の中にイメージしたものがストレスなく表現できるってことですよね。
しおや そうですね。やっぱりそれはすごく大きい。
かわの なるほどねぇ。
しおや ちょっとこれだけのことをしたいのに、クラスを定義して、メソッドを書いて、コンストラクタを用意して… っとかってやらなくて済むときがあるっていうのがやっぱりストレスフリーでいい。
かわの その説明が一番わかりやすい。
(以上、インフォサイエンスにて)