Gracenote with Groovy

みなさんこんばんは、12月9日担当の@tetsurokitaharaです。すでに日付が変わっていますが、まだ夜明け前だということで、ご勘弁願えればと思います。

Grailsセミナー開催報告

本題に入る前に、私が所属する某大学で、NTTソフトウェアの@_y_u_さんと@uehajさんを招いて「迅速Webアプリケーション開発セミナー」というGrailsセミナーを行いました。9時から18時までほぼブッ続けのスケジュールで、Grailsの使い方の基礎の基礎から始めて、乱数を用いたちょっとしたジョークWebアプリ(「診断メーカー」的なもの)を作るところまで、一気にガツガツ進めていくというものでした。受講者の9割ぐらいがGrails/GroovyどころかWebアプリの開発経験がないという状況でしたが、受講者からの評判はかなり高く、続編を強く希望する声が上がった中での終了となりました。セミナー終了後は懇親会を開き、@_y_u_さんと@uehajさんのお二人には、お忙しいところ懇親会にもご参加いただきました。@_y_u_さん、@uehajさん、本当にありがとうございました。

セミナーなどの写真はこちら
http://www.flickr.com/photos/99632794@N08/sets/72157637329746515/

Get started with Gracenote with Groovy

略したらGGGになるようにしたかったんですが、あまりいいのが思いつきませんでした。それはともかく…。G*関連のAdvent Calendarということでネタを考えたんですが、全然思いつかず、ウチの学生が卒業研究で取り組んでるものからネタになりそうなものを拾ってこようと考えた結果(苦笑)、Gracenoteを取り上げることにしました。これだってGから始まるんだから文句あるか、ってことで。

まあ、こんな言い訳じみた前フリはこのぐらいにして、Gracenoteというのは音楽CDに関するデータベースを提供しているサービスです。Web APIが用意されていて、これを使うことで音楽CDに関する(発売年やジャンルなどといった)情報を取得することができます。あらかじめGracenoteのサイトでClient IDを作成しておけば、クエリー用のXMLドキュメントをPOSTメソッドでWebサーバに投げたら、検索結果がXML形式で返されてくる、という至極単純明快な方式です。GroovyにはMarkupBuilderとかXMLを扱いやすくする仕組みがあるので、そのあたりを試してみるための題材としては丁度いいかと。

アカウント作成

まずは、アカウントを作成します。
https://developer.gracenote.com/?language=ja
にアクセスして、常識に従ってやればアカウントは簡単に作れます。アカウントを作成したらさっそくログインしてみましょう。ログインしたら「マイアプリ」(My Apps)をクリックし、ページ下部にある「Add a new app」をクリックしましょう。ここでアプリの名前を登録するとClient IDなどを取得することができます。今回はとりあえず試すだけですから、mysample1 という極めていい加減なアプリ名にしました。

追加されたアプリの名前をクリックすると、「Client ID for Mobile Client, Web API, and eyeQ」という欄があって、「8桁の10進数+ハイフン+32桁の16進数」からなる文字列があるのが分かると思います。これがClient IDで、Gracenoteのサーバにアクセスする際に必要になります。

ユーザIDの取得

なぜなのか分かりませんが、Client IDだけではダメで、User IDを取得する必要があります。User IDを取得するには、

<QUERIES>
  <QUERY CMD='REGISTER'>
    <CLIENT>nnnnnnnn-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</CLIENT>
  </QUERY>
</QUERIES>

(「nn…xxxx」にはClient IDが入る)
https://cnnnnnnnn.web.cddbp.net/webapi/xml/1.0/ (nnnnnnnnはClient IDの最初の8桁)にPOSTメソッドで投げてやります。すると、

<RESPONSES>
  <RESPONSE STATUS="OK">
    <USER>mmmmmmmmmmmmmmmmmm-yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy</USER>
  </RESPONSE>
</RESPONSES>

という形式でUser IDの情報が返ってきます。

とりあえず、ここまでをGroovyで書いてみましょうか。HTTPBuilderを使いますので、あらかじめインストールしておいてください。こんな感じで書くことができますね。

import groovy.xml.*
import groovyx.net.http.*
import static groovyx.net.http.ContentType.XML

CLIENT_ID = "nnnnnnnn-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

def register = new StringWriter()
def xml = new MarkupBuilder(register)
xml.QUERIES {
  QUERY(CMD: "REGISTER") {
    CLIENT(CLIENT_ID)
  }
}
println register

