どう書く?orgでScalaを書いてくれる人もでてきたので、超一部の方向けにScalaのことでも書いてみようと思います。言うなれば、基礎を終えた人のScalaミニtips。知っている人は知っている、でもあまり知られていないことを並べていきます。

下に行くほどマニア度あがります、たぶん。FPよりの話が多いかもしれません。はやりのYコンビネータの話とか。たぶん。

ではいってみましょう。

Predefされているものは把握しておきましょう

Scalaにはscala.Predefというオブジェクトがあります。この中で定義されているものは常にインポートされていて使える状態になっているので把握しておきましょう。

例:

 1def exit(status: Int): Nothing = {
 2  java.lang.System.exit(status)
 3  throw new Throwable()
 4}
 5
 6def assert(assertion: Boolean) {
 7  if (!assertion)
 8    throw new java.lang.AssertionError("assertion failed")
 9}
10
11def print(x: Any) = Console.print(x)
12def println() = Console.println()
13def println(x: Any) = Console.println(x)

C言語ライクなforはどうかくの?

こうです。

1val a = Array(1,2,3,4,5)
2var i= -1;while({i += 1; i < a.size;}) {
3  println(a(i))
4}

setterを使おう

setHogeみたいなのは、カッコわるいです。RubyのアクセッサーしかりPythonのpropertyしかり、 instance.name = value 形式で扱えるのがモダンな言語というものです。

1class Test {
2  var _name = "default"
3  def name_=(newValue:String) { _name = newValue }
4  def name = _name
5}
6
7val a = new Test
8a.name = "new"
9println(a.name)

単項演算子も定義できます。

できます。

 1class Test {
 2  var _name = "default"
 3  def name_=(newValue:String) { _name = newValue }
 4  def name = _name
 5  def unary_- = "unary:" + name
 6}
 7
 8val a = new Test
 9a.name = "new"
10println(-a)

applyでオブジェクトをメソッドのように呼び出せるよね? hoge(index) = value はオーバーライドできないの?

できますよ。なんとupdateというすげーふつーの名前のメソッドを定義するんです。

 1object dictionary {
 2val data = Array(null, "A","B","C")
 3
 4def apply(x:String) = x match {
 5  case "one" => data(1)
 6  case "two" => data(2)
 7  case "three" => data(3)
 8}
 9
10def update(x:String,y:String) = x match {
11  case "one" => data(1) = y 
12  case "two" => data(2) = y
13  case "three" => data(3) = y
14}
15
16}
17dictionary("one") = "X"   
18dictionary("two") = "Y"
19dictionary("three") = "Z"
20println(dictionary("one")+","+dictionary("two")+","+dictionary("three"))

可変長引数は取れますか。また、リストや配列を展開してメソッドにわたせますか。

もちろん。

1def sumPlus(plus: Int, n: Int*) = plus + sum(n :_*)

JAVAのObject型可変長変数をとるメソッドはどう呼びますか?

ちょっとめんどくさいですが、こうです。

1String.format("%d %s", List(1, "hoge").map(_.asInstanceOf[AnyRef]).toArray)

インスタンスのメソッドを束縛できますか?そのとき、メソッドがオーバーロードされている場合はどうしますか?

こうします。オーバーロードされている場合は _ の後ろに型をつけます。

1val format = (new SimpleDateFormat("dd")).format _:Date => String

ここからFPよりです。

カリー化はできますか?

Function.curriedを使います。uncurriedもあります。

1def test(i:int, j:int) = {
2  printf("i:{0}, j:{1}", i, j)
3}
4val f = Function.curried(test _)(1)
5f(1)

遅延評価は?

lazyを使います。Streamも使いこなせるとハッピーです。

たとえば、無限フィボナッチ数列は以下のように定義します。

1lazy val fib: Stream[Int] = Stream.cons(0,
2     Stream.cons(1, fib.zip(fib.tail).map(p => p._1 + p._2)))

先生、関数合成がしたいです・・・

Haskellでは短く直感的にかけるからいいですよね。

Scalaでは正直、逆に長ったらしくなりますが、どうしてもというなら・・・

1({ x:int => x + 1 } andThen { x:int => x * 2 })(3) // => 8
2({ x:int => x + 1 } compose { x:int => x * 2 })(3) // => 7

forはモナドに使える構文です。リスト内包表現用ではありません。

リストはモナドですし、HaskellのMaybeに相当するOptionというモナドもあります。map, flatMap, filterメソッドを実装すればforで書けるオブジェクトを定義できます。

Yコンビネータとかいうものの話が(一部で)盛り上がってますが、そういうのってScalaでできるんですか。型があるから複雑だと思うんだけど。

えーと、無名関数で再帰したいんでしょうか?こんな感じでどうでしょう?ふつーはこんなの使いませんけどね。

1def Y[A,B](f:((A=>B),A)=>B,x:A):B = f((x1:A)=>Y(f,x1),x)
2
3println(
4  Y(
5    (f:((int,int)) => int, arg:(int,int)) => arg match { case (x, y) => x match {
6      case 0 => y
7      case _ => f((x - 1, y + x))
8    }} , (10, 0))
9)

