( ꒪⌓꒪) ゆるよろ日記

( ゚∀゚)o彡°オパーイ!オパーイ! ( ;゚皿゚)ノシΣ フィンギィィーーッ!!!

Seasar Conference 2009 Autumnでscalaについて発表しました。

Seasar Conference 2009 Autumnに、Javaプログラマに捧げるScala入門というタイトルで登壇させて頂きました。
当日はあいにくの天候にも関わらず、たくさんの方にご来場頂きありがとうございました。


発表資料は、Seasar Conference 2009 Autumnのセッション情報のページに掲載されていますが、SlideShareにも置いておきます。


また、デモについては別のエントリで補足します。
Seasar Conference 2009 Autumnでのデモ - ゆろよろ日記



Scalaの言語仕様を一気に説明するのは無理だと思っていたので、直前で構成を変えてデモを最初に見せるようにしました。
まず、デモを見てもらって「Scalaって変態言語だからすぐに使うのは無理じゃね?」っていう先入観を破壊する作戦です。
ご来場された方々がどう感じたかはわかりませんが、「ちょっと使ってみよう」と少しでも思っていただけたらうれしいです。


実際、デモで見せた「S2Conteinerをscalaの対話型インタプリタから操作する」方法は、私が携わっている案件で活用しつつあります。

後半すっ飛ばした部分についての補足

やっぱり時間不足で、Scalaにおける「進化したオブジェクト指向」の部分を説明しきれなかったのが心残りです。
ここで少し補足しようと思います。


ってかねー、Scalaの言語仕様を50分で説明とか無謀なんスよ。
後半省略したtraitとかcaseクラスなんかまだ序の口で、implicit系やGenericsやextractorあたりがオモシロイんだけどそこまでたどり着くには2,3時間必要だっつーの。


scala-beで「Scalaについて0からみっちり説明しますっ!」的な勉強会とか企画してみようかしらん・・・。

クラスとオブジェクト
  • クラスの定義は、class クラス名( 引数名:型,...) extends スーパークラス with Trait...{ ...}
    • クラスの定義では、コンストラクタ引数を指定できる。
    • コンストラクタ引数にval/varを付与することで、自動でアクセサを定義
      • コンストラクタ引数にvalをつけると、読み取り専用フィールドとなる。class Foo(val name) でnameフィールドを読み取り可能。
      • コンストラクタ引数にvarをつけると、読み取り/書き込み可能フィールドとなる。class Foo(var name) でnameフィールドを変更可能。
    • コンストラクタ引数にもprivateなどのアクセス修飾子やoverrideを指定できる
    • コンストラクタを複数持たせる場合は、def this( i:Int ... )などのようにdef thisで定義する
    • メソッドをoverrideする場合は、明示的にoverrideキーワードを付与しないとコンパイルエラーとなる。ovreride def toString( s:String) = "foo"
  • クラス定義はネストできる。インナークラスやインナーオブジェクトなど。
  • object オブジェクト名でシングルトンオブジェクトを定義できる
  • 同一スコープに同じ名前のクラスとオブジェクトが存在する場合、相互にprivateアクセスが可能な特権的アクセス権が付与される。(コンパニオンオブジェクト)
  • staicが無いScalaでは、特定クラスに関するメタ操作はコンパニオンオブジェクトに実装することが多い。
  • コンパニオンオブジェクトにapplyメソッドを実装して、クラスに対するファクトリメソッドを提供するパターンが多用される。
    • List(1,2,3)でListオブジェクトが生成できるのは、Listオブジェクトのdef apply[A](xs : A*) : List[A]メソッドでListオブジェクトを生成してるから
class  Foo ( s:String, i:Int )  {
   val  p1:String = s
   var  p2:Int = i
   private  var  p3  =  1
   def hoge  = s * p3
}
object Foo {
  val aaa ="aaa"
  def setP3( foo:Foo, i:Int) = foo.p3 = i
}

上記のようなFooクラスとFooオブジェクトは、相互にprivateアクセスできるため、Foo.setP3( foo,10)でfooオブジェクトのprivateフィールドのp3フィールドを変更している。

traitとmixin
  • trait は実装をもてるinterface
    • すごく乱暴な説明。traitは、コンパイルされるとクラスになる
    • どちらかというとabstractクラス
  • classやobjectに複数のtraitをMix-In
    • class Foo extends Bar with Baz
      • Fooクラスは、"Bar"traitと"Baz"traitからフィールド、メソッドを継承
    • 複数のtraitをMixinする場合は、クラス階層は"直列化"されるので、ダイヤモンド継承問題は発生しない
      • class Foo extends Bar with Bazは、Foo → Baz → Bar のような継承関係になる
  • trait の中で実装を持たないメンバはabstract
    • 関数以外に、フィールドもabstrat扱い
    • valで宣言したフィールドをdefで関数としてoverrideできる。(逆も可能)