def http = new HTTPBuilder("https://c${CLIENT_ID.split('-')[0]}.web.cddbp.net")
http.post(path: '/webapi/xml/1.0/', body: register, requestContentType: XML) { response ->
  def returnedXml = http.parseResponse(response, XML)
  println XmlUtil.serialize(returnedXml)
}

前半では、MarkupBuilderを使ってクエリー用のXMLドキュメントを生成しています。実は何気にMarkupBuilderを使うのは初めてなんですが、なかなか便利ですね。MarkupBuilderオブジェクトに対するメソッド呼び出しで、存在しないメソッドが呼ばれると、XMLエレメントの追加とみなして同名のエレメントを追加し、メソッド呼び出しの引数にMapオブジェクトがあれば属性、Stringオブジェクトがあればテキストノードを追加、クロージャがあれば再帰的に子エレメントを追加する、という仕組みのようですね。追加しようと思っているエレメント名と同名のメソッドをimport staticしてたりすると、エレメント追加の意味にならなくてハマるので、そこだけ注意が必要です。

後半では、このXMLドキュメントをHTTPBuilderで投げているだけです。HTTPBuilderクラスにはpostというメソッドがあるので、これを使うのが便利です。httpsアクセスがうまく行くと、そのレスポンスが引数となってクロージャが呼び出されますので、そこで行うべき処理を書きます。ここでは、とりあえずXML形式で表示しているだけです。

アーティストを指定してCDアルバムを検索してみよう

アーティスト名などのクエリーを投げてCDアルバムの情報を取得する手順も、基本的にはClient IDからUser IDを取得するのと変わりません。たとえば、PerfumeのCDアルバム一覧を取得しようと思ったら、次のようにすればOKです。

import groovy.xml.*
import groovyx.net.http.*
import static groovyx.net.http.ContentType.XML

CLIENT_ID = "nnnnnnnn-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
USER_ID = "mmmmmmmmmmmmmmmmmm-yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"

def query = new StringWriter()
def xml = new MarkupBuilder(query)
xml.QUERIES {
  AUTH {
    CLIENT(CLIENT_ID)
    USER(USER_ID)
  }
  LANG("jpn")
  QUERY(CMD: "ALBUM_SEARCH") {
    TEXT(TYPE: "ARTIST", "Perfume")
  }
}
println query

def http = new HTTPBuilder("https://c${CLIENT_ID.split('-')[0]}.web.cddbp.net")
http.post(path: '/webapi/xml/1.0/', body: query, requestContentType: XML) { response ->
  def returnedXml = http.parseResponse(response, XML)
  println XmlUtil.serialize(returnedXml)
}

簡単ですね。QUERYのところには複数の条件を設定できます。
ためしに、

  TEXT(TYPE: "ARTIST", "Perfume")

の次の行に

  TEXT(TYPE: "TRACK_TITLE", "ねぇ")

を追加してもう一度実行してみましょう。

次に、単に得られたXMLドキュメントを表示させるだけでなく、パージングしてみましょう。上のプログラムのreturnedXmlという変数は、XMLドキュメント全体を表すと同時にトップレベルエレメントも表していると考えることができ(正確な説明かどうかはよく分かりませんが)、あたかもプロパティにアクセスするかのようにXMLドキュメントをたどっていくことができます。Gracenoteが返すXMLドキュメントは、トップレベルエレメントがRESPONSES、その中にREPONSEがあって、その中に検索されたCDアルバム数分だけALBUMエレメントがあるという構造になっているので、例えば次のようにして検索結果を取得することができます。

import groovy.xml.*
import groovyx.net.http.*
import static groovyx.net.http.ContentType.XML

CLIENT_ID = "nnnnnnnn-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
USER_ID = "mmmmmmmmmmmmmmmmmm-yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"

def query = new StringWriter()
def xml = new MarkupBuilder(query)
xml.QUERIES {
  AUTH {
    CLIENT(CLIENT_ID)
    USER(USER_ID)
  }
  LANG("jpn")
  QUERY(CMD: "ALBUM_SEARCH") {
    TEXT(TYPE: "ARTIST", "Perfume")
    TEXT(TYPE: "TRACK_TITLE", "ねぇ")
  }
}
println query