さて、数少ない日本人Scala好きの皆さん。どれくらいご存知でしたでしょうか。まぁ、基礎からちょっとScalaやればこれくらいは皆さんご存知だと思うんですが、やはりScalaはカオスな言語なので、なかなか見つけられない機能も多いんで、最近Scalaやりはじめた人は参考にしてもらえるとうれしいです。


誰もが一度使うと便利さと気軽さに感動するRubyが誇るライブラリ、 Rake

プログラムのビルドもそうなんですが、雑多なタスクを簡単に書けて、整理できるのがなんといっても魅力的。RailsなんかではDBの作成から何から、ばんばんRakeタスクにされていますよね。

 

さて、俺はPythonistasなので、PythonでRakeみたいなのがほしいわけです。ビルドに限っていえばPythonは Scons という素晴らしいツールがあります。C言語はおろか、JAVA、PDF、PostScriptなどなど、さらにはSubversionもサポートしていますし、並列コンパイルもでき、実績も多数で申し分ありません。

でも俺がしたいのは、雑多なタスクを放り込む、コレ。そういうのを簡単にやるライブラリってPythonではないんでしょうか。RubyではRakeが標準添付されるというのに。いえ、あります。前からあるんです。それが distutils

distutils(およびその拡張のsetuptools)はPythonの標準的なパッケージ配布システムとして有名です。でも実は自分でコマンドを定義してRakeのように使うことができることはあまりしられていないのではないでしょうか。

distutilsでコマンドを定義する

distutilsでコマンドを定義するにはdistutils.core.Commandクラスを継承し、色々オーバーライドします。説明もつけられますし、オプションもとれて、sub_commandsで依存関係(コマンド分割)を設定することもできます。

 1class MyCommand(Command):
 2  description = "コマンドの説明"
 3  user_options = [("host=", "h", "hostname")] # オプション
 4  sub_commands = [("pre_task", None)] # サブコマンド
 5  def run(self): pass # 実行する内容
 6  def initialize_options(self): # オプションの初期化
 7    self.host = None
 8  def finalize_options(self):
 9    if not self.host: raise "Error"
10
11setup(cmdclass={"my_command":MyCommand})

といった具合です。非常にPythonicなやり方ですね。ただ、いろいろオーバーライドしないといけないので面倒です。そこで、俺がコピペで使っている自家製テンプレの登場です。

もっと簡単にdistutilsでタスク

30行程度のテンプレを足して、簡単にタスクを定義できるようにしましょう。namespace機能はPython2.5以上限定です。

 1from __future__ import with_statement
 2from distutils.core import Command, setup
 3from contextlib import contextmanager
 4
 5_cmds = {}
 6_namespace = []
 7_get_ns = lambda:_namespace and "_".join(_namespace)+"_" or ""
 8class CommandType(type):
 9  def __new__(cls, class_name, class_bases, classdict):
10    d = dict(user_options=[], finalize_options=lambda s:None)
11    d.update(classdict)
12    def _(self):
13      [setattr(self,i[0].rstrip("="),None) for i in d["user_options"]]
14    d["initialize_options"] = _
15    d["boolean_options"] = [i for i,j,k in d["user_options"] if not i.endswith("=")]
16    def _(self):
17      map(self.run_command, self.get_sub_commands())
18      return classdict["run"](self)
19    d["run"] = _
20    name = _get_ns()+class_name.lower()
21    cls = type.__new__(cls, name, class_bases + (object,), d)
22    cls.description = cls.__doc__
23    if class_name != "BaseCommand" : _cmds[name] = cls
24    return cls
25class BaseCommand(Command): __metaclass__ = CommandType
26
27@contextmanager
28def namespace(name):
29  _namespace.append(name)
30  yield _get_ns()
31  _namespace.pop()

これだけです。これでかなり簡単にタスクが作れるようになります。例を示しましょう。

 1with namespace("db") as ns:
 2  with namespace("create") as ns:
 3    class Test1(BaseCommand):
 4      """test1 discription
 5      """
 6      user_options = [ ("host=", "h", "hostname"),
 7                      ("port=", "p", "port"),
 8                      ("force", "f", "force execute")]
 9      def run(self):
10        print self.host
11        print self.force
12        print "hello, ", self.__class__.__name__
13
14      sub_commands = [(ns+"test2",None)]
15
16    class Test2(BaseCommand):
17      """test2 discription
18      """
19      def run(self):
20        print "hello, ", self.__class__.__name__
21
22
23  class Test3(BaseCommand):
24    """test3 discription
25    """
26    def run(self):
27      print "hello, ", self.__class__.__name__
28
29class Test4(BaseCommand):
30  """test4 discription
31  """
32  user_options = [ ("host=", "h", "hostname") ]
33  def run(self):
34    print "hello, ", self.__class__.__name__
35
36  def finalize_options(self):
37    if not self.host:
38      raise ValueError("host must not be None")
39
40
41setup(cmdclass=_cmds)