trait  Bar  {
   val   bs:String = "Bar"
   def  bar( n:Int )  = bs * n
}
trait Baz { def baz( n:Int ) = "Baz" * n }

class Foo extends Bar with Baz

上記のように、"Bar"traitと"Baz"traitをMixinしたFooクラスは、"Bar"traitのbar関数と、"Baz"traitのbaz関数を継承している。

scala> val foo = new Foo
foo: Foo = Foo@4069b25f

scala> foo.bar(1)
res0: String = Bar

scala> foo.baz(3)
res1: String = BazBazBaz
caseクラスとパターンマッチ
  • case class <クラス名>(コンストラクタ引数)
    • case class は名前(...)でインスタンス生成( immutable object)
      • case class Foo( i:Int,s:String)で、Foo(1,"a")のようにインスタンス生成
      • コンパイラがファクトリメソッドを生成する
    • case classのコンストラクタ引数はすべて"val"を付与される
      • コンストラクタ引数に対して自動で読み取り専用のアクセサを定義
    • case classでは、コンストラクタ引数を元に自動的にtoString,equals,hashCodeが追加される
      • Foo(1,"a") == Foo(1,"a") はtrue
  • case クラスはパターンとして使える
  • match式はswitchの強力なやつ
    • 値 match { case パターン => 処理 ...}
    • javaのswitchは定数しか許容しないが、match式は値/型/caseクラスなどを指定できる
      • 定数パターン "aaa" => {...} ← 定数として"aaa"にマッチ
      • 変数パターン a => {println( a )} ← aにはマッチした値が束縛され、=> {...}の中で使用できる。正規表現のパターンマッチのイメージ
      • コンストラクタパターン Foo(1,s) => {...} ← Fooはcaseクラスなど。sは変数パターンで、任意の値にマッチ。
      • 型付きパターン case i:Int => {...} ← Int型にマッチ
      • ガード条件 case i if i > 0 => { ...} ← i > 0を満たす場合のみマッチする
    • case _ => はデフォルトのパターン
    • パターンは、記述した順番に評価される
    • match式は必ず値を返す
    • マッチするパターンが無い場合は、MatchErrorがthrowされる
    • マッチ式の網羅性は、コンパイラによってチェックされる
      • 必ずマッチしないパターンは警告が発生する
  • シールドクラス
    • シールドcaseクラスとしてマッチ式に適用される可能性がある複数のクラスが存在する場合は、すべてのパターンを記述しないとコンパイルエラー
    • sealed abstract class Aを継承するcase classにB,C,Dがある場合
    • def test( a:A) = { a match { case B => {print("B")}, case C=>{print("C") }
      • これは、Dのパターンが無いためコンパイルエラー
sealed abstract class Pt
case class CC(s:String) extends Pt
case class DD(i:Int ,s:String) extends Pt

def test(p:Pt) = p  match {
   case CC( s ) => println( s )
   case DD( 3 ,_) => println("match DD")
}

上記のように抽象クラスPtを継承する2つのcase class CCとDDがある場合

scala> val cc = CC( "CCCC") // ファクトリでインスタンス生成
cc: CC = CC(CCCC)

scala> cc == CC("CCCC") // equalsがオーバライドされているので別インスタンスでもtrue
res2: Boolean = true

scala> val dd1 = DD( 1,"DD1")
dd1: DD = DD(1,DD1)

scala> val dd2 = DD( 3,"DD2")
dd2: DD = DD(3,DD2)

scala> test( cc) // 1番目のパターンにマッチしている
CCCC

scala> test ( dd1 )  // マッチしないのでエラー
scala.MatchError: DD(1,DD1)
	at .test(<console>:17)
	at .<init>(<console>:20)
	at .<clinit>(<console>)
	at RequestResult$.<init>(<console>:3)
	at RequestResult$.<clinit>(<console>)
	at RequestResult$result(<console>)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.Delegati...
scala> test (dd2) // 2番目のパターンにマッチしている
match DD

最後に

ご来場いただいた方々には厚く御礼申し上げます。
また、発表の機会を与えていただいたSeasarファウンデーションの各位に、深く感謝したいと思います。


これを機会に、scalaに興味をもってくれる人が増えてくれるといいなぁと、思います。