def http = new HTTPBuilder("https://c${CLIENT_ID.split('-')[0]}.web.cddbp.net")
println http
http.post(path: '/webapi/xml/1.0/', body: query, requestContentType: XML) { response ->
  def returnedXml = http.parseResponse(response, XML)
  returnedXml.RESPONSE.ALBUM.each {
    println it.ARTIST
    println it.TITLE
    println it.DATE
  }
}

ARTISTはアーティスト名、TITLEはCDアルバムのタイトル(TRACK_TITLEは曲名)、DATEは発売年を表します。詳細は、 https://developer.gracenote.com/web-api を見てください。

使いやすいように処理をメソッドにまとめよう

https://developer.gracenote.com/web-api を見ると、PythonJavaRuby用のunofficialなラッパークラスが用意されているのが分かります。でもGroovyを使えば、ラッパークラスを作ったり、誰かが作ったのをダウンロードしなくても、簡単にGracenote Web APIを使うことができるというのが、お分かりいただけたと思います。とはいえ、定型処理はやっぱりメソッドにまとめた方が分かりやすいと思うので、ラッパークラスを作るところまでは行く必要ないと思いますが、処理をメソッドにまとめましょう。たとえばこんな感じです。

import groovy.xml.*
import groovyx.net.http.*
import static groovyx.net.http.ContentType.XML

CLIENT_ID = "nnnnnnnn-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
USER_ID = getUserID(CLIENT_ID)


def getUserID(clientID) {
  def register = new StringWriter()
  def xml = new MarkupBuilder(register)
  xml.QUERIES {
    QUERY(CMD: "REGISTER") {
      CLIENT(clientID)
    }
  }
  def http = new HTTPBuilder("https://c${clientID.split('-')[0]}.web.cddbp.net")
  http.post(path: '/webapi/xml/1.0/', body: register, requestContentType: XML) { response ->
    def returnedXml = http.parseResponse(response, XML)
    return returnedXml.RESPONSE.USER.text()
  }
}

def searchAlbums(artist, trackTitle, clientID, userID) {
  def query = new StringWriter()
  def xml = new MarkupBuilder(query)
  xml.QUERIES {
    AUTH {
      CLIENT(clientID)
      USER(userID)
    }
    LANG("jpn")
    QUERY(CMD: "ALBUM_SEARCH") {
      TEXT(TYPE: "ARTIST", artist)
      TEXT(TYPE: "TRACK_TITLE", trackTitle)
    }
  }
  def http = new HTTPBuilder("https://c${CLIENT_ID.split('-')[0]}.web.cddbp.net")
  http.post(path: '/webapi/xml/1.0/', body: query, requestContentType: XML) { response ->
    def returnedXml = http.parseResponse(response, XML)
    return returnedXml.RESPONSE.ALBUM
  }
}


searchAlbums("Perfume", "ねぇ", CLIENT_ID, USER_ID).each {
  println "「${it.TITLE}」(${it.DATE}年発売)"
}

実行結果

「JPN」(2011年発売)
「ねぇ」(2010年発売)
「JPN」(2011年発売)
「JPN」(2011年発売)
「JPN」(2011年発売)
「ねぇ」(年発売)
「Perfume JPN」(2012年発売)
「ねぇ」(2010年発売)
「ねぇ」(年発売)
「VOICE」(年発売)

データベースの管理・維持をどのように行っているのか詳しくは知らないのですが、データが完全ではないので表記の揺れや欠落があったりします。実際にアプリケーションを作る際にはそのあたりの考慮も必要です。

おわりに

GracenoteのWeb APIをGroovyから叩くといっても、単にXMLドキュメントをpostするとXMLドキュメントが返ってくるというだけなので、GroovyにおけるXMLドキュメントの扱いに慣れている方にとっては物足りない記事になってしまったかもしれませんが、MarkupBuilderなどXML関連のテクニックを試すには、Gracenote Web APIというのは、なかなか手頃で楽しい題材だと思います。

明日(っていうか今日)は、@ykubota さんです。

GroovyにProcessingを! G* Advent Calender 2012

どうも、はじめまして。@tetsurokitahara です。

ノリでG* Advent Calenderに参戦してみたのの、そもそもブログ自体やったことないのにどうしたものかなあ、と思っていて、でもそう思っていたのはホンノ一瞬で、エントリーしたことをすっかり忘れてしまい、今、思い出して、とりあえず、はてなダイアリーを開設してみたところです。