かなり直感的になったのではないでしょうか。withを使えば名前空間も結構簡単に実現できます。コマンドのヘルプを見てみましょう。

 1# python task.py --help-commands
 2Standard commands:
 3  build            build everything needed to install
 4  build_py         "build" pure Python modules (copy to build directory)
 5  build_ext        build C/C++ extensions (compile/link to build directory)
 6  build_clib       build C/C++ libraries used by Python extensions
 7  build_scripts    "build" scripts (copy and fixup #! line)
 8  clean            clean up temporary files from 'build' command
 9  install          install everything from build directory
10  install_lib      install all Python modules (extensions and pure Python)
11  install_headers  install C/C++ header files
12  install_scripts  install scripts (Python or otherwise)
13  install_data     install data files
14  sdist            create a source distribution (tarball, zip file, etc.)
15  register         register the distribution with the Python package index
16  bdist            create a built (binary) distribution
17  bdist_dumb       create a "dumb" built distribution
18  bdist_rpm        create an RPM distribution
19  bdist_wininst    create an executable installer for MS Windows
20
21Extra commands:
22  db_test3         test3 discription
23
24  db_create_test2  test2 discription
25
26  db_create_test1  test1 discription
27
28  test4            test4 discription
29
30
31usage: task.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
32  or: task.py --help [cmd1 cmd2 ...]
33  or: task.py --help-commands
34  or: task.py cmd --help

こんな感じで、標準コマンドに加え「Extra commands」という形で定義したタスクが使えるようになります。もちろん、distutilsのビルド機能、パッケージ管理機能も使えますよ。

db_create_test1のヘルプを見てみましょう。

 1# python task.py db_create_test1 --help
 2Common commands: (see '--help-commands' for more)
 3
 4  setup.py build      will build the package underneath 'build/'
 5  setup.py install    will install the package
 6
 7Global options:
 8  --verbose (-v)  run verbosely (default)
 9  --quiet (-q)    run quietly (turns verbosity off)
10  --dry-run (-n)  don't actually do anything
11  --help (-h)     show detailed help message
12
13Options for 'db_create_test1' command:
14  --host (-h)   hostname
15  --port (-p)   port
16  --force (-f)  force execute
17
18usage: task.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
19  or: task.py --help [cmd1 cmd2 ...]
20  or: task.py --help-commands
21  or: task.py cmd --help

ちゃんとオプションが表示されていますね。最後に、db_create_test1タスクを実行してみましょう。

1# python task.py db_create_test1 --host=localhost
2running db_create_test1
3running db_create_test2
4hello,  Test2
5localhost
6None
7hello,  Test1

サブコマンドのdb_create_test2が実行されていること、きちんとオプションがselfの属性として設定されていることがわかりますね。


標準的な機能だけでもRakeのようなタスクはつくれますが、30行程度の工夫で断然便利になります。Rakeと違いrakeコマンドのような外部コマンドも必要なく、pythonのスクリプト1つで実現できるのも手軽です。というわけで、雑多なタスクはdistutilsでまとめてみてはいかがでしょうか。


最近は手が空いたときにまとめてお題をとくようになってきました。 どう書く?org です。あいかわらず、Scalaです。はい。

そのどう書く?orgですが、今のままだとちょっと「多言語クックブック」にはならない気がしてます。クックブックってのが曖昧ですが、あくまで俺の中のイメージのクックブックです。

一般投稿が可になった時に、ちょっと感じてたんですがやっぱり最近ある傾向が顕著で。というのは数学パズル系とか、(数学的な、事務処理などではない)アルゴリズム勝負!なお題ばっかりなんですよね。プログラミング自体が好きな人ってやっぱり関数型言語界隈の方々だったり、数学的なバックグラウンド持っている人が多いですから。一般投稿が可になればまぁそういう方々がお題を投稿してくださることが多いのかな、と思ってます。

もちろん、頭使ってそういうアルゴリズム考えたりするのは楽しいんですけどね、そればっかりだとちょっと・・・。ならお前がお題考えろ、ですよね、ほんとすみません。

  • JPEGをGETして色反転して保存
  • アクセスログのIPアドレスを逆引き
  • ローカル変数の一覧を取得

こういうお題、結構好きだったんですよね。この言語ってどんなことできるんだろ、ライブラリとかそろってるのかな、っていういろんな側面を見せてくれるじゃないですか。

一方、数学パズル系とかだとほとんどがリスト処理とか、コレクションの処理になっちゃう。ぶっちゃけていえば、「その言語だから」っていうのが少なくなるって言うか・・・。大抵そのままほかの言語に移植可能だったり・・・。もちろん、Haskellだから、遅延評価だから、Lispだから、マクロだから、Cだからビット演算だから、とかはあると思うんですがね。

実用系のお題とパズル系のお題が3:7くらいだと一番個人的にはうれしいかなあ。         そんな私は、最近、ボーナスがでた勢いで(音楽用)キーボード、エレキギターを相次いで購入。あれほど音楽はもうやめよっかな、と思っていたのに。機材を実家においてきたのに。ほんとに好きなものって、やっぱやめられないんですね。願わくば、俺にとってプログラミングもそうであらんことを。