成らぬは人の為さぬなりけり

エンジニアライフをエンジョイする為のブログ

Scala2.10の新機能を勉強する その2 ImplicitClass

前回のつづきで、
今日はScala2.10のImplicitClassをやってみたいと思います。

環境

ImplicitClassって?

簡単に言うと、ImplicitConversionを楽に書ける機能、
と理解して良いんでしょうかね?
SIP-13 - Implicit classes - Scala Documentation

つまり

  • 変換する型の定義
  • 変換するメソッドの定義

が一発で書ける。

とりあえず、書いてみます。

ImplicitClassを書いてみる


こんな感じで書いてみました。
「HalfInt」はclassの定義とimplicit defの定義が必要ですが、
「DoubleInt」はimplicit classの定義だけで済んでいます。
これは確かに完結で良い。
ただ、これ、同じようにコンパイルされるんだろうか?
という事で、jadってみます。

package implicitclass;

import scala.Function0;
import scala.Predef$;
import scala.runtime.*;

// Referenced classes of package implicitclass:
//            ImplicitClass_01$

public final class ImplicitClass_01
{
    public static class HalfInt
    {

        public int half()
        {
            return i / 2;
        }

        private final int i;

        public HalfInt(int i)
        {
            this.i = i;
            super();
        }
    }

    public static class DoubleInt
    {

        public int _mthdouble()
        {
            return i * 2;
        }

        private final int i;

        public DoubleInt(int i)
        {
            this.i = i;
            super();
        }
    }

    public static class delayedInit.body extends AbstractFunction0
    {

        public final Object apply()
        {
            Predef$.MODULE$.println(BoxesRunTime.boxToInteger($outer.DoubleInt(1)._mthdouble()));
            Predef$.MODULE$.println(BoxesRunTime.boxToInteger($outer.toHalfInt(1).half()));
            return BoxedUnit.UNIT;
        }

        private final ImplicitClass_01$ $outer;

        public delayedInit.body(ImplicitClass_01$ $outer)
        {
            if($outer == null)
            {
                throw new NullPointerException();
            } else
            {
                this.$outer = $outer;
                super();
                return;
            }
        }
    }


    public static void main(String args1[])
    {
        ImplicitClass_01$.MODULE$.main(args1);
    }

    public static void delayedInit(Function0 function0)
    {
        ImplicitClass_01$.MODULE$.delayedInit(function0);
    }

    public static String[] args()
    {
        return ImplicitClass_01$.MODULE$.args();
    }

    public static void scala$App$_setter_$executionStart_$eq(long l)
    {
        ImplicitClass_01$.MODULE$.scala$App$_setter_$executionStart_$eq(l);
    }

    public static long executionStart()
    {
        return ImplicitClass_01$.MODULE$.executionStart();
    }

    public static HalfInt toHalfInt(int i)
    {
        return ImplicitClass_01$.MODULE$.toHalfInt(i);
    }

    public static DoubleInt DoubleInt(int i)
    {
        return ImplicitClass_01$.MODULE$.DoubleInt(i);
    }
}

ほぼ同じようにコンパイルされてるっぽい(?)

ImplicitClassを切り出してみる

次にImplicitClassを切り出してみます。

コメントにも書いてありますが、
implicit classをトップレベルで書けないようです。
以下のようなエラーになりました。

