2012年1月11日水曜日

SmartDoxの実装技術

昨日はSmartDoxの紹介をしましたが、 今日はSmartDoxの実装技術について説明します。
既存のプログラムの改良では、今まで積み上げてきたアーキテクチャや 使っているフレームワークの関係があって、 思い切ったコーディング方針の変更はなかなかできません。
SmartDoxは新規開発なので、練習も兼ねて ボクが認識している今風のScalaプログラミングを取り入れてみました。
  • Scala
  • sbt
  • conscript
  • GitHub
  • 関数型プログラミング
  • 代数データ構造
  • Scalaz
  • パーサーコンビネータ

Scala, sbt

実装言語Scala、ビルドツールsbtは現在の既定路線です。
sbtは、後発だけあって何かと使いやすいのと、Scalaプログラミング特有の 色々な事情をハンドリングしてくれるので、Scalaプログラミングでは必須です。
たとえば、Scalaは、コンパイルしたScalaバージョンの異なるライブラリのバイナリ互換が鬼門で、 JavaだとJARファイルが libname-1.0.jarとなるところを、Scalaだとlibname2.9.1-0.1.jarといった具合に 使用するScalaライブラリのバージョン(2.9.1)をファイル名に埋め込むのが、基本的な運用に なっていますが、sbtはこのあたりをうまくハンドリングしてくれます。
編集、デバッグにはEclipseを使っていますが、sbtのEclipseプラグインで「.classpath」を生成して、 ライブラリの依存性を取り込む運用にしています。
ただし、sbtだけでは力不足のところもあります。 以下の処理はsbtでやり方を見つけられなかったのでmavenとantを併用しています。
  • JavaのみのプロジェクトのJar作成(2.9.1を付けないJar名)
  • FTPによるmavenリポジトリへのアップロード(sftpならできるのですが、昔ながらのftpはダメみたい)

conscript

SmartDoxのインストールには conscript を使いました。
conscriptはGitHubに格納したアプリケーションの構成情報を使用して、 アプリケーションの依存関係を解決した上でローカル環境にインストールしてくれる インストーラです。
プログラムの提供側、利用者側のどちらもかなり便利なので、これから広く使われるように なるのではないかと思います。

GitHub

ソースコードの管理はGitHubを使っています。 GitHubはUIが使いやすいので、 このところ新規プログラムはGitHub上で開発していますが、 SmartDoxの場合は、conscriptを使うというためという理由もあります。
GitHubは、ソースコードのバージョン管理という枠組みを超えて、 conscriptのような形でソフトウェアリポジトリ的な使われ方もしてきています。 こういう新しい応用が出てくるので、使っていて刺激的ですね。

関数型プログラミング

関数型プログラミングは、ボクがLispをかじっていた頃(25年ぐらい前)はラムダ計算をベースに、 List、クロージャといった部品を使ってプログラミングしていくものでしたが (さらにいうと綺麗につくると性能がでないので、手続き型的なプログラミングにしたり、 nreverseといった破壊的な関数を使うのがバッドノウハウ)、現在は状況が一変しています。
まず ハードウェア性能の向上で、関数型言語の宿命だった動作性能は事実上あまり問題にならなくなっています。 Javaを使って大丈夫な用途であればScalaでも基本的には大丈夫と考えてよいでしょう。 RubyやPythonで大丈夫な用途であれば、Scalaだと逆に高速に動作しそうです。
技術的には、 モナド、型クラスという新しい技術が登場し、これが新しい関数型プログラミングの基盤になって います。
ボクも2008年にScalaを始める前は、 昔の関数型プログラミングのイメージで関数型言語を捉えていたのですが (List処理が便利で開発効率がアップとか)、 現在では全く別物と考えています。 モナド、型クラスはそれだけのインパクトのある技術ということが分かりました。 いつの間にか、こんなことになっていたのか、という感じです。
さらに、Scalaでは関数型プログラミング(FP)とオブジェクト指向プログラミング(OOP)の融合した Object-Functional Programming(OFP)という概念が提唱しています。
歴史的な蓄積やOOADからの連続性を考えると、 OOPは今後も軸の一つで在り続けることになると予想されます。 ここにFPの記述力をどのように融合させていくのかということが、 実務の世界では非常に重要になるのは明らかで、 OFPはこれは今後大発展する分野と考えています。
このあたりを意識しつつ SmartDoxの開発では、今風のFP的な技術をできるだけ使うことにしました。

代数的データ型