Processingとは

で、自分が気持ちよくプログラムを書けるかというのも大事なんですが、職業柄、どういう言語なら初学者が挫折せずに楽しみながらプログラミングを習得できるか、ということに興味があります。Rubyなんかお約束の記述が少なくて初学者がプログラミングを体験するのに適していると思いますが、初学者向けのRubyのプログラミングの解説書とかって、やっぱりgetsとputsから入って文字列をナンヤカンヤして・・・ってな感じで、CUIが中心なんですよね。今の若い人って、CUI時代のソフトウェアを見たことがないので、CUIなプログラムが書けても何もうれしくありません。極端な話、CUIプログラミングで学んだ条件分岐や繰り返し、配列などの概念がGUIプログラミングでもやっぱり必須なんだということがそもそも想像できない人もいるわけです。

楽しみながらプログラミングを覚えるといえば、やっぱりゲーム作りですよね。で、ゲームを作るにはグラフィックス表示が必須です。そういうグラフィックスを用いたゲームを初学者がちろっと作ってみるのに、もっとも適したのは、Processing (http://www.processing.org/) だと思います。たとえば、

float t = 0;
float dt = 0.2;

// 一番最初に呼ばれる関数
void setup() {
  size(200, 200);                // ウィンドウサイズを設定
}

// 1/60秒ごとに自動実行
void draw() {
  background(0);                 // 画面を黒色で初期化
  float x = 100 + 100 * cos(t);
  float y = 100 + 100 * sin(t);
  ellipse(x, y, 10, 10);         // 円を描画
  t += dt;
}

// マウスボタンがクリックされたら呼ばれる関数
void mouseClicked() {
  if (dt > 0)
    dt = 0;
  else
    dt = 0.2;
}

と書くだけで、白いボールがウィンドウをぐるぐる回ります。

Processingでは、setup()関数が最初に呼ばれ、その後、draw()関数が1/60秒ごと(変更可能)に自動的に呼ばれます。line、rect、ellipseなどの、グラフィックス描画用の組み込み関数が豊富にあり、これらを使うことで簡単にグラフィックスを描画できます。イベントハンドリングは、keyPressed(), mouseClicked(), mousePressed(), mouseReleased(), mouseDragged()などのコールバック関数を定義することで簡単に実現できます。

これはこれでよくできているのですが、Processingは裏でJavaプログラムに変換されコンパイル、実行されます。なので、ちょっと凝ったことをしようと思ったら、例外ハンドリングを覚えないといけないし、ArrayListやHashMapといったコレクションフレームワークも覚えないといけません。Javaマスターのみなさんからしたら、それぐらいおぼえたらええやん(なぜか似非関西弁)と思われるかもしれませんが、こういうことを覚える心理的負荷は結構大きいものです。だいたい、初学者からしたら、なぜ配列の他にVectorだのArrayListだの似たようなものがいろいろあるのかよく分からないし。

ProcessingでGroovy的な書き方をできるようにする

そこでようやく本題なのですが、ProcessingをGroovyに書けたらいいんじゃないか、と。そうすれば、例外ハンドリングもしなくてもとりあえず怒られないし、ArrayListだのHashMapだのいろいろなクラスを覚える必要もなくなるだろう、と。巷にはGrocessingというまさに全く同じコンセプトのものがあるらしいのですが、いろいろ調べても実態がいまいち分かりませんでした。

Groovyが(ほぼ)Javaの上位互換になっているように、GroovyなProcessingも通常のProcessingの上位互換になってほしいです。
やり方は、単純です。Processingでは、上のプログラムはPAppletというクラスのサブクラスとして扱われます。つまり、Processingの実行系は、上のプログラムを
public class mysketch extends PApplet {

}
で囲んでmain関数を補っていてコンパイルします。なので、同じことをGroovyでやって、Groovy上で実行すれば、Groovyのシンタックスシュガーなどがそのまま使えるはず、という寸法です。

で、実際作ってみたのがコレです。

def s1 = ["import processing.core.*", 
          "class MyApp extends PApplet {"]
def s2 = ["}", 
       	  "(new MyApp()).runSketch()"]
if (args.length < 1) {
  System.err.println("Usage: groovy processing.groovy <script-file>")
  exit(1)
}
def script = (new File(args[0])).readLines()
def head = script.findAll { 
  it.trim().startsWith("package") || it.trim().startsWith("import") 
}
def whole_code = (head + s1 + (script - head) + s2).join("\n")
(new GroovyShell()).parse(whole_code).run()

このファイルをprocessing.groovyとし、実行したいProcessingのプログラムをmysketch.pdeとすると、
$ groovy processing.groovy mysketch.pde
とすれば実行できます。
(Processingに付属するcore.jarが必要です。)

やってることは単純で、指定されたファイルを読み込んで、それを「class MyApp extends PApplet {」と「}」で囲んでるだけです。
ただし、import文はclass文より前にないといけないので、そこだけ
抜き出して先頭にくっつけてます。Processingにpackage文を使うのかどうかはよく分かりませんが、一応それも先頭にくっつけています。
あとは、GroovyShellにパースさせて実行させているだけです。

一応、これでGroovyっぽくProcessingプログラムを書けるようになります。たとえば、こんな感じ。

def balls = []

void setup() {
  size(200, 200)
}

void draw() {
  background(0)
  if (random(1) < 0.01) {
    balls.add([x: random(200), y: 0])
  }
  balls.each {
    it.y++
    ellipse(it.x, it.y, 10, 10)
  }
}

上の例では、リストとマップをGroovyに書いてます。あと、一応申し訳程度にクロージャも使っています。うーん、どうでしょう。個人的には結構おもしろいと思うんですけどね。さらに発展させて、DSLだの演算子オーバーロードだのいろいろやってみるとか。みなさん、あんまり興味ないですかね、こういうの。

ただ、これには結構致命的な欠点があって、たとえばellipseを間違えてelipseとか書いちゃうと、こうなります。

Exception in thread "Animation Thread" groovy.lang.MissingMethodException: No signature of method: MyApp.elipse() is applicable for argument types: (java.lang.Float, java.lang.Integer, java.lang.Integer, java.lang.Integer) values: [81.713745, 1, 10, 10]
Possible solutions: list(), ellipse(float, float, float, float), enable(), hide(), size(), delete()
	at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:55)
	at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:78)
	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:46)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:133)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:153)
	at MyApp$_draw_closure1.doCall(Script1.groovy:16)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:601)
	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
	at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
	at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:272)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:877)
	at groovy.lang.Closure.call(Closure.java:412)
	at groovy.lang.Closure.call(Closure.java:425)
	at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:1376)
	at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:1348)
	at org.codehaus.groovy.runtime.dgm$162.invoke(Unknown Source)
	at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoMetaMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:271)
	at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:53)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
	at MyApp.draw(Script1.groovy:14)
	at processing.core.PApplet.handleDraw(Unknown Source)
	at processing.core.PApplet.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:722)