[error] /Users/xxxx/workspace/scala-samples/scala-2.10-sample/src/main/scala/implicitclass/ImplicitClass_02.scala:10: `implicit' modifier cannot be used for top-level objects
[error] implicit class TripleInt(i: Int) {
[error]                ^
[error] one error found
[error] (compile:compile) Compilation failed

importに関しては、implicit defで定義する時も同じような使いかたをするだろうし、
あまり変わらないかな??

今日は、ここまでにします。
次回はValueClassに挑戦。
(内容薄いですが、メモなので、、、、(逃

Scala2.10の新機能を勉強する その1 StringInterpolation

久々のブログですが、
今更ながらScala2.10の新機能をちゃんと勉強しておこうと思います。

※終わったらRuby2.0やろうと思ってますが、、、

今回のテーマ

StringInterpolation
です。

環境

StringInterpolationって?

Rubyの式展開のように、文字列リテラル内に、式を書く事ができる機能です。

Interpolationって、どういう意味だろうか、、、
初めて聞いたので、意味がわからず、、調べてみました。

alc

http://eow.alc.co.jp/search?q=interpolation&ref=sa
→〔他のものに〕挿入すること、差し挟むこと

なるほど、文字列になんか差し込むので、StringInterpolation、なんですね。
とりあえず言葉の意味は理解できました。

式を埋め込んでみる

まずは、単純に変数の値を埋め込んでみます。

これで、

Hello hoge!

となります。
Rubyの記法に比べると{}が無くて、#が$になった感じですね。

では、次に、計算式やらメソッド呼び出しやらを埋め込んでみます。

ここでは{}が必要な場面が登場しました。
変数もしくは引数なしのメソッド呼び出しは{}は不要ですが、
それ以外は必要になるようです。
※当然といえば当然ではある、、、。

フォーマットを指定して埋め込む

次はフォーマットを指定して値を埋め込んでみたいと思います。

ここで最後の行はエラーになりました。
コメントにも書きましたが、
piはDouble型になるので、Intを期待するフォーマット「%d」は使えません。
※コンパイル時にエラーになります

[error] /xxxx/workspace/scala-samples/scala-2.10-sample/src/main/scala/interpolation/StringInterpolation_03.scala:10: type mismatch;
[error]  found   : Double
[error]  required: Int
[error]   println(f"$hoge is $pi%2.4d") // これはpiがDoubleなので、エラーになる
[error]                       ^
[error] one error found
[error] (compile:compile) Compilation failed

仕組みを知る

このStringInterpolationの仕組みはStringContextのメソッド飛び出しで実現されているようです。
試してみます。

こんな感じ。

hoge"...."

これは

StringContext(....).hoge(....)

に展開されるようなので、
ImplicitConversionと組み合わせれば、他にも便利なリテラルが実現できそうです。

他の機能を勉強してからやってみたいと思います。

Scala勉強日誌 - Akka その1

Scala勉強日誌 - Actor - 成らぬは人の為さぬなりけり
大分前にActorの勉強して、続きでAkkaの勉強しようと思ってて、完全に忘れていたので、
再開したいと思います。
(仕事で必要になって、勉強したので、メモしているだけ、、、)

今日のテーマは、、、

  • SBTプロジェクトを作る
  • AkkaのActorを書いてみる
  • ActorからActorを呼び出して、結果を受け取ってみる
  • Routerを使ってみる。

yagince/akka_practice · GitHub

例によって、環境は、、、

  • OS:MacOSX10.8
  • Scala : 2.10.0
  • sbt :0.12.2
  • Akka : 2.1.0

SBTプロジェクトを作る

まずは、プロジェクトを作ります。
今回は、全部ビルド定義ファイルは全部Scalaで書きたかったので、
こんな感じで作成
project/Build.scala

import sbt._
import Keys._

object BuildSettings {
  val buildOrganization = "yagince"
  val buildVersion      = "0.0.1"
  val buildScalaVersion = "2.10.0"

  val buildSettings = Defaults.defaultSettings ++ Seq (
    organization := buildOrganization,
    version      := buildVersion,
    scalaVersion := buildScalaVersion
  )
}

object Resolvers {
  val typeSafe = "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/"
  val otherResolvers = Seq(typeSafe)
}

object Dependencies {
  val akkaCore = "com.typesafe.akka" %% "akka-actor" % "2.1.0"
}

object AkkaPracticeBuild extends Build {
  import Resolvers._
  import Dependencies._
  import BuildSettings._

  val dependencies = Seq (
    akkaCore
  )
  
  val project = Project (
    "akka-practice",
    file("."),
    settings =
      buildSettings ++ Seq (
        resolvers := otherResolvers,
        libraryDependencies ++= dependencies
      )
  )
}

project/build.properties

sbt.version=0.12.2

AkkaのActorを書いてみる

では、早速書いてみます。

src/main/scala/akka/sample/AkkaExample01.scala

package akka.sample

import actor.PrintActor
import akka.actor.{Props, ActorSystem}

object AkkaExample01 extends App {
  val system = ActorSystem("sample")
  val actor = system.actorOf(Props[PrintActor], "hoge")

  actor ! "HelloWorld!"

  system.shutdown
}
package akka.sample

import actor.PrintActor
import akka.actor.{Props, ActorSystem}

object AkkaExample01 extends App {
  val system = ActorSystem("sample")
  val actor = system.actorOf(Props[PrintActor], "hoge")

  actor ! "HelloWorld!"

  system.shutdown
}

src/main/scala/akka/sample/actor/PrintActor.scala

package akka.sample.actor

import akka.actor.Actor

class PrintActor extends Actor {
  def receive = {
    case x => println(x)
  }
}

実行結果

> run-main akka.sample.AkkaExample01
[info] Running akka.sample.AkkaExample01 
HelloWorld!
[success] Total time: 0 s, completed 2013/02/13 22:16:22

単純に渡したオブジェクトを出力するだけのアクターです。
これだけ見ても、scala標準Actorより記述量が少ないですね。
そして、シンプル!
素晴らしい。

ActorからActorを呼んで結果を受け取ってみる

Scala標準Actorの同期メソッドみたいのあるのかな??
無さそう???と思っていたら、
こんな風にするのが普通なのかな???
というわけで書いてみます。

package akka.sample

import akka.actor.{ActorSystem, Actor, Props}
import akka.routing.RoundRobinRouter

object AkkaExample02 extends App {
  val system = ActorSystem("sample")
  val actor = system.actorOf(Props[Master])

  actor ! 100

  Thread.sleep(500)
  system.shutdown
}

class Master extends Actor {
  val actor = context.actorOf(Props[DoubleActor])

  def receive = {
    case i:Int => actor ! i
    case Doubled(x) => println("received : %d".format(x))
  }
}

class DoubleActor extends Actor {
  def receive = {
    case i:Int => sender ! Doubled(i*2)
  }
}

case class Doubled(i:Int)

senderへ結果を返して、呼び出し側のreceiveへメッセージパッシングする感じですかね?
確かに、これはシンプルで綺麗だ。
よくよく考えると、同期メソッドって必要ないんじゃなかろうか、、、

Routerを使ってみる

AkkaにはRouterという機能があるようです。
Actorのインスタンスを管理して、
Routerにメッセージ送信すると、
Actorへよしなにバランシングしながらメッセージを横流ししてくれるような感じでしょうか?
書いてみます。

package akka.sample

import actor.{Doubled, DoubleActor}
import akka.actor.{ActorSystem, Actor, Props}
import akka.routing.RoundRobinRouter

object AkkaExample02 extends App {
  val system = ActorSystem("sample")
  val actor = system.actorOf(Props[Master])

  (0 to 10).foreach(actor ! _)

  Thread.sleep(100)
  system.shutdown
}

class Master extends Actor {
  val router = context.actorOf(Props[DoubleActor].withRouter(RoundRobinRouter(2)))

  def receive = {
    case i:Int => router ! i
    case Doubled(x) => println("received : %d".format(x))
  }
}
> run-main akka.sample.AkkaExample02
[info] Running akka.sample.AkkaExample02 
received : 0
received : 2
received : 6
received : 10
received : 4
received : 14
received : 8
received : 18
received : 12
received : 16
received : 20
[success] Total time: 0 s, completed 2013/02/13 22:27:41

一個前の例のMasterクラスを書き換えて、
DoubleActorをRouterで管理するようにしてみました。
Routerの種類は

  • akka.routing.RoundRobinRouter
  • akka.routing.RandomRouter
  • akka.routing.SmallestMailboxRouter
  • akka.routing.BroadcastRouter
  • akka.routing.ScatterGatherFirstCompletedRouter
  • akka.routing.ConsistentHashingRouter

Routing (Scala) — Akka Documentation
これだけあるようです。
それぞれの違いは、また次回ということで、、、

さて、今日はここまでにします。

CoffeeScriptを書いてみる その2

前回は、
関数定義、文字列内変数展開をやってみました。
さて、今回のテーマは、、、

  • 可変長引数
  • レンジ
  • 比較演算子
  • 条件付き代入
  • 無名関数

をやってみたいと思います。

可変長引数

関数の引数に「...」をつけると可変長引数になります。

hoge = (ary...) ->
  console.log ary

hoge 1,2,3,4,5  

実行結果

[ 1, 2, 3, 4, 5 ]

ちゃんと配列で取得できているようですね。

レンジ

配列をレンジ(範囲)で作成することができます。
書き方はRubyとほぼ同じでした。

range = [1..10]
console.log range

実行結果

[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]

逆順にすることもできます。

reverse_range = [10..0]
console.log reverse_range

実行結果

[ 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 ]

「..」を「...」にすると、右端を含めないレンジになります。
※昇順でも降順でも同じ

reverse_range_2 = [10...0]
console.log reverse_range_2

実行結果

[ 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 ]

比較演算子

等価比較

CoffeeSriptでは「==」を使うと「===」にコンパイルされます。
なので、==で型も含めた比較になります。
※CoffeeScriptにはJavaScriptでいう==に相当する比較演算子は無いそうです。

console.log 1 == "1"

実行結果

false

では、同じ事をJavaScriptでやってみます。
今回はNode.jsを使ってやってみます。

$ node
> 1 == "1"
true
> 1 === "1"
false

「1=="1"」はJavaScriptではtureになりますね。

「==」以外にも「is」という演算子が用意されています。

coffee> 1 is "1"
false

※ファイルに書くのがめんどくさくなってきたので、辞めました。。。
「is」は「===」にコンパイルされるようです。
他にも自然言語でかける演算子がいくつか用意されています。

coffee> 1 is "1"
false
coffee> 1 isnt "1"
true
coffee> not true
false
coffee> not 1
false
coffee> not undefined
true
coffee> not null
true
coffee> true and false
false
coffee> yes
true
coffee> no
false
coffee> false or true
true

こういうのはコードが非常にリーダブルになるので好き。

存在確認をする演算子もあります。

coffee> hoge?
false

RubyActiveSupportのObject#tryのような使い方もできます。

coffee> hoge?.foo
undefined
coffee> hoge = {foo: "foo"}
{ foo: 'foo' }
coffee> hoge?.foo
'foo'

条件付き代入

nullもしくは、undefinedの場合のみ代入する。
foo = null
foo ?= "foo"
console.log foo

実行結果

foo

※これはREPLからやると、エラーになる、、、

ちなみに、「?=」で代入する場合、変数「foo」は定義されている必要があります。
fooが未定義の場合


Error: In foo.coffee, the variable "foo" can't be assigned with ?= because it has not been defined.

と怒られました。

falseの時のみ代入する
bar = false
bar ||= "bar"
console.log bar

実行結果

bar

ちなみに、これもbarが定義されていないと怒られます。
これは、Rubyと同じで、条件的にfalseと判定できればなんでもいけるのか??
というわけで試してみます。

a = false
a ||= "Yes"
console.log a

b = 0
b ||= "Yes"
console.log b

c = null
c ||= "Yes"
console.log c

d = undefined
d ||= "Yes"
console.log d

e = ""
e ||= "Yes"
console.log e

実行結果

Yes
Yes
Yes
Yes
Yes

なるほど。
変数が定義されてさえいれば、あとはRubyと同じようなノリなのかな。

変数が定義されていない場合に代入する、というのは、こんな感じで書くんでしょうか?

coffee> hoge = hoge ? "hoge"
'hoge'

無名関数

最後に、無名関数を関数の引数に渡す場合の書き方です。
基本的には、関数定義時となんらかわりありません。

setTimeout ->
  console.log "Hoge"
, 1000

実行結果

$ coffee nameless.coffee                                                                                                    
Hoge

一秒待って、Hogeと表示されます。
ちなみに、当たり前な事ではありますが、こう書くと、意図しない動きになります。。。

setTimeout ->
  console.log "Hoge"
  , 1000

実行結果

$ coffee nameless.coffee                                                                                                    
Hoge 1000

setTImeoutに対して、

console.log("hoge",1000)

を呼び出す関数を渡した事になるので、当たり前ではありますが、
最初ちょっと???になりました。
(インデントに慣れていないので、、、)

今日はここまで。
次は配列の操作とかクラス定義とかやろうと思ってます。

CoffeeScriptを書いてみる その1

前回、CoffeeScriptの環境を作ったので、
早速書き始めてみようと思います。

今回のテーマ

  • 関数定義
  • 文字列内変数展開

関数定義

CoffeeScriptでの関数定義はこんな感じになるようです。
helloworld.coffee

helloworld = -> console.log "hello world"

呼び出してみます。

helloworld()

実行

$ coffee helloworld.coffee
hello world

引数がある関数を定義してみます。
square.coffee

square = (x) -> x * x
console.log square(10)

実行

$ coffee square.coffee
100


関数呼び出す時に、引数は括弧なしでも呼び出せるっぽい??
ちょっと試してみます。
hoge.coffee

hoge = (x,y) -> x + y
console.log hoge 1, 2

実行

$ coffee hoge.coffee
3

括弧なしでも呼び出せるっぽい、ですね。
もう一発試してみます。
hoge2.coffee

hoge = (x,y) -> x + y
hoge2 = (x,y,z) -> x + y + z
console.log hoge 1, hoge2 2, 3, 4

実行

$ coffee hoge2.coffee
10

おぉ、できた、なるほど。

では、引数なしの場合も括弧は省略できるんでしょうか?
CoffeeScriptはRuby+Pythonライクらしいので、
Rubyライクであればできるだろうし、Pythonライクであれば関数オブジェクトが返ってくるのかな?
やってみます。

function.coffee

f = -> "called"
console.log f

実行

$ coffee function.coffee
[Function]

なるほど、関数オブジェクトが返ってくるっぽい。
以下のように変更して実行してみます。

f = -> "called"
console.log f
console.log f()
console.log f.call()

実行

$ coffee function.coffee
[Function]
called
called

こうなるんですね、なるほど。

では、このcoffeeファイルをJavaScriptにコンパイルしてみましょう。

$ coffee -c function.coffee

コンパイルするときは、「-c」をつけて実行します。
結果

// Generated by CoffeeScript 1.4.0
(function() {
  var f;

  f = function() {
    return "called";
  };

  console.log(f);

  console.log(f());

  console.log(f.call());

}).call(this);

大体予想どおりではありました。
ちなみに、

  var f;

  f = function() {
    return "called";
  };

これは、

  var f = function() {
    return "called";
  }

こうならないのは何故なんでしょう?
前者のほうが効率が良いんでしょうか?
JavaScriptの言語仕様に詳しくないので、理由はわかりませんでした。。。

文字列内変数展開

CoffeeScriptはRubyのように文字列リテラル内で変数展開ができるようです。
試してみます。
string_interpolation.coffee

i = 1
console.log "hoge #{i} foo"

実行

$ coffee string_interpolation.coffee
hoge 1 foo

構文はRubyまんまですね。
では、Rubyみたいに式展開はできるのでしょうか?

i = 1
console.log "hoge #{i + 100} foo"

実行

$ coffee string_interpolation.coffee
hoge 101 foo

おぉー、できるんですね。なるほど。
関数呼び出しもできるのかな?

f = -> "aaaaaaaaa"
console.log "hoge #{f()} foo"

実行

$ coffee string_interpolation.coffee
hoge aaaaaaaaa foo

まぁあたりまえか。できますよね。

では、こいつもコンパイルしてみましょう。

i = 1
console.log "hoge #{i} foo"
console.log "hoge #{i + 100} foo"

f = -> "aaaaaaaaa"
console.log "hoge #{f()} foo"

string_interpolation.js

// Generated by CoffeeScript 1.4.0
(function() {
  var f, i;

  i = 1;

  console.log("hoge " + i + " foo");

  console.log("hoge " + (i + 100) + " foo");

  f = function() {
    return "aaaaaaaaa";
  };

  console.log("hoge " + (f()) + " foo");

}).call(this);

なるほど、文字列連結になるんですね。

というわけで、今日はここまで。
(もうちょっと仕様を深く知りたいなぁ)

HomebrewでNode.js入れて、npmでCoffeeScriptを入れる

CoffeeScriptを勉強する為に環境を構築したので、メモ。

環境

  • OS : MacOSX 10.8.2
  • Node.js : 0.8.16
  • npm : 1.1.71
  • CoffeeScript : 1.4.0
  • Homebrew : 0.9.3

インストール手順

  1. HomebrewでNode.jsをインストール
  2. npmをインストール
  3. CoffeeScriptをインストール

brewから直接CoffeeScriptをインストールできるみたいですが、
npmの管理下に置かれないみたいなので、
別々にインストールすることにしました。


HomebrewでNode.jsをインストール

brew install node

npmをインストール

ここでちょっとはまる。。。orz

curl http://npmjs.org/install.sh | sudo sh    

これでインストールできます、という記事をちらほら見てやってみると、

sh: line 1: syntax error near unexpected token `newline'
sh: line 1: `<html>Moved: <a href="https://npmjs.org/install.sh">https://npmjs.org/install.sh</a>'

というエラーでインストールに失敗します。

メッセージを見ると、どうやら「https」に移動されたようです。
httpsに直して、実行して無事インストールできました。
(これに気づくのに1時間程、、、)

curl https://npmjs.org/install.sh | sudo sh    

CoffeeScriptをインストール

sudo npm install -g coffee-script   

今回は-gをつけて、グローバルにインストールしました。
(つけないとローカルインストールになるので、node_modulesにインストールされるようです)

早速書いて実行してみる

helloworld.coffee

helloworld = -> console.log "hello world"
helloworld()

実行

coffee helloworld.coffee
hello world

実行できたようです。
次回はEmacs24のpackageを使ってcoffee-modeを入れてみようかな、、、

Play2.0でsbtの独自タスクを定義する

sbtの独自タスク追加方法は、ググればちらほら出てくるのですが、
Playを使ったsbtの独自タスクの定義方法がよくわからず、
ちょっと苦労したので、メモします。
(苦労した、という程の事でもないのですが。。。)

今回の環境

OS : Mac OS X 10.7.5
Scala : 2.9.1
sbt : 0.11.3
Play : 2.0.4

今日の流れ

  • sbtでの独自タスク定義方法
  • playでの独自タスク定義方法

sbtで独自タスク

sbtで環境依存ファイルを変更するためのカスタムタスクを書いてみたよ - ブログなんだよもん
(こちらを参考にさせて頂きました)
まず、sbtオンリーの場合は、上記サイトにあるように、
こんな感じで書きます。

project/Build.scala

import sbt._

object ProjectBuild extends Build {
  val hogeKey = TaskKey[Unit]("hoge", "hoge task")

  val hoge = hogeKey := {
    println("hoge")
  }

  lazy val root = Project(
    id = "my task",
    base = file("."),
    settings = Defaults.defaultSettings ++ Seq(hoge)
  )
}
  1. TestKeyでkeyと説明を定義
  2. :=メソッドを使ってタスクを定義
  3. Projectのsettingsへ追加

という流れになります。

実行してみます。

% sbt hoge
[info] Loading global plugins from /Users/xxx/.sbt/plugins
[info] Loading project definition from /Users/xxx/workspace/scala/scala_practice/project
[info] Set current project to my task (in build file:/Users/xxx/workspace/scala/scala_practice/)
hoge
[success] Total time: 0 s, completed 2012/12/03 22:26:54

ちゃんとhogeと出力されました。

Playプロジェクトでsbt独自タスクを定義する

PlayのBuild.scalaはこんな感じです。


project/Build.scala

import sbt._
import Keys._
import PlayProject._

object ApplicationBuild extends Build {

    val appName         = "hoge"
    val appVersion      = "1.0"

    val appDependencies = Seq(
      "org.squeryl" %% "squeryl" % "0.9.5" withSources(),
      "mysql" % "mysql-connector-java" % "5.1.18"
    )

  lazy val main = PlayProject(appName, appVersion, appDependencies, mainLang = SCALA).settings(
    resolvers += "t2v.jp repo" at "http://www.t2v.jp/maven-repo/"
  )
}

PlayではProjectがPlayProjectになっています。


PlayProjectのapplyメソッドを見てみると。

package sbt
object PlayProject extends java.lang.Object with sbt.Plugin with sbt.PlayExceptions with sbt.PlayKeys with sbt.PlayReloader with sbt.PlayCommands with sbt.PlaySettings with scala.ScalaObject {
  def apply(name : scala.Predef.String, applicationVersion : scala.Predef.String = { /* compiled code */ }, dependencies : scala.Seq[sbt.ModuleID] = { /* compiled code */ }, path : sbt.File = { /* compiled code */ }, mainLang : scala.Predef.String = { /* compiled code */ }, settings : => scala.Seq[sbt.Setting[_]] = { /* compiled code */ }) : sbt.Project = { /* compiled code */ }
}

「settings」というパラメータがある!
というわけで、以下のようにしてみました。

import sbt._
import Keys._
import PlayProject._

object ApplicationBuild extends Build {

    val appName         = "ripple"
    val appVersion      = "1.0"

    val appDependencies = Seq(
      "org.squeryl" %% "squeryl" % "0.9.5" withSources(),
      "mysql" % "mysql-connector-java" % "5.1.18",
      "jp.t2v" % "play20.auth_2.9.1" % "0.3" withSources()
    )

  lazy val main = PlayProject(appName, appVersion, appDependencies, mainLang = SCALA, settings = Seq(Tasks.hogeTask)).settings(
    resolvers += "t2v.jp repo" at "http://www.t2v.jp/maven-repo/"
  )

  object Tasks {
    val hogeTaskKey = TaskKey[Unit]("hoge", "test task")
    lazy val hogeTask = hogeTaskKey := {
      println("hoge")
    }
  }
}

で、playコマンド実行。
エラー、、、

...
[error] References to undefined settings: 
...

うーむ。
エラーメッセージからなにがいけなかったのか、よくわかりません。
と、いうわけで、型をちゃんと調べることにしました。


[追記]
ここの原因は、id:xuweiさんからご指摘頂き、

Defaults.defaultSettings ++ Seq(Tasks.hogeTask)

Defaults.defaultSettingsを忘れている事だと分かりました。

そもそも:=の型は??
    def :=(value : => S) : sbt.Project.Setting[sbt.Task[S]] = { /* compiled code */ }
PlayProjectのsettingsの型は?
settings : => scala.Seq[sbt.Setting[_]]


なるほど、そもそも型が違うようです。
[訂正]id:xuweiさんからのご指摘により、同じ型である事がわかりました。

しかし、よくよく見ると、
Projectにはsettingsというメソッドが用意されている。

Project#settingsの型は?
  def settings(ss : sbt.Project.Setting[_]*) : sbt.Project = { /* compiled code */ }

「sbt.Project.Setting」これだ!

というわけで、結論、以下のようにして解決しました。

import sbt._
import Keys._
import PlayProject._

object ApplicationBuild extends Build {

    val appName         = "hoge"
    val appVersion      = "1.0"

    val appDependencies = Seq(
      "org.squeryl" %% "squeryl" % "0.9.5" withSources(),
      "mysql" % "mysql-connector-java" % "5.1.18",
      "jp.t2v" % "play20.auth_2.9.1" % "0.3" withSources()
    )

  lazy val main = PlayProject(appName, appVersion, appDependencies, mainLang = SCALA).settings(
    resolvers += "t2v.jp repo" at "http://www.t2v.jp/maven-repo/",
    Tasks.hogeTask
  )

  object Tasks {
    val hogeTaskKey = TaskKey[Unit]("hoge", "test task")
    lazy val hogeTask = hogeTaskKey := {
      println("hoge")
    }
  }
}

実行してみます。

% play 
[info] Loading global plugins from /Users/xxx/.sbt/plugins
[info] Loading project definition from /Users/xxx/workspace/ripple/project
[info] Set current project to ripple (in build file:/Users/xxx/workspace/ripple/)
        _               _ 
 _ __ | | __ _ _  _| |
| '_  \| |/ _' | || |_|
|  __/|_|\____|\__ (_)
|_|            |__/ 
             
play! 2.0.4, http://www.playframework.org

> Type "help play" or "license" for more information.
> Type "exit" or use Ctrl+D to leave this console.

[hoge] $ hoge
hoge
[success] Total time: 0 s, completed 2012/12/03 23:02:27

ちゃんと表示されました。

うーん、勉強不足だ、、、すっごい遠回りした。。orz

[追記]
一応、自分のメモとして、訂正前の状態も書き残しましたが、
ちゃんと調べずに、誤った事を記載してしまい申し訳ありませんでした。
今後も、じゃんじゃんご指摘頂けると助かります。