まずデータ構造として、代数的データ型(Algebraic data type)を使用します。 Scalaでは、代数的データ型は直接サポートされていませんが、 case classがこの目的での利用を想定して提供されています。 たとえば A Scala Tutorial for Java programmers にも以下の記述があります。 このあたりの背景は、 Object-Oriented Pattern Matching に詳しいようです。
In Java, such a tree would be represented using an abstract super-class for the trees,
and one concrete sub-class per node or leaf. In a functional programming language,
one would use an algebraic data-type for the same purpose. Scala provides the concept of case classes which is somewhat in between the two
今までは、内部データ構造はオブジェクト指向的な作り方にしていたわけですが、 SmartDoxではcase classを使ってimmutableな構造をにしてみました。
オブジェクト指向的な作りの場合、可変オブジェクトを複数のコンポーネントで共有して 少しづつ変更を加えていくプログラミングモデルになりますが、 代数的データ型(case class&immutable)にすると、コンポーネントで全複写しながら変換する プログラミングモデルになります。 immutableにすると、なにか不測の事態があったときの回避方法が限られるためOOP派的には 非常に怖い選択ですが、 FPでは避けて通れない道です。
SmartDoxでは、このプログラミングモデルにチャレンジしてみたわけです。 今の所、特に問題もなく、このプログラミングモデルでもやっていけそうな感触を得ています。
また、代数的データ構造を取っても、いざという時には型クラスの技法を使って 色々細工ができそうという読みもありました。 なにか問題が出てきたら試してみようと思っています。

Scalaz

Scalaz は、型クラスと純粋関数型データ構造を提供するScalaライブラリです。 Scalaの暗黙型変換、暗黙パラメータの機能を利用してHaskell的な型クラスを提供しています。
Scalaプログラミングを足掛け4年程やってきて分かってきたのは、 FPは関数の合成でプログラミングしていくのがコツということです。 Scalaでも提供されているMonadという仕組みは、この関数合成のメカニズムの一種で 普通の関数合成(composeやandThen, orElseなど)よりもより強力な関数合成を可能に します。 このMonadを活用したプログラミングスタイルをMonadicプログラミングと呼ぶようです。
Scalaの基本機能にもMonadはあるので、ScalaだけでもMonadicプログラミングは できるのですが、Scalazの強力な型クラス群を使用すると、さらに強力な Monadicプログラミングを行うことができるようになります。
Scalazが用意している型クラスはたとえば、Monoid, Functor, Applicative Functor, Monad といったもので、MonadをKleisli圏で合成するようなこともできるようになっています。 このあたりの型クラスを手足のように使えるようになるのが現在目標としていることで、 そのためにSmartDoxではできるだけScalazを使ってプログラミングする方針にしています。
Scalazを使うもう一つの効用は、Scalaで型クラスを使うためのよいお手本になるということ。 Scalaでは、型クラスの機能は直接サポートされておらず、暗黙パラメタ、暗黙変換の機能を駆使 して実装する必要があります。この実装技術をScalazで学ぶことができます。
型クラスは、フレームワークと実装クラスを疎結合に保ちつつ(静的な)多態性を可能にする 非常に強力な言語機能と理解しています。 Scalazの技法を使えば、Scalaでも型クラスを使った プログラミングが可能になるわけで、いずれ自分のプログラミング技法に 取り入れたいと考えています。

パーサーコンビネータ

SmartDoxでは、 SmartDox文書のパースにパーサーコンビネータを使ってみましたが、 驚くほど簡単にパーサーを書くことができることが分かりました。
パーサーの記述はBNF的なDSLになっていますが、基本的な動きとしては MonadicでかつApplicativeという感じです。 Parser[T]を返す関数がMonadic的な動き、その中で「^^」メソッドに渡すクロージャが Applicative的な動きと思います。
簡単なパーサーであれば、BNF的DSLという理解の範囲で使えますが、 細かいことをしようとするとMonadicかつApplicativeな動きを理解して いないと辛そうです。 パーサーコンビネータをある程度使いこなせるようになったのも、 Scalazを使ってMonadicプログラミングに慣れてきた効用かなと 思います。
パーサーコンビネータを使っていて分かったのは、 パーサーコンビネータは文字列以外にも使えるということ。 DOMなどのXML木やマイ代数的データ型からパターンマッチングで構造を抽出して ドメインモデルを構築するという用途には簡単に適用できそうです。 翻って考えてみればこのあたりがMonadicプログラミングの威力ですね。


以上、SmartDoxを素材にScala+Scalazを中心としたFP技術を概観してきました。 15年前はC→Javaによって、OOPがメインストリームのプログラミングパラダイムになり プログラミングの生産性が大きく向上しました。 それと同等の大きなムーブメントがFPの本格導入により起こりそうです。 もちろん前述したように、OOPは今後も軸の一つとなり続けるでしょうから、 OOPとFPを融合した新しいプログラミングパラダイムという形になるでしょう。
最後にどのプログラミング言語が残るのかは分かりませんが、現時点では Scala+Scalazで技を磨いておくのが有力な選択肢だと思います。

0 件のコメント:

コメントを投稿