MyApp$_draw_closure1.doCall(Script1.groovy:16)
とか書いてあるから16行めが原因かと思いきや、これはprocessing.groovyがコードを追加した後のスクリプトでの16行目ですから、実際は14行目です。ちょっと注意が必要ですね。

おわりに

G*界隈ではグラフィクスといえばGroovyFX!という感じですが、とにかく単純明快!お手軽!という意味では、Processingはかなりいいと思います。最近のプログラミング環境はいろいろ便利になってるのは間違いないんですが、その便利さを実感できるようになるには、始めにいろいろ勉強しないといけない場合が多いように思います。その点、Processingは、学んだ量とそれによってできる事柄とが、かなり線形に近い印象があって、初学者でも苦にならずに学べると思います。GroovyとProcessingがタッグを組めば、初学者向けのプログラミング環境としてはかなりいいものができるんじゃないか、と日頃から思っているわけです。

今回、スクリプトを文字列レベルでいじるという、かなりイケてない手法を取りましたが、もうちょっとなんとかしたいですね。ユーザが書いた(Groovyな)Processingスクリプトをmysketch.groovyというファイルに保存したとすると、Groovyではmysketchというクラスとして扱われるわけですが、これを動的にPAppletのサブクラスに変更できないのでしょうか。調べてみたのですが、クラスの継承関係を動的に変えるというのは、よく分かりませんでした。Groovyならそういうことできてもいいのにな、と思うのですが。その方が動的言語っぽさを活かしてるっぽくておもしろいような気がするんですが。

みなさんも、ぜひ「GroovyなProcessing」で遊んでみていただけると幸いです。



編集後記:こんな内容で良かったのかなぁ。